forked from orion/obsidian
108 lines
2.6 KiB
Markdown
108 lines
2.6 KiB
Markdown
|
Infix operators are symbols that alias binary (2-argument) [[Functions|functions]].
|
||
|
|
||
|
They're defined like so:
|
||
|
```haskell
|
||
|
infixl <precedence> <fn> as <operator>
|
||
|
-- or
|
||
|
infixr -- ..
|
||
|
```
|
||
|
|
||
|
e.g.
|
||
|
```haskell
|
||
|
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.
|
||
|
```haskell
|
||
|
|
||
|
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
|
||
|
|
||
|
```haskell
|
||
|
a + b == c
|
||
|
```
|
||
|
|
||
|
this behaves how you'd expect; this is equivalent to
|
||
|
|
||
|
```haskell
|
||
|
((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` or `f >>> g`
|
||
|
- function application has `f $ a` or `a # f`
|
||
|
- [[Functor|map]] has `f <$> a` or `a <#> f`
|
||
|
- [[Bind|bind]] has `f =<< m` or `m >>= f`
|
||
|
|
||
|
In general, right-to-left operators tend to be easier to refactor into & out of because they closely mirror the expressions they replace:
|
||
|
|
||
|
```haskell
|
||
|
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:
|
||
|
|
||
|
```haskell
|
||
|
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:
|
||
|
```haskell
|
||
|
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.
|