Operator precedence: F# and Haskell

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...

Haskell

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#

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