How does Function.bind.bind(Function.call) uncurry?
Asked Answered
S

4

15

We have this line in my code base:

var uncurryThis = Function.bind.bind(Function.call);

That I'm trying to work through. Presumably, it uncurries. How do I work this out?

I guess it's a version of Function.bind whose own this is bound to Function.call. Doesn't help me enough. And I haven't found any uses, so I'm not even sure if you call it standalone or need to call it "as a method", only, you know, bind it first.

Swanhildas answered 5/5, 2014 at 22:29 Comment(8)
Where did you see this? Is part of a well-known library or just some magical line of code you found laying around with no comments or context?Primate
@Primate He took a job at Google.Swanhildas
@Swanhildas I happened to find this jsPerf. It might be useful.Primate
I think that the term "curry" or "uncurry" used in the context of JavaScript is inaccurate in general; in this case, I'm not sure what it's intended to mean.Capitular
+1 to @Capitular that the use of "curry" is quite wrong here. The Base2 library does call this function "unbind"Myrtie
Have a look at dev.to/melkornemesis/how-to-curry-this--uncurry-this-4l9f - I wrote an article demystifying uncurrying this with Function.bind.bind(Function.call).Planoconcave
@Planoconcave thanks -- you should probably attribute this question, iirc it's a license requirement.Swanhildas
Here's a clue, that would have been helpful for me: Function.call === f.call === Function.prototype.callLoveless
C
18

It passes the call function to the bind function, with the bind function itself being the value of this. Thus you get in return a wrapper around the bind function that arranges for this to be the call function when you call it. That, in turn, is a function that lets you create a wrapper around the call function bound to some argument you pass it.

In case you haven't been drinking coffee nonstop since you woke up this morning, step by step:

  • Function.bind.bind is a reference to the bind function. The reference is generated from a property of — confusion point 1 — the bind function itself. Remember, the bind function, when called with some function as the object, is used to create a wrapper around that function with this bound to the first argument passed in.
  • Thus that function call gives you a function back. That function works as if you called Function.call.bind(something).
  • If you pass some random function as an argument to that function, then, you get back a wrapper around the random function that, when called, will act like randomFunction.call(whatever).

So:

function random() {
  alert(this.foo);
}

var bb = Function.bind.bind(Function.call);

var randomcall = bb(random);

randomcall({ foo: "hello world" }); // alerts "hello world"

The ultimate point is this: you've got a function, and inside the function there's code that expects this to have some properties, and it uses this in one way or another. You'd really like to be able to use that function with some object here, some object there. You can obviously do that with

random.call(someObject);

But this magic "bind-bind-call" trick gives you a cheap way to create a variation on your function that lets you avoid the explicitly-coded invocation of .call(). It also allows you to hang onto your senior front-end developer position for a little bit longer.

edit — I'm going to spoil the punch line above because I just thought of a good reason to use the bind+call trick to obtain a function that arranges to make a call to some desired function that expects to operate via this on some "owner" object. Let's say you've got an array of strings, and you'd like to get a version of those strings in lower-case. You could write this:

var uc = ["Hello", "World"];
var lc = uc.map(function(s) { return s.toLowerCase(); });

But with the magic "bb" function we could also write:

var uc = ["Hello", "World"];    
var tlc = bb(String.prototype.toLowerCase);
var lc = uc.map(tlc);

Not much of an improvement written that way, but if one were to make a set of bb()-ified wrappers of all the handy String prototype methods, it might make more sense. Of course, everything has a price, and it's probably the case that such wrappers will have some performance impact. (If practices like this were common then runtimes could probably be improved.)

Capitular answered 5/5, 2014 at 22:36 Comment(5)
I laughed my coffee through my nose for you. Is there some way to follow and get more programming humor from you?Acidforming
@Acidforming ha ha thanks for finding this; I was looking for it the other day but couldn't find it :) I'm usually not that funny!Capitular
Do you have a blog or something? Surely someone with so many rep score must have a blog!Acidforming
@Acidforming I do but I'm too lazy to write to it. There was something I was going to write about the other day though, so maybe I'll get the energy. I'm going to a conference next week so maybe I'll write some stuff then :) (Oh and the blog is linked from my profile.)Capitular
Have a great time at the conference! Meanwhile this might be on topic here: https://mcmap.net/q/352967/-using-function-prototype-bind-with-an-array-of-argumentsAcidforming
M
7

OK. You know what bind does? It's a method of Functions to fix their this argument, and returns a new function. It could be simplified to:

function bind(context) {
    var fn = this;
    return function() {
        return fn.apply(context, arguments);
    };
}

I will abbreviate function calls with contexts in a more functional style with lots of partial application: bindfn(context) -> fncontext. With arguments: (bindfn(context))(…) is equal to fncontext(…).

Similarly, call does take a this value but instead of returning a function, it applies it right now: callfn(context, …) -> fncontext(…).

So now let's get at your code: bind.call(bind, call). Here, you're applying bind on bind with call as the this value: bindbind(call). Let's expand this (with above rule) to bindcall. What if we now supplied some arguments to it?

bindbind(call) (fn)(context, …)

bindcall (fn)(context, …)

call fn(context, …)

fncontext(…)

Step by step, we could do

uncurryThis = bindbind(call) // bindcall

func = uncurryThis(method) // callmethod

result = func(context, …) // methodcontext(…)

A practical use case for this are any "class" methods that are supposed to be converted to a static function, taking the object (on which the method would be called upon) as the first argument:

var uncurryThis = Function.bind.bind(Function.call);
var uc = uncurryThis(String.prototype.toUpperCase);
uc("hello") // in contrast to "hello".toUpperCase()

This can be helpful if you cannot place a method call, but need a static function; e.g. as in

["hello", "world"].map(uc) // imagine the necessary function expression

Also, the method you want to invoke might not be a method of the object itself, as in

var slice = uncurryThis(Array.prototype.slice);
slice(arguments) // instead of `Array.prototype.slice.call(arguments)` everywhere

If it helps, here is also an explicit implementation, without any binds:

function uncurryThis(method) {
    return function(context/*, ...*/)
        return method.apply(context, Array.prototype.slice.call(arguments, 1));
    };
}
Myrtie answered 6/5, 2014 at 0:33 Comment(0)
B
1

I think this can be explained more clearly if you work backward.

Context:

Suppose we want to lowercase an array of strings. This can be done like so:

[‘A’, ‘B’].map(s => s.toLowerCase())

Let's say, for whatever reason, I want to make this call more generic. I don't like how s is bound to this and the fat arrow is tied to toLowerCase().

How about this?

[‘A’, ‘B’].map(String.prototype.toLowerCase)

Well, this doesn't work because map passes the element as the first argument but String.prototype.toLowerCase takes no arguments. It expects the input string to be passed as this.

So a question is can we create a wrapper function that makes this work?

[‘A’, ‘B’].map(wrapper(String.prototype.toLowerCase))

wrapper returns a function that turns the first argument passed into this for String.prototype.toLowerCase to use.

I claim that your uncurryThis === wrapper.


Proof:

So let's not try to understand unCurryThis all at once. Instead, let's use some formulas to transform unCurryThis into something more understandable.

Some formulas first:

instance.function(...args)
=== (instance.constructor.prototype).function.call(instance, ...args)
=== (Class.prototype).function.call(instance, ...args) [1]
=== (Class.prototype).function.bind(instance)(...args) [2]

For example,

Class === String
instance === 'STRING'
function === toLowerCase
args === []
---
'string'.toLowerCase()
=== ('STRING'.constructor.prototype).toLowerCase.call('STRING')
=== (String.prototype).toLowerCase.call('STRING')
=== (String.prototype).toLowerCase.bind('STRING')()

So let's just blindly apply these formulas without worrying about what the confusing uncurryThis looks like:

'string'
=== (wrapper)(String.prototype.toLowerCase)('STRING')
=== (uncurryThis)(String.prototype.toLowerCase)('STRING')
=== (Function.bind.bind(Function.call))(String.prototype.toLowerCase)('STRING')

// Function.bind is not really the generic form because it's not using the prototype
// Here Function is an instance of a Function and not the constructor.prototype
// It is similar to calling Array.bind or someFunction.bind
// a more correct version would be
// someFunction.constructor.prototype.bind === Function.prototype.bind, so
=== (Function.prototype.bind.bind(Function.prototype.call))(String.prototype.toLowerCase)('STRING')

// Apply formula 2
// instance.function(...args) === (Class.prototype).function.bind(instance)(...args) [2]
// Class === Function
// function === bind
// instance === Function.prototype.call
// ...args === String.prototype.toLowerCase
=== instance.function(...args)('STRING')
=== (Function.prototype.call).bind(String.prototype.toLowerCase)('STRING')

// Apply formula 2 again
// Class == Function
// function == call
// instance === String.prototype.toLowerCase
// ...args === 'STRING'
=== instance.function(...args)
=== (String.prototype.toLowerCase).call('STRING')

// Apply formula 1
instance.function(...args) === (Class.prototype).function.call(instance, ...args) [1]
// Class === String
// function === toLowerCase
// instance === 'STRING'
// args === []
=== instance.function(...args)
=== 'STRING'.toLowerCase(...[])
=== 'STRING'.toLowerCase()

// So we have
(wrapper)(String.prototype.toLowerCase)('STRING')
=== (uncurryThis)(String.prototype.toLowerCase)('STRING')
=== 'STRING'.toLowerCase()
=== 'string'

Reverse Proof:

So you might wonder "how did the guy even derive the uncurryThis function"?

You can reverse the proof to derive it. I am just copying the equations from above but reversed:

'STRING'.toLowerCase()
=== (String.prototype.toLowerCase).call('STRING') // apply formula [1]
=== (Function.prototype.call).bind(String.prototype.toLowerCase)('STRING') // apply formula [2]

// At this point, you might wonder why `uncurryThis !== (Function.prototype.call).bind)
// since it also takes (String.prototype.toLowerCase)('STRING')
// This is because passing in (Function.prototype.call).bind) as an argument
// is the same as passing in Function.prototype.bind
// `this` binding isn't done unless you call
// (Function.prototype.call).bind)(String.prototype.toLowerCase)
// at that exact moment.
// If you want to be able to pass unCurryThis as a function, you need to bind the
// Function.prototype.call to the Function.prototype.bind.

=== (Function.prototype.bind.bind(Function.prototype.call))(String.prototype.toLowerCase)('STRING') // apply formula 2
=== (Function.bind.bind(Function.call))(String.prototype.toLowerCase)('STRING') // un-generic-ize
=== (uncurryThis)(String.prototype.toLowerCase)('STRING')
=== (wrapper)(String.prototype.toLowerCase)('STRING')

=>

unCurryThis === wrapper === Function.bind.bind(Function.call)

Still pretty confusing to follow but try to write out what Class, function, instance, and args are each time I am applying formulas [1] and [2], and it should make sense.

Bede answered 26/1, 2019 at 5:21 Comment(0)
M
0

when we call bind on a function, it returns new function with this is replaced by context:

function random() {
  alert(this.foo);
}
var newRandom = random.bind({foo:"hello world"}) //return new function same as //`random` with `this` is replaced by object {foo:"hello world"}

the same we have:

Function.bind.bind(Function.call)
// return new Function.bind with its `this` is replaced by `Function.call`

It has following source(used simplified version of bind function given by @Bergi):

var bb = function bind(context){
  var fn = Function.call;
  return function() {
        return Function.call.apply(context, arguments); //also replace fn here for easier reading
    };
}

Notice that context here will be function, for example random, so wen call bb(random) we has newRandom function as:

newRandom = function(){
   return Function.call.apply(random, arguments); //also replace 
}
//`apply` function replace `this` of Function.call to `random`, and apply Function(now become `random`) with arguments in `arguments` array.
Mccue answered 1/6, 2015 at 2:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.