Ruby的代码块是否与C#的lambda表达式相同?

Shu*_*huo 29 c# ruby

这两个基本上是一回事吗?他们看起来和我很相似.

lambda表达式是否从Ruby借用了它的想法?

Mat*_*ggs 45

Ruby实际上有4个结构都非常相似

块背后的想法是实现真正轻量级策略模式的一种方式.一个块将在函数上定义一个协程,该函数可以使用yield关键字将控制权委托给该协程.我们在ruby中使用块几乎所有东西,包括几乎所有的循环结构或using在c#中使用的任何地方.块外的任何内容都在块的范围内,但是反之不是真的,除了块内的返回将返回外部范围.它们看起来像这样

def foo
  yield 'called foo'
end

#usage
foo {|msg| puts msg} #idiomatic for one liners

foo do |msg| #idiomatic for multiline blocks
  puts msg
end
Run Code Online (Sandbox Code Playgroud)

PROC

一个proc基本上是一个块并将其作为参数传递.一个非常有趣的用途是你可以在另一个方法中传递一个proc作为一个块的替代.Ruby有一个用于proc强制的特殊字符,它是一个特殊的规则,如果方法签名中的最后一个参数以&开头,它将是方法调用的块的proc表示.最后,有一个内置方法调用block_given?,如果当前方法定义了一个块,它将返回true.看起来像这样

def foo(&block)
  return block
end

b = foo {puts 'hi'}
b.call # hi
Run Code Online (Sandbox Code Playgroud)

为了更深入一点,有一个非常巧妙的技巧,rails添加到Symbol(并在1.9中合并到核心ruby).基本上,通过调用to_proc它旁边的任何东西,强制和强制都会发挥其魔力.所以rails们添加了一个Symbol#to_proc,可以在传入的任何内容上调用自己.这可以让你为任何聚合样式函数编写一些非常简洁的代码,它只是在列表中的每个对象上调用一个方法

class Foo
  def bar
    'this is from bar'
  end
end

list = [Foo.new, Foo.new, Foo.new]

list.map {|foo| foo.bar} # returns ['this is from bar', 'this is from bar', 'this is from bar']
list.map &:bar # returns _exactly_ the same thing
Run Code Online (Sandbox Code Playgroud)

更高级的东西,但imo真的说明了你可以用procs做的那种魔术

Lambda表达式

lambda的目的在ruby中与在c#中几乎相同,这是一种创建内联函数以传递或在内部使用的方法.像块和过程一样,lambdas是闭包,但与前两个不同,它强制执行arity,并且从lambda返回退出lambda,而不是包含范围.您可以通过将块传递给lambda方法来创建一个,或者在ruby 1.9中传递给 - >

l = lambda {|msg| puts msg} #ruby 1.8
l = -> {|msg| puts msg} #ruby 1.9

l.call('foo') # => foo
Run Code Online (Sandbox Code Playgroud)

方法

只有严肃的红宝石极客真正理解这一个:)方法是一种将现有函数转换为可以置于变量中的函数的方法.您可以通过调用method函数并传入符号作为方法名称来获取方法.你可以重新绑定一个方法,或者如果你想炫耀你可以将它强制转换成一个proc.重写前一种方法的方法是

l = lambda &method(:puts)
l.call('foo')
Run Code Online (Sandbox Code Playgroud)

这里发生的是你正在为puts创建一个方法,将它强制转换为proc,将其作为lambda方法的块的替换传递,而lambda方法又返回lambda


随意询问任何不清楚的事情(在没有irb的情况下,在一个晚上写这个真的很晚,希望它不是纯粹的胡言乱语)

编辑:解决评论中的问题

list.map&:bar我可以将此语法与带有多个参数的代码块一起使用吗?假设我有hash = {0 =>"hello",1 =>"world"},我想选择以0为键的元素.也许不是一个好例子. - Bryan Shen

要深入到这里,但要真正理解它是如何工作的,你需要了解ruby方法调用的工作方式.

基本上,ruby没有调用方法的概念,所发生的是对象将消息传递给彼此.obj.method arg您使用的语法实际上只是围绕更明确的形式的糖,这是obj.send :method, arg,并且在功能上等同于第一种语法.这是语言的基本概念,就是为什么之类的东西method_missingrespond_to?意义,在第一种情况下你只是处理无法识别的消息,你正在检查,看它是否正在监听该消息的第二位.

另一件要知道的是相当深奥的"splat"算子*.根据其使用的位置,它实际上做了非常不同的事情.

def foo(bar, *baz)
Run Code Online (Sandbox Code Playgroud)

在一个方法调用中,如果它是最后一个参数,splat将使该参数全局上传所有传入函数的附加参数(类似于paramsC#)

obj.foo(bar, *[biz, baz])
Run Code Online (Sandbox Code Playgroud)

在方法调用(或任何其他接受参数列表)时,它会将数组转换为裸参数列表.下面的代码段与上面的代码段相同.

obj.foo(bar, biz, baz)
Run Code Online (Sandbox Code Playgroud)

现在,send*记,Symbol#to_proc基本上是这样实现的

class Symbol
  def to_proc
    Proc.new { |obj, *args| obj.send(self, *args) }
  end
end
Run Code Online (Sandbox Code Playgroud)

所以,&:sym将要创建一个新的proc,它调用.send :sym传递给它的第一个参数.如果传递了任何额外的args,它们将被args全局化为一个名为的数组,然后将其splat到send方法调用中.

我注意到&在三个地方使用:def foo(&block),list.map&:bar,以及l = lambda&method(:puts).他们有同样的含义吗? - Bryan Shen

是的,他们这样做.一个&将呼吁to_proc它旁边的东西.在方法定义的情况下,它在最后一个参数上具有特殊含义,您可以在其中拉入定义为块的协同例程,并将其转换为proc.方法定义实际上是语言中最复杂的部分之一,可以在参数和参数的位置中存在大量的技巧和特殊含义.

b = {0 =>"df",1 =>"kl"} p b.select {| key,value | key.zero?我试图将其转换为p b.select&:zero ?,但它失败了.我想这是因为代码块的参数数量是两个,但是&:零?只能拿一个参数.有什么方法可以做到吗? - Bryan Shen

这应该在早些时候解决,不幸的是你不能用这个技巧来做.

"方法是将现有函数转换为可以放入变量的函数的方法." 为什么l =方法(:puts)不够?在这种情况下,lambda和意味着什么? - Bryan Shen

这个例子非常人为,我只是想在它之前显示相同的代码,在那里我将proc传递给lambda方法.我会花一些时间来重新写一下,但你是对的,method(:puts)完全足够了.我试图展示的是你可以使用&method(:puts)任何可以阻挡的地方.一个更好的例子就是这个

['hello', 'world'].each &method(:puts) # => hello\nworld
Run Code Online (Sandbox Code Playgroud)

l = - > {| msg | put msg} #ruby 1.9:这对我不起作用.在我检查了Jörg的答案之后,我认为它应该是l = - >(msg){puts msg}.或者也许我使用的是不正确的Ruby版本?我的红宝石1.9.1p738 - Bryan Shen

就像我在帖子中所说的那样,当我写答案时,我没有可用的东西,而你是对的,我傻了(把我的大部分时间花在1.8.7上,所以我不习惯新语法)

在stabby位和parens之间没有空隙.试试l = ->(msg) {puts msg}.实际上对这种语法有很多阻力,因为它与语言中的其他内容完全不同.


Jör*_*tag 33

C#与Ruby

这两个基本上是一回事吗?他们看起来和我很相似.

他们是非常不同的.

首先,C#中的lambdas执行两个非常不同的事情,其中​​只有一个在Ruby中具有等价物.(相当于,惊喜,lambda,而不是块.)

在C#中,lambda表达式文字被重载.(有趣的是,就我所知,它们是唯一重载的文字.)并且它们的结果类型超载.(同样,它们是唯一的在C#的事情,可以在它的结果类型而过载,方法只能在他们的参数类型被重载.)

C#lambda表达式文本可以要么是匿名一段可执行代码一个抽象表示一个匿名一段可执行代码的,这取决于它们的结果类型是否为Func/ ActionExpression.

Ruby没有任何后者功能的等价物(嗯,有特定于解释器的非便携式非标准化扩展).前一个功能的等价物是lambda,而不是块.

lambda的Ruby语法与C#非常相似:

->(x, y) { x + y }           # Ruby
(x, y) => { return x + y; } // C#
Run Code Online (Sandbox Code Playgroud)

在C#中,return如果只有一个表达式作为正文,则可以删除分号和大括号:

->(x, y) { x + y }  # Ruby
(x, y) => x + y    // C#
Run Code Online (Sandbox Code Playgroud)

如果只有一个参数,则可以不用括号:

-> x { x }  # Ruby
x => x     // C#
Run Code Online (Sandbox Code Playgroud)

在Ruby中,如果参数列表为空,则可以不使用参数列表:

-> { 42 }  # Ruby
() => 42  // C#
Run Code Online (Sandbox Code Playgroud)

在Ruby中使用文字lambda语法的另一种方法是将块参数传递给Kernel#lambda方法:

->(x, y) { x + y }
lambda {|x, y| x + y } # same thing
Run Code Online (Sandbox Code Playgroud)

这两者之间的主要区别在于你不知道是什么lambda,因为它可以被覆盖,覆盖,包装或以其他方式修改,而文字的行为不能在Ruby中修改.

在Ruby 1.8中,您也可以使用,Kernel#proc尽管您应该避免使用它,因为该方法在1.9中有所不同.

Ruby和C#之间的另一个区别是调用 lambda 的语法:

l.()  # Ruby
l()  // C#
Run Code Online (Sandbox Code Playgroud)

即在C#中,您使用相同的语法来调用您将用于调用其他任何内容的lambda,而在Ruby中,调用方法的语法与调用任何其他类型的可调用对象的语法不同.

另一个不同之处在于,在C#中,()内置于语言中,并且仅适用于某些内置类型,如方法,委托,Actions和Funcs,而在Ruby中,.()它只是语法糖,.call()因此可以通过以下方式与任何对象一起使用实施一种call方法.

触发与lambdas

那么,什么 lambdas呢?嗯,他们是Proc班级的实例.除了有轻微的复杂性之外:实际上有两种不同类型的实例Proc略有不同.(恕我直言,Proc该类应该分为两类,用于两种不同的对象.)

特别是,并非所有Proc的都是lambdas.您可以Proc通过调用Proc#lambda?方法来检查a 是否为lambda .(通常的惯例是将lambda称为Proc"lambdas"而将非lambda 称为Proc"procs".)

非拉姆达特效是通过使块创建Proc.newKernel#proc.但请注意,在Ruby 1.9之前,Kernel#proc创建一个lambda,而不是proc.

有什么不同?基本上,lambdas的行为更像是方法,procs的行为更像是块.

如果您已经关注了Project Lambda for Java 8邮件列表的一些讨论,那么您可能会遇到这样的问题:非局部控制流应该如何处理lambdas.特别是,在lambda中有三种可能的合理行为return(嗯,三种可能但只有两种是真正合理的):

  • 从lambda回来
  • 从方法返回lambda被调用
  • 从lambda创建的方法返回

最后一个有点不确定,因为一般来说这个方法已经返回了,但是其他两个都很有意义,而且没有比另一个更正确或更明显.Project Lambda for Java 8的当前状态是它们使用两个不同的关键字(returnyield).Ruby使用两种不同的Procs:

  • procs从调用方法返回(就像块一样)
  • lambda从lambda返回(就像方法一样)

它们在处理参数绑定方面也有所不同.同样,lambdas的行为更像是方法,而procs的行为更像是块:

  • 你可以传递更多的参数到一个proc而不是参数,在这种情况下,多余的参数将被忽略
  • 你可以传递较少的参数到proc而不是参数,在这种情况下,多余的参数将被绑定 nil
  • 如果传递单个参数,它是一个Array(或响应to_ary)和proc有多个参数,所述阵列将解包和元件结合到参数(完全一样,他们会在解构分配的情况下)

块:轻量级触发器

块本质上是一个轻量级的proc.Ruby中的每个方法都只有一个块参数,它实际上并没有出现在它的参数列表中(后面会有更多内容),即是隐式的.这意味着在每个方法调用中,您都可以传递一个块参数,无论方法是否需要它.

由于该块未出现在参数列表中,因此没有可用于引用它的名称.那么,你如何使用它?好吧,你可以做的唯一两件事(不是真的,但稍后会更多)是通过关键字隐式调用yield并检查块是否通过了block_given?.(由于没有名称,你不能使用callnil?方法.你会怎么称呼它们?)

大多数Ruby实现以非常轻量级的方式实现块.特别是,它们实际上并不将它们作为对象实现.但是,由于它们没有名称,因此无法引用它们,因此实际上无法判断它们是否对象.您可以将它们视为触发器,这样可以更容易,因为要记住一个不那么不同的概念.只是将它们实际上没有像块一样实现为编译器优化.

to_proc&

这里真正的方式来引用块:在&印记/修改/一元前缀运算符.它只能出现在参数列表和参数列表中.

参数列表中,它表示" 隐式块包装到proc中并将其绑定到此名称".在参数列表,它的意思是" 解开这个Proc成块".

def foo(&bar)
end
Run Code Online (Sandbox Code Playgroud)

在方法内部,bar现在绑定到表示块的proc对象.这意味着您可以将其存储在实例变量中以供以后使用.

baz(&quux)
Run Code Online (Sandbox Code Playgroud)

在这种情况下,baz实际上是一个采用零参数的方法.但当然它需要所有Ruby方法采用的隐式块参数.我们传递变量的内容quux,但首先将其展开到块中.

这种"展开"实际上不仅适用于Procs.&首先调用to_proc对象,将其转换为proc.这样,任何对象都可以转换为块.

Symbol#to_proc我相信,最广泛使用的例子是90年代后期首次出现的例子.当它被添加到ActiveSupport并从它传播到Facets和其他扩展库时变得流行.最后,它被添加到Ruby 1.9核心库并向后移植到1.8.7.这很简单:

class Symbol
  def to_proc
    ->(recv, *args) { recv.send self, *args }
  end
end

%w[Hello StackOverflow].map(&:length) # => [5, 13]
Run Code Online (Sandbox Code Playgroud)

或者,如果将类解释为创建对象的函数,则可以执行以下操作:

class Class
  def to_proc
    -> *args { new *args }
  end
end

[1, 2, 3].map(&Array) # => [[nil], [nil, nil], [nil, nil, nil]]
Run Code Online (Sandbox Code Playgroud)

Methods和UnboundMethods

表示一段可执行代码的另一个类是Method类.Method对象是方法的具体代理.您可以创建一个Method对象,通过调用Object#method的任何对象,并传递要具体化方法的名称:

m = 'Hello'.method(:length)
m.() #=> 5
Run Code Online (Sandbox Code Playgroud)

.:响应Method,所以你可以将它们传递给你可以通过块的任何地方:

m = 'Hello'.:length
m.() #=> 5
Run Code Online (Sandbox Code Playgroud)

An to_proc是尚未绑定到接收器的方法的代理,即UnboundMethod尚未定义的方法.你不能调用一个self,但你可以调用一个UnboundMethod对象(它必须是你从中得到方法的模块的一个实例),它会将它转换为bind.

Method通过调用族中的一个方法创建对象,并将方法UnboundMethod的名称作为参数传递.

[1, 2, 3].each(&method(:puts))
# 1
# 2
# 3
Run Code Online (Sandbox Code Playgroud)

广义可调用对象

就像我上面已经暗示过的那样:Module#instance_methods和Procs 没什么特别之处.任何做出响应对象Method可以被称为和任何它响应对象call可以被转换为一个to_proc,从而解开成块并传递到一个希望将块的方法.

历史

lambda表达式是否从Ruby借用了它的想法?

可能不是.大多数现代编程语言都有某种形式的匿名文字代码块:Lisp(1958),Scheme,Smalltalk(1974),Perl,Python,ECMAScript,Ruby,Scala,Haskell,C++,D,Objective-C,甚至PHP(! ).当然,整个想法可以追溯到Alonzo Church的λ演算(1935年甚至更早).


Joe*_*sky 8

不完全是.但他们非常相似.最明显的区别在于,在C#中,lambda表达式可以在任何可能具有恰好是函数的值的位置; 在Ruby中,每个方法调用只有一个代码块.

他们都从Lisp(一种可以追溯到20世纪50年代后期的编程语言)借用了这个想法,后者又借用了20世纪30年代发明的Church's Lambda Calculus中的lambda概念.

  • Joel:我认为这个想法是你将lambda存储到一个变量中,然后你可以将该变量作为参数传递给一个函数. (2认同)