In the following code taken from Elm Form Example, line 122, what does the <<
operator mean?
Field.field Field.defaultStyle (Signal.send updat
My second attempt :D
<<
vs <|
The difference between <<
and <|
is that <<
is used to compose functions and <|
is used to omit parentheses.
Let's look at type annotation found here:
<< : (b -> c) -> (a -> b) -> a -> c
This definition tells us, that when you pass two functions to function <<
, you will get function a -> c
.
hi a =
a + 2
hello a =
a * 2
bye =
hello << hi
c =
bye 3
c
returns value 10
.
<< is a function composition - returns function.
Composition creates a pipe of computations, chain of functions. This pipe waits for input, and when provided, first function starts computation, sends output to next etc.
import Html
add x y =
Debug.log "x" x + Debug.log "y" y
add9 =
add 4 << add 5
main =
Html.text <| toString <| add9 2
Note: In the above example I use partial application. It means that I don't provide all parameters to function and as a result I get function.
If you run above example in web browser and look at the console output, you will see:
x: 5
y: 2
x: 4
y: 7
If we write it as math operations it will look like this:
4 + (5 + 2)
4 + 7
Note: We can also use forward version >>
.
Looking at signature of this operator:
(<<) : (b -> c) -> (a -> b) -> a -> c
For <<
operator, there is a function b -> c
as the first parameter, and a function a -> b
as the second:
(b -> c) << (a -> b)
But there is also a third parameter a
. Because ->
is right-associative, then
(<<) : (b -> c) -> (a -> b) -> a -> c
is equivalent to:
(<<) : (b -> c) -> (a -> b) -> (a -> c)
.
So that <<
returns function a -> c
.
In programming languages, the associativity (or fixity) of an operator is a property that determines how operators of the same precedence are grouped in the absence of parentheses; i.e. in what order each operator is evaluated:
a = b = c
is parsed as a = (b = c)
Here I use <<
as infix operator, but we can also use it as a prefix operator enclosing it with parenthesis: (<<) (b -> c) (a -> b)
or (<|) (add 4) (add 5)
.
elm < 0.18 used to let you take normal functions and use them as infix operators.
<|
operator<| is a function application - returns value
We basically use it instead of parentheses.
text (something ++ something)
can be written as
text <| something ++ something
So looking at signature of this operator:
(<|) : (a -> b) -> a -> b
we can see that for <|
operator, there is a function a -> b
as the first parameter, and value a
as the second:
(a -> b) <| a
and it returns b
.
We can get the same value with function application <|
:
v1 = add 4 <| add 5 <| 4
v2 = (add 4 << add 5) 4
|>
.<|
and <<
It is function composition. For your concrete example it means
\x -> (Signal.send updateChan (toUpdate x))
In elm it's not a part of the syntax but part of the standard library: Basics.<<
<< 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.
Explantion for javascript
developers:
--elm
(a << b) x
Will be similar
//javasript
a(b(x))
<<
or >>
is called function composition