Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pointfree (or library) function for applying two functions to single input

I keep reusing lambda expressions such as

\x -> (f x, g x)

where I apply the same input to two functions and encapsulate the result in a pair. I can write a function capturing this

combine :: (a -> b) -> (a -> c) -> a -> (b,c)
combine f g x = (f x, g x)

Now the above lambda expression is just combine f g. I have two questions.

  1. I'm interested to know if there is a standard library function that does this that I just can't find.
  2. Out of curiosity, I'd like to rewrite this function in point-free style, but I'm having a lot of trouble with it.
like image 561
bshourd Avatar asked Jan 16 '13 03:01

bshourd


3 Answers

  1. Control.Arrow has the function (&&&) for this. It has a "more general" type, which unfortunately means that Hoogle doesn't find it (maybe this should be considered a bug in Hoogle?).

  2. You can usually figure this sort of thing automatically with pointfree, which lambdabot in #haskell has as a plugin.

For example:

<shachaf> @pl combine f g x = (f x, g x)
<lambdabot> combine = liftM2 (,)

Where liftM2 with the (r ->) instance of Monad has type (a -> b -> c) -> (r -> a) -> (r -> b) -> r -> c. Of course, there are many other ways of writing this point-free, depending on what primitives you allow.

like image 155
shachaf Avatar answered Nov 15 '22 01:11

shachaf


I'm interested to know if there is a standard library function that does this that I just can't find.

It's easy to miss because of the type class, but look at Control.Arrow. Plain Arrows can't be curried or applied, so the Arrow combinators are pointfree by necessity. If you specialize them to (->), you'll find the one you want is this:

(&&&) :: (Arrow a) => a b c -> a b c' -> a b (c, c')

There are other, similar functions, such as the equivalent operation for Either, which specialized to (->) looks like this:

(|||) :: (a -> c) -> (b -> c) -> Either a b -> c

Which is the same as either.

Out of curiosity, I'd like to rewrite this function in point-free style, but I'm having a lot of trouble with it.

Since you're duplicating an input, you need some way of doing that pointfree--the most common way is via the Applicative or Monad instance for (->), for example \f g -> (,) <$> f <*> g. This is essentially an implicit, inline Reader monad, and the argument being split up is the "environment" value. Using this approach, join f x becomes f x x, pure or return become const, fmap becomes (.), and (<*>) becomes the S combinator \f g x -> f x (g x).

like image 40
C. A. McCann Avatar answered Nov 15 '22 00:11

C. A. McCann


There are actually quite a few ways of doing this. The most common way is to use the (&&&) function from Control.Arrow:

f &&& g

However, often you have more functions or need to pass the result to another function, in which case it is much more convenient to use applicative style. Then

uncurry (+) . (f &&& g)

becomes

liftA2 (+) f g

As noted this can be used with more than one function:

liftA3 zip3 f g h
like image 6
ertes Avatar answered Nov 15 '22 00:11

ertes