Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a Ruby equivalent for JavaScript's Function.prototype.bind?

JavaScript happy times fun land

// make a method
var happy = function(a, b, c) {
  console.log(a, b, c);
};

// store method to variable
var b = happy;

// bind a context and some arguments
b.bind(happy, 1, 2, 3);

// call the method without additional arguments
b();

Output. Yay!

1 2 3

In Ruby

# make a method
def sad a, b, c
  puts a, b, c
end

# store method to variable
b = method(:sad)

# i need some way to bind args now
# (this line is an example of what i need)
b.bind(1, 2, 3)

# call the method without passing additional args
b.call

Desired output

1, 2, 3

For what it's worth, I know JavaScript can change the context of the binding with the first argument passed to .bind. In Ruby, I'd be 99% happy even if I couldn't change the context. I primarily need to simply bind parameters to the method.

Question

Is there a way to bind parameters to an instance of a Ruby Method such that when I call method.call without additional parameters, the bound parameters are still passed to the method?

Goal

This is a common JavaScript idiom and I think it would be useful in any language. The goal is to pass a method M to a receiver R where R does not need (or have) intrinsic knowledge of which (or how many) parameters to send to M when R executes the method.

A JavaScript demonstration of how this might be useful

/* this is our receiver "R" */
var idiot = function(fn) {
  console.log("yes, master;", fn());
};


/* here's a couple method "M" examples */
var calculateSomethingDifficult = function(a, b) {
  return "the sum is " + (a + b);
};

var applyJam = function() {
  return "adding jam to " + this.name;
};

var Item = function Item(name) {
  this.name = name;
};


/* here's how we might use it */
idiot(calculateSomethingDifficult.bind(null, 1, 1));
// => yes master; the sum is 2

idiot(applyJam.bind(new Item("toast")));
// => yes master; adding jam to toast
like image 890
Mulan Avatar asked Aug 28 '13 15:08

Mulan


People also ask

What is function prototype bind?

prototype. bind() The bind() method creates a new function that, when called, has its this keyword set to the provided value, with a given sequence of arguments preceding any provided when the new function is called.

What is .bind in JavaScript?

JavaScript Function bind() With the bind() method, an object can borrow a method from another object. The example below creates 2 objects (person and member).

How do you bind an argument in JavaScript?

The full syntax of bind : let bound = func. bind(context, [arg1], [arg2], ...); It allows to bind context as this and starting arguments of the function.

Why do we use BIND in JS?

We use the Bind() method to call a function with the this value, this keyword refers to the same object which is currently selected . In other words, bind() method allows us to easily set which object will be bound by the this keyword when a function or method is invoked.


2 Answers

Normally, rebinding methods isn't something you do in Ruby. Instead, you use blocks:

# This is our receiver "R"
def idiot(&block)
  puts("yes, master; #{block.call}")
end


# Here's a couple method "M" examples
def calculateSomethingDifficult(a, b)
  return "the sum is #{a + b}"
end

def applyJam(object)
  return "adding jam to " + object.name
end

class Item
  attr_reader :name
  def initialize(name)
    @name = name
  end
end


# Here's how we might use it
idiot do
  calculateSomethingDifficult(1, 1)
end
#=> yes master; the sum is 2

# You *can* change calling context too (see instance_exec), but I'd
# discourage it. It's probably better to just pass the object as a
# parameter.
idiot do
  applyJam(Item.new("toast"))
end
#=> yes master; adding jam to toast

If you really want to "bind" methods like you do in JavaScript it's definitely possible though:

class Method
  def bind *args
    Proc.new do |*more|
      self.call *(args + more)
    end
  end
end

That should make your example work almost as you originally described:

# make a method
def sad a, b, c
  puts a, b, c
end

# store method to variable
b = method(:sad)

# Get a "bound" version of the method
b = b.bind(1, 2, 3)

# call the method without passing additional args
b.call

If you need it exact, you can probably define Object#bindable_method to return some BindableMethod class that does what you want. For most cases though I think the above should work for you.

like image 189
Ajedi32 Avatar answered Oct 22 '22 12:10

Ajedi32


Proc#curry in Ruby is similar to bind in JavaScript.

def happy(a, b, c, d = 100)
  puts a, b, c, d
end

proc = method(:happy).to_proc.curry # proc is now a curried Proc

b = proc.call(1,2) # b is a curried Proc with 1 and 2 bound as the first arguments

b.call(3) # Call the proc, providing the 3rd argument

You can't exactly duplicate your example code because when the curried proc is called with the necessary arguments, it returns the result of the proc --- in other words, you cannot bind ALL of the arguments and then call the proc later --- you have to leave at least one argument unbound.

This isn't necessarily a better option than the code provided by Ajedi32, but I think it's worth mentioning because it's built-in to Ruby.

See the docs here: http://ruby-doc.org/core-2.2.0/Proc.html#method-i-curry

like image 28
Nathan Avatar answered Oct 22 '22 12:10

Nathan