在Ruby中实现类似JavaScript的闭包

时间:2016-01-20 03:39:00

标签: javascript ruby

我发现在不使用以下模式的闭包的情况下编程很困难:

function outerFunction(input) {
  var closureVar = "I'm here with ";

  function innerFunction() {
    return closureVar + input;
  }

  return innerFunction();
}

outerFunction('outer input'); // I'm here with outer input

这对我有意义。 outerFunction定义了innerFunction在调用时引用的环境。

我想看看Ruby如何完成同样的事情。我理解Ruby使用块,procs和lambas,但我似乎无法用JavaScript实现相同的词法范围。

Ruby天真的尝试:

def outer_function
  list = [ 1, 2, 3 ]

  list.each { |item| print item }
end

# Fails because list has an undefined method on the second iteration of the loop

另一次尝试

def outer_function
  list = Proc.new { return [ 1, 2, 3 ] }

  list.call.each { |item| another_function item }
end

def another_function item
  puts item
end

# Same problem

那么,我怎样才能在Ruby中实现这种模式呢?

3 个答案:

答案 0 :(得分:3)

虽然Javascript有一种类型的lambda构造(匿名函数),但Ruby有几种。最常见的是块(传递给示例中的each中的方法),Procs(类似于绑定到执行上下文的块,即使在传递时也保留),以及可能最接近的Lambdas匿名函数。我不会在这里详细介绍Procs vs Lambdas。

以下是Ruby中这些构造的示例:

# Using a block + yield
def my_method(arg)
  puts arg + yield(3)
end

my_method(5) { |arg| arg * 2 }
# => 11

# Binding the block to a Proc then using #call
def print(&block)
  puts block.call
end

# Creating a Proc that can be passed around and then #called
def other_method(arg, other_args*)
  arg.call(other_args*)
end

var = 3
prc = Proc.new { puts var }
other_method(prc)

# Creating a Lambda using stabby syntax
num = 3
l = -> (arg) { arg + 2 + num }
other_method(l, 1)

在Ruby中,您可以将您的JS示例编写为:

def outer(input)
  closure_var = "I'm here with "

  inner = -> { closure_var + input }

  inner.call
end

outer('outer input')

在IRB:

jbodah@Joshs-MacBook-Pro-2 2.1.3p242 ~ (none) $ irb
irb(main):001:0>     def outer(input)
irb(main):002:1>       closure_var = "I'm here with "
irb(main):003:1>
irb(main):004:1*       inner = -> { closure_var + input }
irb(main):005:1>
irb(main):006:1*       inner.call
irb(main):007:1>     end
=> :outer
irb(main):008:0>
irb(main):009:0*     outer('outer input')
=> "I'm here with outer input"

答案 1 :(得分:3)



function outerFunction(input) {
  var closureVar = "I'm here with ";

  function innerFunction() {
    return closureVar + input;
  }

  return innerFunction();
}

console.log(outerFunction('outer input')); // I'm here with outer input




让我们首先重写您的ECMAScript以使用函数表达式:



const outerFunction = function (input) {
  const closureVar = "I'm here with ";

  const innerFunction = function () {
    return closureVar + input;
  }

  return innerFunction();
}

console.log(outerFunction('outer input')); // I'm here with outer input




现在让我们把它改写成更现代的风格:



const outerFunction = input => {
  const closureVar = "I'm here with ";

  const innerFunction = () => closureVar + input;

  return innerFunction();
};

console.log(outerFunction('outer input')); // I'm here with outer input




现在,将其转换为Ruby实际上相当简单:

outer_function = -> input {
  closure_var = "I'm here with "

  inner_function = -> { closure_var + input }

  inner_function.()
}

puts outer_function.('outer input') # I'm here with outer input

正如您所看到的,翻译实际上非常简单,语义也很相似。然而,它并不是非常惯用的Ruby。通常,对于封装状态,对象和方法优于闭包,但有时封闭是有用的。

更惯用的风格可能是这样的:

def outer_method(input)
  closure_var = "I'm here with "

  inner_function = -> { closure_var + input }

  inner_function.()
end

puts outer_method('outer input') # I'm here with outer input

但对于这样一个小玩具的例子,很难说并且很难证明Ruby会是什么样的。

如果您对此类内容感兴趣,可能需要查看as_matrix,其中我将展示如何使用各种语言(包括Clojure和其他各种Lisps,Smalltalk)中的闭包来实现链接列表。以及它的一些后代,ECMAScript和CoffeeScript,Ruby,Python,PHP,Perl等。请注意,代码在大多数语言中看起来非常相似,但请注意,代码在几乎所有语言中都是非常不恰当的,除了计划,也许是Clojure。就我所知,它甚至不是惯用的CommonLisp。

你在问题​​的后半部分发布的Ruby代码与你问题的前半部分中的代码或概念完全无关:没有闭包,没有词法作用域的问题,也没有必要使用它们。

事实上,您发布的第一个片段就是按原样运作:

def outer_function
  list = [ 1, 2, 3 ]

  list.each { |item| print item }
end

outer_function
# 123

第二个只是一个小问题:

def outer_function
  list = Proc.new { return [ 1, 2, 3 ] }

  # The call to `each` is dead code since the call to `list.call` will already return
  list.call.each { |item| another_function item }
end

# This method never gets called
def another_function item
  puts item
end

outer_function
# => [1, 2, 3]

return用于从方法返回值。那么,return从哪个方法返回?好吧,它从outer_function返回,因为那是唯一的方法! listProc,不是方法。要从Proc返回,请改为使用next关键字:

def outer_function
  list = Proc.new { next [ 1, 2, 3 ] }

  list.call.each { |item| another_function item }
end

def another_function item
  puts item
end

outer_function
# 1
# 2
# 3

或者,您可以完全省略next,因为在复合表达式(块,方法,类,等等)中计算的最后一个表达式无论如何都是它的返回值:

def outer_function
  list = Proc.new { [ 1, 2, 3 ] }

  list.call.each { |item| another_function item }
end

def another_function item
  puts item
end

outer_function
# 1
# 2
# 3

或者,您可以将Proc替换为lambda(实际上也是Proc但语义略有不同):

def outer_function
  list = -> { return [ 1, 2, 3 ] }

  list.call.each { |item| another_function item }
end

def another_function item
  puts item
end

outer_function
# 1
# 2
# 3

Proc(由Proc.newKernel#proc创建)和lambdas(由Kernel#lambda创建或stabby lambda literal -> (params) { code }之间存在两个语义差异):return中的Proc从词法封闭方法返回(就像在块中一样)而lambda中的return从lambda本身返回(就像在方法中一样),并且Proc s的参数绑定语义与块的相同,而lambdas的参数绑定语义与方法相同。

回顾一下:有两个区别,return和参数绑定。在这两种情况下,Proc的行为都像一个块,lambda的行为就像一个方法。助记符:Proc押韵"阻止"和" lambda"和"方法"都是希腊人。

答案 2 :(得分:0)

下面将为你做,只要注意inner_function不是一个真正的方法。因此它是一个lambda,行为就像一个方法。在此处阅读更多内容LINK

def outer_function(item)
    outer_variable = "input"
    inner_function = lambda { puts item   puts outer_variable }
    inner_function[]
end

outer_function "I'm here with " # prints "I'm here with input"