I've been trying to make a combinator with this type signature:
(a -> b -> c) -> (c -> d -> e) -> a -> b -> d -> e
I've been through Data.Aviary.Birds and all the tacit programming help sites that I can find, but to no avail. Also, if there's a general algorithm to make these, it would be greatly appreciated but not necessary.
Our definition will start like this:
foo :: (a -> b -> c) -> (c -> d -> e) -> a -> b -> d -> e
foo abc cde a b d = e
Now let's fill in the missing bits.
We need an e
; the only way to get that is by applying the second function to a c
and d
.
e = cde c d
We are already given a d
, but we need a c
. How do we get a c
? By applying the first function to an a
and a b
.
c = abc a b
We are given both of these, so we're done.
foo :: (a -> b -> c) -> (c -> d -> e) -> a -> b -> d -> e
foo abc cde a b d = e
where
e = cde c d
c = abc a b
We might stop here. This is a perfectly good definition.
But if we want to try to make it more terse, let's start by substituting the definition of e
foo abc cde a b d = cde c d
where
c = abc a b
and then of c
foo abc cde a b d = cde (abc a b) d
We immediately see that we can eta reduce to remove the d
.
foo abc cde a b = cde (abc a b)
The type is now slightly more general. d -> e
has collapsed into one type variable, since it can actually be anything.
foo :: (a -> b -> c) -> (c -> de) -> a -> b -> de
We can now see in the aviary that our combinator is actually the blackbird, flipped.
blackbird :: (c -> d) -> (a -> b -> c) -> a -> b -> d
foo :: (a -> b -> c) -> (c -> de) -> a -> b -> de
foo = flip blackbird
And indeed if we look at the source for the blackbird, it looks much like what we have written.
-- | B1 combinator - blackbird - specs 'oo'.
blackbird :: (c -> d) -> (a -> b -> c) -> a -> b -> d
blackbird f g x y = f (g x y)
Can we go more point-free? We might consider uncurrying abc
foo abc cde a b = cde (uncurry abc (a, b))
rewriting this nesting with function composition
foo abc cde a b = (cde . uncurry abc) (a, b)
and currying back again
foo abc cde a b = curry (cde . uncurry abc) a b
And now we can chop off two more parameters.
foo abc cde = curry (cde . uncurry abc)
We should definitely stop here. But what if we now flip the arguments
foo = flip $ \cde abc -> curry (cde . uncurry abc)
rewrite the right half to make it point-free
foo = flip $ \cde abc -> (curry . ((cde .) . uncurry)) abc
and eta reduce once more
foo = flip $ \cde -> curry . ((cde .) . uncurry)
and take one final ridiculous step
foo = flip $ (curry .) . (. uncurry) . (.)
We are now point-free!