I am learning haskell currently and I am having a really hard time wrapping my head around how to explain <$>
and <*>
's behavior.
For some context this all came from searching how to use an or
operation when using takeWhile
and the answer I found was this
takeWhile ((||) <$> isDigit <*> (=='.'))
In most of the documentation I have seen, <*>
is used with a container type.
show <*> Maybe 10
By looking at
(<$>) :: Functor f => (a -> b) -> f a -> f b
It tells me that <*> keeps the outer container if its contents and applies the right to the inside, then wraps it back into the container
a b f a f b
([Int] -> String) -> [Just]([Int]) -> [Just]([String])
This makes sense to me, in my mind the f a
is essentially happening inside the container, but when I try the same logic, I can make sense to me but I cant correlate the logic
f = (+) <$> (read)
so for f
it becomes
a b f a f b
([Int] -> [Int -> Int]) -> ([String] -> [Int]) -> ([String] -> [Int -> Int])
So f
being the container really confuses me when I try and work out what this code is going to do. I understand when I write it out like this, I can work it out and see its basically equivalent to the .
(.) :: (b -> c) -> (a -> b) -> a -> c
b c a b a c
([Int] -> [Int -> Int]) -> ([String] -> [Int]) -> ([String] -> [Int -> Int])
so it can be written as
f = (+) . read
Why not just write it as just that? Why wasn't the original snippet just written as
takeWhile ((||) . isDigit <*> (=='.'))
or does <$>
imply something in this context that .
des not?
Now looking at <*>
, it seems like it is basicly exactly the same as the <$> except it takes two containers, uses the inner of both, then puts it pack in the container
(<*>) :: Applicative f => f (a -> b) -> f a -> f b
so
Just show <*> Just 10
f a b f a f b
[Just]([Int->Int]->[Int]) -> [Just]([Int->Int]) -> [Just]([Int])
However with functions, it becomes murky how things are being passed around to each other. Looking at the original snippit and breaking it up
f1 :: Char -> Bool -> Bool
f1 = (||) . isDigit
f2 :: Char -> Bool
f2 = f1 <*> (== '.')
<*>
behavior in f2
is
f a b f a f b
([Char] -> [Bool] -> [Bool]) -> ([Char] -> [Bool]) -> ([Char] -> [Bool])
So using previous logic, I see it as Char ->
is the container, but its not very useful for me when working out what's happening.
It looks to me as if <*>
is passing the function parameter into right side, then passing the same function parameter, and the return value into the left?
So to me, it looks equivalent to
f2 :: Char -> Bool
f2 x = f1 x (x=='_')
Its a bit of mental gymnastics for me to work out where the data is flowing when I see <*>
and <$>
. I guess im just looking for how an experienced haskell-er would read these operations in their head.