An introduction to a few FP concepts with Elm.
Functions are curried by default in Elm.
From Wikipedia: "the technique of translating the evaluation of a function that takes multiple arguments (or a tuple of arguments) into evaluating a sequence of functions, each with a single argument".
> String.repeat
<function> : Int -> String -> String
> String.repeat 2 "Elm is Great!"
"Elm is Great!Elm is Great!" : String
> String.repeat 2
<function> : String -> String
> repeatTwice = String.repeat 2
<function> : String -> String
> repeatTwice "Elm is Great!"
"Elm is Great!Elm is Great!" : String
We can apply arguments to functions in the way we saw above, by separating with whitespace. E.g. functionName <arg1> <arg2>
etc. We can also use the two pipe operators |>
and <|
to control how we apply arguments to functions.
Here's a super basic example:
> "Elm is Great!" |> repeatTwice
"Elm is Great!Elm is Great!" : String
But this really shines when we want to perform a sequence of operations in a particular order:
> String.reverse
<function> : String -> String
> String.reverse "Elm is Great!"
"!taerG si mlE" : String
> repeatTwice (String.reverse "Elm is Great!")
"!taerG si mlE!taerG si mlE" : String
> "Elm is Great!" |> String.reverse |> repeatTwice
"!taerG si mlE!taerG si mlE" : String
> "Elm is Great!" |> String.reverse |> String.repeat 2
"!taerG si mlE!taerG si mlE" : String
>
We can also compose functions with compatible type signatures together to give us new functions. We use the >>
and <<
operators to do this (which one we chose depends on the order).
> repeatTwice >> String.reverse
<function> : String -> String
> (repeatTwice >> String.reverse) "Tom"
"moTmoT" : String
> "Tom" |> repeatTwice >> String.reverse
"moTmoT" : String
> repeatTwiceAndReverse = repeatTwice >> String.reverse
<function> : String -> String
> repeatTwiceAndReverse "Tom"
"moTmoT" : String
When order matters:
> addOneThenDouble = (+) 1 >> (*) 2
<function> : number -> number
> addOneThenDouble 6
14 : number
> doubleThenAddOne = (+) 1 << (*) 2
<function> : number -> number
> doubleThenAddOne 6
13 : number
Type signatures have to match up:
> repeatTwice
<function> : String -> String
> String.length
<function> : String -> Int
> repeatTwice >> String.length
<function> : String -> Int
> "foo" |> repeatTwice >> String.length
6 : Int
> repeatTwice << String.length
💥
Maybe formalises the concept that something may or may not exist.
type Maybe a = Just a | Nothing
> List.head
<function> : List a -> Maybe.Maybe a
> List.head []
Nothing : Maybe.Maybe a
> List.head [1,2,3,4]
Just 1 : Maybe.Maybe number
What if you want to perform an operation on a maybe type? Maybe.map!
Let's say we want to take the first of a list of integers and double it:
> double = (*) 2
<function> : number -> number
> List.head >> double
💥
> doubleTheHead = List.head >> Maybe.map double
<function> : List number -> Maybe.Maybe number
> doubleTheHead []
Nothing : Maybe.Maybe number
> doubleTheHead [1,2,3]
Just 2 : Maybe.Maybe number
>
There are a number of types which support mapping over the inner value of something which wraps them up (List
, Result
, Maybe
). In Learn You A Haskell, Mira Lipovaca talks about this concept of something in a box, which scurries out to have an operation perfomed on it, before scurrying back in to the box. You can also think of it as a value with some context. Elm doesn't really formalize the relationship between all of these, but Haskell does, with typeclasses. So all of these types which support map
ping over them implement the Functor
typeclass. You can think of it as an interface. The dreaded Monad
is just another typeclass which supports some specific operations and can be thought of as an extension of Functor
.
Feedback:
Maybe
section to allow visualising the concept a bit more