2.6 KiB
Infix operators are symbols that alias binary (2-argument) Functions.
They're defined like so:
infixl <precedence> <fn> as <operator>
-- or
infixr -- ..
e.g.
eq :: Int -> Int -> Boolean
eq = -- ...
add :: Int -> Int -> Int
add = -- ...
infixl 1 add as +
infixl 1 eq as ==
(1 + 2) == 3
-- same as
eq (add 1 2) 3
Associativity
Operators are either left or right associative (infixl
or infixr
).
When multiple infix operators with the same precedence are chained, associativity tells the language how to group them, e.g. &&
is right-associative, while *
is left associative.
e.g.
a && b && c
-- interpreted as
(a && (b && c))
a * b * c
-- interpreted as
((a * b) * c)
Precedence
The precedence of operators is an int from 1-9 used as a tie-break, for example in
a + b == c
this behaves how you'd expect; this is equivalent to
((a + b) == c)
as the precedence of +
(6) is higher than ==
's (4), the grouping is first done around a + b
.
Common Operators
Operator | Associativity | Precedence | Aliases |
---|---|---|---|
$ |
Directionality
Most commonly used operators have flipped variants, e.g.
- function composition has
g <<< f
orf >>> g
- function application has
f $ a
ora # f
- Functor has
f <$> a
ora <#> f
- Bind has
f =<< m
orm >>= f
In general, right-to-left operators tend to be easier to refactor into & out of because they closely mirror the expressions they replace:
map (add 1) maybeNum
-- into
add 1 <$> maybeNum
foo (bar a)
-- into
foo <<< bar
Left-to-right, on the other hand, can read better to humans and plays better with pipelines containing both [[Bind|bind >>=
]] and [[Functor|map <#>
]].
Consider an expression piping Maybe String
to split :: String -> Array String
then Data.Array.NonEmpty.fromArray :: Array a -> Maybe (NonEmptyArray a)
, then toLower
each element:
String.toLower
<$> (
Array.NonEmpty.fromArray
=<< String.split "."
<$> mStr
)
We need to wrap the right hand of the last map
because the precedence of =<<
is 1
(the lowest) and the precedence of <$>
is 4
.
Written RTL, though, gives:
mStr
<#> String.split "."
>>= Array.NonEmpty.fromArray
<#> String.toLower
This works because <#>
's precedence (1) is the same as >>=
. The lower precedence on flipped map means you'll often need more parentheses wrapping its arguments (..) <#> (..) >>= (..)
as opposed to entire expressions .. <$> (.. =<< ..)
.
Personally, I try to stick to RTL except for expressions including bind.