I want something like J's fork feature, I guess. Is there any way to do this?
-
1By asking the `pointfree` program: `pointfree "m h f g x = h (f x) (g x)"` -> `m = liftM2`. @kqr's answer is equivalent and might be better since the Applicative instance is often more efficient than the Monad instance. – bheklilr Nov 22 '13 at 20:11
-
@bheklilr Isn't the only reason `pointfree` suggests `liftM*` that it was written before `liftA*` existed? As soon as applicative is a superclass of monad, we can throw `liftM2` out of the window, can't we? – kqr Nov 22 '13 at 20:17
-
@kqr I would imagine so, or it could just be that `pointfree` looks in `Control.Monad` before `Control.Applicative`. Would there potentially be Monads for which `liftA*` has a different implementation than `liftM*`? My guess is that the behavior would be identical, but since Applicatives can sometimes add parallelism where Monads can't I wouldn't chuck `liftM*` out of the window just yet. – bheklilr Nov 22 '13 at 20:27
-
@bheklilr It depends on whether or not it's "allowable" for them to differ. `Monoid e => Either e` admits a neat `Applicative` which cannot form a `Monad`, but once the Applicative-Monad Proposal hits it's going to be even weirder for an the instances to differ... – J. Abrahamson Nov 22 '13 at 20:51
2 Answers
This is, using so-called applicative style,
h <$> f <*> g
using <$>
and <*>
from Control.Applicative
.
An alternative is to lift h
into the (->) r
applicative functor with
liftA2 h f g
The intuition behind that is that if
h :: a -> b -> c
-- then --
liftA2 h :: (r -> a) -> (r -> b) -> r -> c
so the lifted version takes two functions r -> something
instead of the actual something
s, and then feeds an r
to get the something
s out of the functions.
The liftA*
and the corresponding combination of <$>
and <*>
are equivalent.

- 14,791
- 3
- 41
- 72
While @kqr has the more practical solution based on the Applicative
instance for ((->) a)
, we can also talk about it in the "pipey" method
+----- f ------+
/ \
<---- h +------< x
\ /
+----- g ------+
which provides a very compositional kind of pointfree program. We'll create this program with tools from Control.Arrow
.
First we get the rightmost part of our diagram using a common missing function in Haskell called diag
or dup
--+
\
+----- x dup :: x -> (x, x)
/ dup x = (x, x)
--+
then the middle is created using the (***)
combinator from Control.Arrow
----- f ----- (***) :: (a -> b) -> (c -> d) -> (a, c) -> (b, d)
f :: (a -> b)
g :: (c -> d)
----- g ----- f *** g :: (a, c) -> (b, d)
then the left side is exactly what uncurry
does for us
+-- uncurry :: (a -> b -> c) -> (a, b) -> c
/ h :: (a -> b -> c)
h uncurry h :: (a, b) -> c
\
+--
Then wiring them all together we can erase the x
points with a very compositional style.
m :: (a -> b -> c) -> (x -> a) -> (x -> b) -> x -> c
m h f g = uncurry h . (f *** g) . dup
+------ f ----+
/ \
<----- h +-----------< x (eta reduced)
\ /
+------ g ----+

- 72,246
- 9
- 135
- 180
-
This was a very neat way of thinking about the problem. Out of curiosity: how does it scale for the equivalent of `liftA3 f g1 g2 g3`? – kqr Nov 22 '13 at 20:42
-
1Not terribly well. You have to do more more work packing and unpacking nested pairs. I'll introduce the standard `Control.Arrow` combinator `f &&& g = (f *** g) . dup` and then we have `liftA3 f g1 g2 g3 = uncurry ($) . first (uncurry f) . first (g1 &&& g2) . second g3 . dup`. Blech. – J. Abrahamson Nov 22 '13 at 20:47
-
For cross referencing, I often hear this kind of programming called programming in the "pair calculus". – J. Abrahamson Nov 22 '13 at 20:48
-
@J.Abrahamson Out of curiosity, are there any Arrow like libraries that can gracefully handle the `liftA3` case? – bheklilr Nov 22 '13 at 21:55
-
I'm not sure. I think Conal Eliott is currently working on writing a lambda calculus compiler which produces arrow-like code. I also see generalizations in papers sometimes where families of functions and irregular, but suggestive syntax works fine. Neither of which is practical, of course. – J. Abrahamson Nov 22 '13 at 22:11
-
Looks like concatenative programming. In Factor this is spelled `x g f bi h` where `bi` is implemented as `[ keep ] dip call`. It moves `f` out of the way for a moment, applies `g` keeping `x` around, then applies `f` and combines the results with `h`. (In Kitten it’s called `bothTo`, which I think is a better name.) – Jon Purdy Nov 23 '13 at 00:04
-
I think it very much is similar! I know you've been looking at typed concatenative programming... I think I owe you an email. – J. Abrahamson Nov 23 '13 at 01:01
-
1Wow, I flubbed the `liftM3` version a bit. It's not quite as bad as I made it out to be: `liftM3 f g1 g2 g3 = uncurry ($) . (uncurry f . (g1 &&& g2)) &&& g3`. We can even use `apply :: uncurry ($)` in the `Arrow (->)` instance to write `apply . ((uncurry f . (g1 &&& g2)) &&& g3)` or even `apply . (liftA2 f g1 g2 &&& g3)` which gives a nice recursion relationship letting us generate `[liftAn | n <- [1..]]`. – J. Abrahamson Nov 24 '13 at 22:14
-
@J.Abrahamson That makes it sound a little like `(<*>)` can be defined in terms of `apply .: (&&&)`, which is cool. – kqr Nov 26 '13 at 11:29