0

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.

Chris Martin
  • 30,334
  • 10
  • 78
  • 137
APerson
  • 8,140
  • 8
  • 35
  • 49
  • For "a general algorithm to make these", see [luqui's answer to *Point Free problems in Haskell*](https://stackoverflow.com/a/29596461/2751851). – duplode Feb 28 '18 at 14:07

2 Answers2

7

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!

Chris Martin
  • 30,334
  • 10
  • 78
  • 137
  • `flip ((.) . (.))` ? – n. m. could be an AI Feb 28 '18 at 07:06
  • `flip ((.) . (.) . flip)`. (I eventually went with `(f .) . g` for a modicum of readability.) Thanks for the extensive answer! – APerson Feb 28 '18 at 07:25
  • 1
    @APerson, from a practical standpoint, `(f .) . g` is pretty informative. You can write it `rmap f . g`, where `rmap` is from `Data.Profunctor` and `(.)` is from `Control.Category`. This generalizes the type somewhat more. You can even use `o` from `Data.Semigroupoid` instead of `(.)` for another little generality boost. – dfeuer Feb 28 '18 at 07:58
  • Er... I think I got that a little confused. I shouldn't write when sleepy. I'll check the details in the morning. – dfeuer Feb 28 '18 at 08:37
6

There's a really easy way: cheat. Let's start by finding out what function we want. For this, we go to Djinn. Type in

f :: (a -> b -> c) -> (c -> d -> e) -> a -> b -> d -> e

and it comes back with

f a b c d = b (a c d)

Nice. Now head over to pointfree.io. Paste in the definition from Djinn, and it says

f = flip ((.) . (.))

Done.

dfeuer
  • 48,079
  • 5
  • 63
  • 167