I'm trying to wrap my head around how Haskell's Control.Arrow
's &&&
works, but fear I'm on the verge of losing my way.
Specifically, I'm confused (as a beginner) by how to makes sense of its behavior from the type signature
(&&&) :: a b c -> a b c' -> a b (c, c')
in
import Control.Arrow
(negate &&& (+5)) <$> [1,2,3]
or even just
(negate &&& (+5)) 5
for example, the first argument is "missing" b
and c
, while the second is missing just c'
, and the result looks to me like (c, c')
, not a b (c, c')
.
Can someone walk me through how &&&
works, in the context of its type?
I always think of &&&
as a split and apply operation. You've got an Arrow value, and you're going to apply two functions (sorry, arrows, but it works with functions and makes explanations easier) to it, and keep both results, thus splitting the stream.
Dead simple example:
λ> (succ &&& pred) 42
(43,41)
Walking the type there, we've got
succ &&& pred :: Arrow a, Enum b => a b (b,b)
A more complex example where it's not all b:
show &&& (== 42) :: Arrow a, Show b, Eq b, Num b => a b (String,Bool)
So in plain english: &&&
takes two functions, and combines them into a single function that takes it input, applies both functions to it, and returns the result pair.
But it's defined on arrows, not functions. Yet it works exactly the same: it takes two arrows, and combines them into a single arrow that takes its input, applies both arrows to it, and returns the result pair.
arrowOne :: Arrow a => a b c
arrowTwo :: Arrow a => a b c'
arrowOne &&& arrowTwo :: Arrow a => a b (c,c')
Addendum: part of what seems to confuse you is whether or not the a
type still appears in the type signatures. The rule of thumb here is that it works the same as for when you see the ->
in function types: it shows as long as it's not applied.
I recall reading some Arrow literature that wrote arrows as b ~> c
(note the tilde not dash) instead of a b c
, to make the parallel with functions more apparent.
The signature says,
(&&&) :: Arrow a => a b c -> a b c' -> a b (c, c')
and (->)
is an instance of Arrow
type class. So, rewriting the signature specialized for (->)
type:
(&&&) :: ((->) b c) -> ((->) b c') -> ((->) b (c, c'))
in, infix form it will look like:
(b -> c) -> (b -> c') -> (b -> (c, c'))
which simply means
(&&&) :: (b -> c) -- given a function from type `b` to type `c`
-> (b -> c') -- and another function from type `b` to type `c'`
-> (b -> (c, c')) -- returns a function which combines the result of
-- first and second function into a tuple
a simple replication would be:
(&:&) :: ((->) b c) -> ((->) b c') -> ((->) b (c, c'))
f &:& g = \x -> (f x, g x)
which would work the same way:
\> (negate &:& (+5)) <$> [1, 2, 3]
[(-1,6),(-2,7),(-3,8)]
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