More on functions
Type variables
Consider a function with a type signature like:
indexOf : String -> List String -> Int
This hypothetical function takes a string and a list of strings and returns the index where the given string was found in the list or -1 if not found.
But what if we instead have a list of integers? We wouldn't be able to use this function. However, we can make this function generic by using type variables or stand-ins instead of specific types.
indexOf : a -> List a -> Int
By replacing String
with a
, the signature now says that indexOf
takes a value of any type a
and a list of that same type a
and returns an integer. As long as the types match the compiler will be happy. You can call indexOf
with a String
and a list of String
, or an Int
and a list of Int
, and it will work.
This way functions can be made more generic. You can have several type variables as well:
switch : ( a, b ) -> ( b, a )
switch ( x, y ) =
( y, x )
This function takes a tuple of types a
, b
and returns a tuple of types b
, a
. All these are valid calls:
switch (1, 2)
switch ("A", 2)
switch (1, ["B"])
Note that any lowercase identifier can be used for type variables, a
and b
are just a common convention. For example the following signature is perfectly valid:
indexOf : thing -> List thing -> Int
Functions as arguments
Consider a signature like:
map : (Int -> String) -> List Int -> List String
This function:
- takes a function: the
(Int -> String)
part - a list of integers
- and returns a list of strings
The interesting part is the (Int -> String)
fragment. This says that a function must be given conforming to the (Int -> String)
signature.
For example, toString
from core is such function. So you could call this map
function like:
map toString [1, 2, 3]
But Int
and String
are too specific. So most of the time you will see signatures using stand-ins instead:
map : (a -> b) -> List a -> List b
This function maps a list of a
to a list of b
. We don't really care what a
and b
represent as long as the given function in the first argument uses the same types.
For example, given functions with these signatures:
convertStringToInt : String -> Int
convertIntToString : Int -> String
convertBoolToInt : Bool -> Int
We can call the generic map like:
map convertStringToInt ["Hello", "1"]
map convertIntToString [1, 2]
map convertBoolToInt [True, False]