import Control.Applicative
import Control.Arrow
filter ((&&) <$> (>2) <*> (<7)) [1..10]
filter ((>2) &&& (<7) >>> uncurry (&&)) [1..10]
Both get the same result! However, it is VERY difficult for me to understand. Could someone here explain it in detail?
Ask specific questions and provide details of the problem If you are struggling with a problem, make sure to include the following: a descriptive title related to the problem you are facing. a brief description of the problem you are facing. a list of error messages you are seeing.
Let's start with the second, which is simpler. We have two mysterious operators here, with the following types:
(&&&) :: Arrow a => a b c -> a b c' -> a b (c, c')
(>>>) :: Category cat => cat a b -> cat b c -> cat a c
The Arrow
and Category
type classes are mostly about things that behave like functions, which of course includes functions themselves, and both instances here are just plain (->)
. So, rewriting the types to use that:
(&&&) :: (b -> c) -> (b -> c') -> (b -> (c, c'))
(>>>) :: (a -> b) -> (b -> c) -> (a -> c)
The second has a very similar type to (.)
, the familiar function composition operator; in fact, they're the same, just with arguments swapped. The first is more unfamiliar, but the types again tell you all you need to know--it takes two functions, both taking an argument of a common type, and produces a single function that gives the results from both combined into a tuple.
So, the expression (>2) &&& (<7)
takes a single number and produces a pair of Bool
values based on the comparisons. The result of this is then fed into uncurry (&&)
, which just takes a pair of Bool
s and ANDs them together. The resulting predicate is used to filter the list in the usual manner.
The first one is more cryptic. We have two mysterious operators, again, with the following types:
(<$>) :: Functor f => (a -> b) -> f a -> f b
(<*>) :: Applicative f => f (a -> b) -> f a -> f b
Observe that the second argument of (<$>)
in this case is (>2)
, which has type (Ord a, Num a) => a -> Bool
, while the type of (<$>)
's argument has type f a
. How are these compatible?
The answer is that, just as we could substitute (->)
for a
and cat
in the earlier type signatures, we can think of a -> Bool
as (->) a Bool
, and substitute ((->) a)
for the f
. So, rewriting the types, using ((->) t)
instead to avoid clashing with the other type variable a
:
(<$>) :: (a -> b) -> ((->) t) a -> ((->) t) b
(<*>) :: ((->) t) (a -> b) -> ((->) t) a -> ((->) t) b
Now, putting things back in normal infix form:
(<$>) :: (a -> b) -> (t -> a) -> (t -> b)
(<*>) :: (t -> (a -> b)) -> (t -> a) -> (t -> b)
The first turns out to be function composition, as you can observe from the types. The second is more complicated, but once more the types tell you what you need--it takes two functions with an argument of a common type, one producing a function, the other producing an argument to pass to the function. In other words, something like \f g x -> f x (g x)
. (This function also happens to be known as the S combinator in combinatory logic, a subject explored extensively by the logician Haskell Curry, whose name no doubt seems strangely familiar!)
The combination of (<$>)
and (<*>)
sort of "extends" what (<$>)
alone does, which in this case means taking a function with two arguments, two functions with a common argument type, applying a single value to the latter two, then applying the first function to the two results. So ((&&) <$> (>2) <*> (<7)) x
simplifies to (&&) ((>2) x) ((<7) x)
, or using normal infix style, x > 2 && x < 7
. As before, the compound expression is used to filter the list in the usual manner.
Also, note that while both functions are obfuscated to some degree, once you get used to the operators used, they're actually quite readable. The first abstracts over a compound expression doing multiple things to a single argument, while the second is a generalized form of the standard "pipeline" style of stringing things together with function composition.
Personally I actually find the first one perfectly readable. But I don't expect most people to agree!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With