I'm currently reading through some chapters regarding applicative and effectful programming.
The chapters begins with functors which I understand as instead of mapping a specific data structure to a specific datatype it allows you to map any data structure to a data type e.g.:
Inc :: [Int] -> [Int]
Inc [] = []
Inc (n:ns) = n + 1 : inc ns
or
sqr :: [Int] -> [Int]
sqr [] = []
sqr (n:ns) = n ^ 2 : sqr ns
Both of these functions above can be abstracted by using map
function which can be defined as:
map :: (a->b) -> [a] -> [b]
map f [] = []
map f (x:xs) = fx : map f xs
which in turn allows you to do inc = map (+1)
or sqr = map(^2)
.
However, the above map functions only allows you to map list to list, however if we want to do something similar but if we want it to allow different data structures like trees or maybe, that's where functors come in e.g. if you want to add any type of data structures
inc :: Functor f => f Int -> f Int
inc = fmap (+1)
Where I get confused now is in the book it says instead of limiting to a single argument in functors we can make it such that it accepts multiple arguments. E.g.:
fmap0 :: a -> f a
fmap1 :: (a -> b) -> f a -> f b
fmap2 :: (a -> b -> c) -> f a -> f b -> f c
And if we do fmap2 (+) (Just 1) (Just 2)
this will return Just 3
, which makes sense as explained above.
However the book now starts to talk about using currying to allow us to get multiple arguments:
pure :: a-> f a
(<*>) :: f (a -> b) -> f a -> f b
I don't see the difference between this and the functions above.
I kinda understand the book saying that pure
converts an element to be the data structure f
. However, <*>
being the generalised form of function application for which the argument function,
the argument value, and the result value are all contained in f
structures is the part I don't understand.
I've seen other posts asking what are applicative effects, but I still didn't quite understand it.