문제

Let's say I have a JavaScript function

function f(x) {
  return a(b(x), c(x));
}

How would I convert that into a point free function? through composing functions? Also are there resources for more info on this?

도움이 되었습니까?

해결책

In general, there's no easy rule to follow when you turn functions into point free style. Either you are going to have to guess, or you can just automate it. In the Haskell IRC channel, we have the lambdabot which is great at turning Haskell functions into point-free style. I usually just consult that, and then work my way backwards if I need to know how it works.

Your particular example can be solved using a couple of helpful functions. I'll show you below how it works, but be aware that it might require a lot of playing around to understand. It also helps if you know really, really basic lambda calculus, because the JavaScript syntax tends to get in the way sometimes.

Anyway, here goes:


Basically, to do this properly, you need three functions: fmap(f, g), ap(f, g) and curry(f). When you have those, f(x) is easily defined as (and this looks much neater in e.g. Haskell)

f = ap(fmap(curry(a), b), c);

The interesting bit lies in defining those three functions.

curry

Normally when you define functions of multiple arguments in JavaScript, you define them like

function f(x, y) {
    // body
}

and you call them by doing something like f(3, 4). This is what is called an "uncurried function" in functional programming. You could also imagine defining functions like

function f(x) {
    return function(y) {
        //body
    }
}

These functions are called "curried functions." (By the way, they are named after a mathematician whose name was Curry, if you wonder about the strange name.) Curried functions are instead called by doing

f(3)(4)

but other than that, the two functions behave very similarly. One difference is that it is easier to work with a point-free style when the functions are curried. Our curry function simply takes an uncurried function like the first one and turns it into a curried function like the second one. curry can be defined as

function curry(f) {
    return function(a) {
        return function(b) {
            return f(a, b);
        }
    }
}

Now, you can use this. Instead of doing pow(3, 4) to get 81, you can do

cpow = curry(pow);
cpow(3)(4);

cpow is the curried version of pow. It doesn't take both arguments at the same time -- it takes them separately. In your specific case, this allows us to go from

function f(x) {
    return a(b(x), c(x));
}

to

function f(x) {
    return curry(a)(b(x))(c(x));
}

This is progress! (Although I admit it looks very weird in JavaScript...) Now, on to less spicy pastures.

fmap

The second piece of the puzzle is fmap(f, g), which takes two functions as arguments and composes them. What I'm saying is,

fmap(f, g)(x) == f(g(x))

This is easy to define, we just let

function fmap(f, g) {
    return function(x) {
        return f(g(x));
    }
}

This is useful when you want to do two things in sequence. Say you want to do the useless operation log(exp(x)). You could do this the traditional way:

function logexp(x) {
    return log(exp(x));
}

You could instead just do

logexp = fmap(log, exp);

This is commonly called composing two functions. To connect this to your example, last we left it off, we had refactored it into

function f(x) {
    return curry(a)(b(x))(c(x));
}

We now notice some visual similarity between this and the function body of fmap. Let's rewrite this with fmap and it becomes

function f(x) {
    return fmap(curry(a), b)(x)(c(x));
}

(to see how I got there, imagine that f = curry(a) and g = b. The last bit with c(x) isn't changed.)

ap

Our last puzzle piece is ap(f, g), which takes two functions and an argument, and does a weird thing. I won't even try to explain it, so I'll just show you what it does:

ap(f, g)(x) == f(x)(g(x))

Remember that f is really just a function of two arguments, only we write it a little differently to be able to do magic. ap is defined in JavaScript as

function ap(f, g) {
    return function(x) {
        return f(x)(g(x));
    }
}

So, to put this in a more practical context: Say you want to raise a number to the square root of itself. You could do

function powsqrt(x) {
    return pow(x, sqrt(x));
}

or, with your newfound knowledge of ap and remembering cpow from the first part about currying, you could also do

powsqrt = ap(cpow, sqrt);

This works because cpow is the curried version of pow. You can verify for yourself that this becomes the right thing when the definition of ap is expanded.

Now, to tie all this together with your example, we need to turn

function f(x) {
    return fmap(curry(a), b)(x)(c(x));
}

Into the final, completely point-free version. If we look at the definition of ap, we see we can do something here to turn this into the point-free version!

function f(x) {
    return ap(fmap(curry(a), b), c)(x);
}

Basically, the easiest way to understand this is to now "unfold" the call to ap. Replace the call to ap with the function body! What we get then, by merely substituting, is

function f(x) {
    return function(y) {
        return fmap(curry(a), b)(y)(c(y));
    }(x);
}

I've renamed one x to y to avoid name collisions. This is still a bit weird, but we can make it a little shorter. After all, it is the same thing as

function f(x) {
    return fmap(curry(a), b)(x)(c(x));
}

which was what we started with! Our call to ap was correct. If you want to, you can further unfold this to see that after everything is said and done, we actually end up with the very thing we started with. I leave that as an exercise.

Wrapping Up

Anyway, the last refactoring of your code made it into

function f(x) {
    return ap(fmap(curry(a), b), c)(x);
}

which of course is the same thing as

f = ap(fmap(curry(a), b), c);

And that's it!

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top