In the following code taken from Elm Form Example, line 122, what does the <<
operator mean?
Field.field Field.defaultStyle (Signal.send updateChan << toUpdate) "" content
Couldn't find it in Elm syntax reference.
Does it mean, when the field changes, instead of sending its content
to updateChan
, send toUpdate
to updateChan
?
<<
is a function composition operator, defined in core library Basics
. All functions from Basics are imported into Elm projects unqualified.
Let's recall basics of Elm type system.
Elm is statically typed. This means that in Elm every variable or function has a type, and this type never changes. Examples of types in Elm are:
Int
String
Maybe Bool
{ name : String, age : Int }
Int -> Int
Int -> String -> Maybe Char
.Static typing means that compiler ensures types of all functions and variables are correct during compilation, so you don't have runtime type errors. In other words, you'll never have a function of type String -> String
receiving or returning Int
, code that allows this won't even compile.
You can also make your functions polymorphic by replacing a concrete type such as String
or Maybe Int
with a type variable, which is an arbitrary lowercase string, such as a
. Many Elm core functions are type polymorphic, for example List.isEmpty
has the type List a -> Bool
. It takes a List
of some type and returns a value of type Bool
.
If you see the same type variable again, then instances of this type variable must be of same type. For example List.reverse
has type List a -> List a
. So if you apply List.reverse
to a list of integers (i.e. to something that has type List Int
), it will return a list of integers back. No way such function can take a list of integers, but return a list of strings. This is guaranteed by the compiler.
All functions in Elm are curried by default. This means that if you have a function of 2 arguments, it is transformed into a function of 1 argument that returns a function of 1 argument. That's why you function application syntax of Elm is so different from function application in other languages such as Java, C++, C#, Python, etc. There's no reason to write someFunction(arg1, arg2)
, when you can write someFunction arg1 arg2
. Why? Because in reality someFunction arg1 arg2
is actually ((someFunction arg1) arg2)
.
Currying makes partial application possible. Suppose you want to partially apply List.member
. List.member
has a type a -> List a -> Bool
. We can read the type as “List.member
takes 2 arguments, of type a
and type List a
”. But we can also read the type as “List.member
takes 1 argument of type a
. It returns a function of type List a -> Bool
”. Therefore we can create a function isOneMemberOf = List.member 1
, which will have the type List Int -> Bool
.
This means that ->
in type annotations of functions is right-associative. In other words, a -> List a -> Bool
is the same as a -> (List a -> Bool)
.
Any infix operator is actually an ordinary function behind the curtains. It's just when a function name consists solely of non-alphanumeric symbols (such as $, <|, <<, etc), it is placed between 2 arguments, not in front of them (like ordinary functions).
But you still can put a binary operator like +
in front of the 2 arguments, by enclosing it in parentheses, so the 2 function applications below are equivalent:
2 + 3 -- returns 5
(+) 2 3 -- returns 5, just like the previous one
Infix operators are just ordinary functions. There's nothing special about them. You can partially apply them just like any other function:
addTwo : Int -> Int
addTwo = (+) 2
addTwo 3 -- returns 5
(<<)
is a function composition operator, defined in core library Basics
. All functions from basics are imported into Elm projects unqualified, meaning you don't have to write import Basics exposing (..)
, it is already done by default.
So just like any other operator, (<<)
is just a function, like any other. What is its type?
(<<) : (b -> c) -> (a -> b) -> a -> c
Because ->
is right-associative, this is equivalent to:
(<<) : (b -> c) -> (a -> b) -> (a -> c)
In other words, (<<)
takes 2 functions of types b -> c
and a -> b
respectively, and returns a function of type a -> c
. It composes 2 functions into one. How does that work? Let's look at a contrived example for simplicity's sake. Suppose we have 2 simple functions:
addOne = (+) 1
multTwo = (*) 2
Suppose we don't have (+)
, only addOne
, how would we create a function that adds 3, not 1? Very simple, we would compose addOne
together 3 times:
addThree : Int -> Int
addThree = addOne << addOne << addOne
What if we want to create a function that adds 2, then multiples by 4?
ourFunction : Int -> Int
ourFunction = multTwo << multTwo << addOne << addOne
(<<)
composes functions from right-to-left. But the above example is simple, because all the types are the same. How would we find a sum of all even cubes of the list?
isEven : Int -> Bool
isEven n = n % 2 == 0
cube : Int -> Int
cube n = n * n * n
ourFunction2 : List Int -> Int
ourFunction2 = List.sum << filter isEven << map cube
(>>)
is the same function, but with arguments flipped, so we can write the same composition from left to right instead:
ourFunction2 = map cube >> filter isEven >> List.sum
When you see something like h << g << f
, then you know that f
, g
, h
are functions. When this construct h << g << f
is applied to a value x
, then you know:
f
to x
g
to the result of the previous steph
to the result of the previous stepTherefore (negate << (*) 10 << sqrt) 25
equals -50.0
, because you first take a square root of 25 and get 5, then you multiply 5 by 10 and get 50, then you negate 50 and get -50.
Before Elm 0.13 (see announcement) function composition operator was (.)
, and its behavior was identical to current (<<)
. (<<)
was adopted in Elm 0.13 from F# language (see Github issue). Elm 0.13 also added (>>)
as equivalent to flip (<<)
, and (<|)
as replacement for function application operator ($)
, and (|>)
as equivalent to flip (<|)
.
You might be wondering if you can turn an ordinary alphanumeric function name into an infix binary operator. Before Elm 0.18 you'd use backticks to make a function infix, so below 2 would be equivalent:
max 1 2 -- returns 2
1 `max` 2 -- returns 2
Elm 0.18 removed this feature. You can't do it in Elm anymore, but languages like Haskell and PureScript still have it.