Using the function map
to change the elements of a list has become a common pattern in modern programming languages. It is often used in Elm and map
is also used in Elm for the Maybe and Result types.
4 min read
·
By Eivind Dagsland Halderaker
·
December 23, 2021
Using the function map
to change the elements of a list has become a common pattern in modern programming languages. It is often used in Elm and writing a function to double all the integers in a list in Elm can be written as such
doubleInts list =
List.map (\number -> number * 2) list
As expected, running the function doubleInts [1,2,3,4]
will return [2,4,6,8]
. You might wonder what other types map is defined for in Elm and how they operate.
In Elm there is a type called Maybe
which is used when a function is not guaranteed to return a proper value. A value of Maybe
has two variants, it can either be a value, written
Just value
or not a value, written as
Nothing
To give an example, say you have a String
and want to convert it into a Float
using the function String.toFloat
, the String
might not represent a valid floating point number. As such, it's type signature is a String
as parameter and Maybe Float
as it's return type.
toFloat : String -> Maybe Float
If given the String
"0.5"
String.toFloat "0.5"
it will return the value
Just 0.5
as "0.5" is a valid floating point number.
On the other hand, if given the string "abc"
String.toFloat "abc"
the conversion to a floating point number fails and as expected the returned value is
Nothing
Both these value still have the type Maybe Float
as both are valid attempts of parsing a floating point number.
The Maybe
type is common in functional programming languages, such as Haskell's Maybe
and Scala with it's Option
. It is used often in functional programming instead of throwing exceptions and null-values.
The definition of the type Maybe
in Elm is:
type Maybe a
= Just a
| Nothing
When mapping a list, each element is transformed by a given function. In the case of a Maybe
, there is either a value or not a value, and can thought of as mapping either an list with a single element or an empty list. You're hoping there's an element inside the Maybe
box to run the function on, however, if the Maybe
is a Nothing
, it will just stay as a Nothing
after mapping the function.
Even though mapping a function of a Nothing
value will not change the value itself, it will still affect the type of the returned Maybe
. Say one wants to check if the Float
inside is greater than 5 by running
Maybe.map (\float -> float > 5) maybeFloat
If give an actual value
Maybe.map (\float -> float > 5) (Just 0.5)
The returned value still is a Maybe
, but it's type has changed from Maybe Float
to Maybe Bool
.
When given a Nothing
Maybe.map (\float -> float > 5) Nothing
There is nothing to compare to, and as such the returned value will still be
Nothing
and it's type will be Maybe Bool
, as we tried to compare the potential floating point value to 5.
The definition of map
for a Maybe
is
map : (a -> b) -> Maybe a -> Maybe b
map function maybe =
case maybe of
Just value ->
Just (function value)
Nothing ->
Nothing
Similar to Maybe
, Elm has a type to represent values that either succeeded or gave an error which is called Result
. It has two variants:
Ok value
and
Err error
Defined in Elm as
type Result error value
= Ok value
| Err error
Given that you want to parse a JSON object as a String
, "{ "height": 5}".
parseJSON : String -> Result String Int
parseJSON JsonString = Decode.field "height" Decode.int
and check that the height is higher than 10 we can use map
on the Result
value
higherThan10 : Result String Int -> Result String Int
higherThan10 resultHeight = Result.map (\height -> height > 10) resultHeight
Similar to Maybe
Result.map (\height -> height > 10) (Ok 5)
will return
Ok False
and
Result.map (\height -> height > 10) (Err "Failed to decode JSON object")
will return
(Err "Failed to decode JSON object")
Here is the definition of map on a Result
map : (a -> b) -> Result error a -> Result error b
map function resultValue =
case resultValue of
Ok value ->
Ok (function value)
Err error ->
Err error
The way map
is defined for different types is inspired by the Functor design pattern in functional programming, which in turn is derived from category theory in maths. In general for a value to be a Functor it has have a function map
with a type signature map : (a -> b) -> f a -> f b
, where f
is the type constructor, i. e. Maybe, Result, or List. If a functor maps the identity function, it has to return the functor unchanged.