Reputation: 14520
i want to do operations on tuples for each position individually. for example
pair_of_sums (a1,b1) (a2,b2) = (a1+a2, b1+b2)
or more general
sum_and_multiplication (a1,b1) (a2,b2) = (a1+a2, b1*b2)
is there any way to make it shorter? like:
pair_of_sums = xxx (+)
sum_and_multiplication = yyy ((+),(*))
Upvotes: 0
Views: 111
Reputation: 10783
One possibility would be to use the bifunctors
package which has some useful methods and functions for working with different types including pairs.
pair_of_sums = (<<.>>) . bimap (+) (+)
For pairs, bimap
takes two functions and maps the first one over the first element and the second one over the second element (so, for this particular instance, we can view it as having the type bimap :: (a -> a') -> (b -> b') -> (a, b) -> (a', b')
). We can see this in its definition
bimap f g ~(a, b) = (f a, g b)
(the ~
essentially just tells Haskell to be a bit more lazy here. It's not really relevant for our purposes.)
In this case, (<<.>>)
takes a pair of functions and a pair and it maps each function in the first pair over the corresponding value of second pair. For this use, we can look at it as having he type (<<.>>) :: (a -> a', b -> b') -> (a, b) -> (a', b')
).
We can again look at the source code for this function to get an idea of how this works for the (,)
instance:
(f, g) <<.>> (a, b) = (f a, g b)
To see how these get put together, we can see that using bimap
gives us this (specializing to the (,)
instance for Bifunctor
, since that is what we are using)
bimap (+) (+) :: Num a => (a, a) -> (a -> a, a -> a)
This gives us a pair of functions that will add the given value to the original value in each position of the pair. For example
λ> fst (bimap (+) (+) (3,5)) 10
13
λ> snd (bimap (+) (+) (3,5)) 100
105
Our composition with (<<.>>)
allows us to turn this pair of functions into a function that takes a pair and gives a pair (which fits with the type signature for it I gave above).
Putting these together, we can have a more detailed look at the reduction performed in a call pair_of_sums (3, 5) (10, 100)
:
pair_of_sums (3, 5) (10, 100)
((<<.>>) . bimap (+) (+)) (3, 5) (10, 100) -- Definition of pair_of_sums
(\x -> (<<.>>) (bimap (+) (+) x)) (3, 5) (10, 100) -- Definition of (.)
(<<.>>) (bimap (+) (+) (3, 5)) --
(10, 100) -- Function application
bimap (+) (+) (3, 5) <<.>> (10, 100) -- Switch from prefix to infix
((+) 3, (+) 5) <<.>> (10, 100) -- After applying bimap for the (,) instance
((+) 3 10, (+) 5 100) -- Definition of (<<.>>) for the (,) instance
(13, 105) -- Apply (+)
This is very similar to how the Applicative
type class works, but it works with Bifunctor
instead of Functor
(actually, this is why the type class (<<.>>)
belongs to is called Biapply
). bimap
is analogous to fmap
and (<<.>>)
is analogous to (<*>)
.
One cool thing about using this technique to define these functions is that they would work for all of the type constructors that are instances of Biapply
, not just (,)
.
Upvotes: 4
Reputation: 12715
import Control.Arrow
f = ((uncurry (***) .) .) . (***)
main = do
print $ f (+) (+) (1, 2) (3, 4) -- (4, 6)
print $ f (+) (*) (1, 2) (3, 4) -- (4, 8)
Here is how it works:
f = ((uncurry (***) .) .) . (***)
f op1 op2 = uncurry (***) . (op1 *** op2)
f op1 op2 p1 p2 = uncurry (***) ((op1 *** op2) p1) p2
f op1 op2 (x1, y1) p2 = uncurry (***) ((op1 *** op2) (x1, y1)) p2
f op1 op2 (x1, y1) p2 = uncurry (***) (op1 x1, op2 y1) p2
f op1 op2 (x1, y1) (x2, y2) = (op1 x1 *** op2 y1) (x2, y2)
f op1 op2 (x1, y1) (x2, y2) = (op1 x1 x2, op2 y1 y2)
Upvotes: 3