Two common patterns that I seem to run all the time while working on my 4x space game are functor and applicative. This episode explains them briefly.
Functor
Functor is a way to apply function over a structure we don’t want to alter. Type of the structure stays same, but values inside of it can change. One of the most common one is list, but there are many others.
Functor type class is defined below. There’s one function fmap that takes two parameters: a function from a to b and structure f a. Result will be structure f b.
class Functor f where
fmap :: (a -> b) -> f a -> f b
This is fairly abstract, so couple example might help. First we define a little helper function that raises it’s argument to 2nd power (in the episode I talk about doubling the value, my mistake there).
-- | this really raises x to 2nd power and doesn't double it
double x = x * x
Given a list of Int we can raise them to power of two by using fmap:
> fmap double [1, 2, 3, 4, 5]
[1, 4, 9, 16, 25]
Since function being applied to structure is type of (a -> b), we can change type of the value inside of the structure. Below is example of turning list of Int to list of Text.
> fmap show [1, 2, 3, 4, 5]
["1", "2", "3", "4", "5"]
This pattern isn’t limited to list and there are many others. You can even define your own ones, if you’re so inclined. The pattern stays the same. One function, fmap, that takes function of type (a -> b) and structure f a and turns it into structure of f b. Details how this is actually done depend on the specific functor.
Other common functor is Maybe that is often used in cases where data might or might not be present. Maybe a has two possible values Just a indicating that value a is present and Nothing indicating that there is no value present. When fmap is used in this context, Just a will turn to Just b and Nothing will stay as Nothing.
> fmap (x -> x * x) $ Just 2
Just 4
> fmap (x -> x * x) Nothing
Nothing
Either a b is sometimes used for value that can be correct or an error. It has two value constructors Right b indicates that value is correct, Left a indicates an error case. a and b don’t have to be of same type (and usually aren’t). For example, if we have Either Text Int, then we have value where error case is Text and correct value is Int.
> fmap double $ Right 5
Right 25
> fmap double $ Left "distance calculation failed because of flux-capacitor malfunction"
Left "distance calculation failed because of flux-capacitor malfunction"
Functors can be placed inside of functors. The only difference is that you have to reach through multiple layers. Simplest way of doing that is to compose multiple fmap functions together like in the example below. Pay attention to in which order nested functors are defined as Maybe [Int] and [Maybe Int] are different things. Former is for case where list of Int might or might not be present. Latter is for case where there’s always list, but single element inside of the list might or might not be present.
> (fmap . fmap) double (Just [1, 2, 3, 4])
Just [1, 4, 9, 16]
> (fmap . fmap) double Nothing :: Maybe Int
Nothing
> (fmap . fm