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]