4

Is there a way to express the following in point-free form:

g(f(x)(y))

It is not a common 'combinator' as far as I can see, though there are perhaps different ways to express it? I am working in Javascript with Ramda.

jramm
  • 6,415
  • 4
  • 34
  • 73
  • 2
    This is the somewhat well-known [*Blackbird* combinator](https://www.angelfire.com/tx4/cus/combinator/birds.html), `λabcd . a(bcd)`. The answer from Ori Drori gives a simple version and the comments on it from customcommander gives a nice alternative. – Scott Sauyet Sep 08 '22 at 13:17
  • 1
    Trivia: the bluebird in Haskell is `.`, which you have to enclose in parenthesis if you want to pass to other functions as an argument, `(.)`; the blackbird in Haskell is therefore `(.).(.)`, which sometimes called the _tits operator_, for obvious reasons. – Enlico Sep 11 '22 at 17:43

2 Answers2

3

I can't think of a readable way to create this combinator in a point-free form. However, since the introduction of arrow functions to JS it's pretty easy to create readable combinators. You can also curry them using Ramda's R.curry, instead of manually curry them (f => g => x => y => g(f(x)(y))):

const { curry, on, identity, multiply, add } = R

const combinator = curry((f, g, x, y) => g(f(x)(y)))

const square = n => n * n;

const squareAddition = combinator(add, square)

const result = squareAddition(1, 2)

console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.28.0/ramda.min.js" integrity="sha512-t0vPcE8ynwIFovsylwUuLPIbdhDj6fav2prN9fEu/VYBupsmrmk9x43Hvnt+Mgn2h5YPSJOk7PMo9zIeGedD1A==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
Ori Drori
  • 183,571
  • 29
  • 224
  • 209
  • If you're using Ramda it feels like just `pipe(f, g)` would do? – customcommander Sep 08 '22 at 08:54
  • 1
    `pipe(f, g)` is equivalent of the result of calling `combinator(f, g)`, and not a combinator by itself, and it's not curried. – Ori Drori Sep 08 '22 at 08:58
  • Ah okay. `const C = curryN(2, compose(curry, pipe)); C(add)(negate)(40)(2);` ? – customcommander Sep 08 '22 at 09:09
  • Very nice, but readability suffers. Post this as an answer. It's exactly what the OP was looking for. – Ori Drori Sep 08 '22 at 09:13
  • Agreed. I prefer your version anyway ;) was just confused initially and keep forgetting about pipe not currying the resulting function – customcommander Sep 08 '22 at 09:27
  • Thanks. You actually got something pretty simple, and the `compose(curry, pipe)` is pretty slick. My ideas included converting the params to any array with R.unapply, and then picking the arguments one by one to combine them, which were super un-readable. – Ori Drori Sep 08 '22 at 09:30
  • This is great, both the answer and the alternative from @customcommander. But I would prefer a different example, as `add` and `negate` commute with one another so the example doesn't make it clear whether this is `add (negate (x)) (negate (y))` or `negate (add (x) (y))`. Perhaps `combinator (add, square)` would do? – Scott Sauyet Sep 08 '22 at 13:28
  • 1
    Good idea. Updated to square. – Ori Drori Sep 08 '22 at 13:53
  • 1
    Nice answer and discussion! – jramm Sep 09 '22 at 08:29
3

Here's a point-free comp2 -

// comp : ('b -> 'result) -> ('a -> 'b) -> ('a -> 'result)
const comp = f => g =>
  x => f(g(x))

// comp2 : ('c -> 'result) -> ('a -> 'b -> 'c) -> ('a -> 'b -> 'result)
const comp2 =
  comp(comp)(comp)

// square : number -> number
const square = x =>
  x * x

// add : number -> number -> number
const add = x => y =>
  x + y
 
// addThenSquare : number -> number -> number 
const addThenSquare =
  comp2(square)(add)
  
console.log(addThenSquare(3)(2))
// 25 : number

Sure it's point-free and it works, but maybe this comp2 is more suitable for you -

// comp2 : ('c -> 'd) -> ('a -> 'b -> 'c) -> ('a -> 'b -> 'd)
const comp2 = f => g =>
  a => b => f(g(a)(b))

Here are comp3, comp4, and comp5 if you think you need them -

// comp3 : ('d -> 'result) -> ('a -> 'b -> 'c -> 'd) -> ('a -> 'b -> 'c -> 'result)
const comp3 =
  comp(comp)(comp2)

// comp4 : ('e -> 'result) -> ('a -> 'b -> 'c -> 'd -> 'e) -> ('a -> 'b -> 'c -> 'd -> 'result)
const comp4 =
  comp(comp)(comp3)

// comp5 : ('f -> 'result) -> ('a -> 'b -> 'c -> 'd -> 'e -> 'f) -> ('a -> 'b -> 'c -> 'd -> 'e -> 'result)
const comp5 =
  comp(comp)(comp4)
Mulan
  • 129,518
  • 31
  • 228
  • 259
  • 1
    Considering that the OP asked how to express `g(f(x,y))` _in point-free form_, I think this answer is more correct, because at least it builds the _blackbird_ combinator (as noted in a comment to the question, this is the name of the combinator the OP describes) starting from a more fundamental combinator, the _bluebird_, that @Mulan calls `comp`, because it's the function composition bird. – Enlico Sep 11 '22 at 17:41
  • 2
    This reminds me of the good ol' `comp(comp) (comp)`. Boy was I excited when a learned about it. Isn't the symmetry just comforting? –  Sep 12 '22 at 12:34