Earlier this week I was looking at a beautiful snippet of F#
which defines a better backwards pipe operator: ^<|.
let inline (^<|) f a = f a
This got me thinking...
In Haskell the precedence of
an operator can be defined arbitrarily, via the infix,
infixr, and infixl commands.
The Haskell 2010 report describes many of the defaults
(§4.4.2), like so:
| Precedence | Left associative operators | Non-associative operators | Right associative operators |
|---|---|---|---|
| 9 | !! | . | |
| 8 | ^, ^^, ** | ||
| 7 | *, /, `div`, `mod`, `rem`, `quot` | ||
| 6 | +, - | ||
| 5 | :, ++ | ||
| 4 | ==, /=, <, <=, >, >=, `elem`, `notElem` | ||
| 3 | && | ||
| 2 | || | ||
| 1 | >>, >>= | ||
| 0 | $, $!, `seq` |
Therefore all the built in operators, operators in libraries, and so on behave however they're defined. For example this Haskell code:
map (3*) $ [1,2,5] !! 1 : []
Returns: [6]
But this doesn't compile because the !!
is of higher precedence and therefore [1]:1 would
be evaluated:
map (3*) $ [1] : [1,2,4] !! 1
The fixity and precedence of operators in libraries can
be checked in GHCi with the :info command:
Prelude> :module Control.Applicative Prelude Control.Applicative> :info <$> (<$>) :: (Functor f) => (a -> b) -> f a -> f b -- Defined in Data.Functor infixl 4 <$> Prelude Control.Applicative> :info <*> class (Functor f) => Applicative f where ... (<*>) :: f (a -> b) -> f a -> f b ... -- Defined in Control.Applicative infixl 4 <*> Prelude Control.Applicative>
(Note: applicative functors seem pretty awesome!)
By default, an operator behaves as if it were infixl 9.
F#, on the other hand, uses the initial character of the operator, and a predefined list of operator precedence.
MSDN describes the operator precedence, which behaves like so (highest to lowest, 26 levels):
| Left associative operators | Non-associative operators | Right associative operators |
|---|---|---|
| f<types> | ||
| f(x) | ||
| . | ||
| prefix operators (+op, -op, %, %%, &, &&, !op, ~op) | ||
| | (pattern match) | ||
| f x (function application) | ||
| ** op | ||
| * op, / op, % op | ||
| - op, + op, (binary) | ||
| :?>, :? | ||
| :: | ||
| ^ op | ||
| &&&, |||, ^^^, ~~~, <<<, >>> | ||
| < op, > op, =, | op, & op | ||
| &, && | ||
| or, || | ||
| , | ||
| := | ||
| -> | ||
| if | ||
| function, fun, match, try | ||
| let | ||
| ; | ||
| | (pipe) | ||
| when | ||
| as |
The difference between the built in <| and the
new ^<| is based on that, entirely.
(Above, you'll see the | for pipes is in the
lower left and the ^ is near the middle row right.)
let inline (<|) f a = f a // built in let inline (^<|) f a = f a // new
This means that the ^<| operator gets
a higher priority than the |> operator --
like the built in <| -- but the new one is
right associative, so it behaves like Haskell's $.
This allows things like those given by Stephen Swenson in the snippet:
module AssociativityComparison =
let forward_pipe =
{1..10} |> Seq.map (fun x -> x + 3) |> Seq.toList
let normal_backward_pipe =
Seq.toList <| (Seq.map (fun x -> x + 3) <| {1..10})
let high_prec_right_assoc_backward_pipe =
Seq.toList ^<| Seq.map (fun x -> x + 3) ^<| {1..10}
module PrecedenceComparison =
let normal_backward_pipe =
{1..10} |> (Seq.map <| (fun x -> x + 3))
let high_prec_right_assoc_backward_pipe =
{1..10} |> Seq.map ^<| fun x -> x + 3
This will be useful!
-- Kevin