Ruby 请教下关于关于闭包的问题

ptmagic · 2012年05月06日 · 最后由 aNdReW_Qx 回复于 2012年05月08日 · 5921 次阅读

def my_method x = "GoodBye" yield("cruel") end x = "Hello" my_method{|y| "#{x}, #{y} world"}

#=>"Hello ,cruel world" 请问怎么规定块的绑定范围?x = “Hello”是在方法外部定义的啊

是指 x 在什么时候不会被 my_method 调用到吗?

#1 楼 @cantin 我想问的是在 my_method 方法中 yield 了,应该给下面的代码块传递一个绑定啊,应该把 X=“GoodBye”给传递过去啊,为什么最后取的值是 Hello 呢?

x 是在 my_method 中定义的局部变量,在外部是调用不到。同个类的不同方法也是无法调用的。 另外:

class C
def show
    @s = '111'
    x = 'asd'
    yield
end

C.new.show { puts self }

puts 出来的是 main 而不是 C,所以 block 的调用者是 main 这个 object,而不是 C 的实例,C 的实例变量是在 block 调用不到的,在 block 中调用 puts @s是不会出现任何东西的,何况是 x 这个局部变量。

闭包的变量是在声明时候绑定的。 另外为了解决在闭包中使用与其外部 scope 重名的变量这个问题,ruby1.9 中增加了新的语法,支持其只使用闭包内部声明的变量

x = 1
proc { |;x|   #注意这个新语法,使用";"就让闭包不使用外部x而使用内部x
    x = 2
}


所以楼主这个问题确实是一个问题,ruby 自己也看到了

#3 楼 @cantin 感谢理解了一点了,剩下再试试

#4 楼 @donnior 感谢您的回答

#4 楼 @donnior

def show
  x = 1
  yield x
end

x = 2
show { |;x| puts x }

输出是为空 如果可以的话,麻烦给个关于;资料的链接,这个我还没接触过,thx

#4 楼 @donnior 楼主说的不是 ruby 闭包的问题,所以加了新语法依然无用

$ irb
1.9.3p194 :001 > def ss
1.9.3p194 :002?>   x = 'aa'
1.9.3p194 :003?>   yield('cc')
1.9.3p194 :004?> end
 => nil 
1.9.3p194 :005 > x = 'bb'
 => "bb" 
1.9.3p194 :006 > ss{|y;x| "#{x}, #{y} world"}
 => ", cc world"


楼主估计还没有理解闭包,闭包的一个重要作用就是延长了外部变量的生命周期(ruby 里面的 binding 就是对外部环境的对象化的结果) 简单一点说,这里有三个 x 变量,外部环境中的变量 x,函数体内部的局部变量 x,传入函数的 block 自身携带的局部变量 x,三者不可混为一谈

#7 楼 @cantin 不好意思,先前把你的问题看错了,不是变量重名的问题。。。。 另外 block local variables 是为了解决 closure 内的局部变量和外部变量重名的问题,你这个闭包里面没有定义局部变量 x

x = 2
show { |;x| 
    x=1
    puts x 
}



更多资料可 google“ruby 1.9 block local variables”

#8 楼 @fsword 因为上面已经有热心人帮解答了闭包 binding 的问题,所以就只是告诉楼主另一种对付重名问题了,呵呵

#8 楼 @fsword 您好,您的意思是不是在 block 里面的参数,除了 yield 传入的参数,其余的都取整个上下文环境的值?

通过 yield 可以实现闭包吗?楼主对于闭包的概念可能有点糊涂,我觉得别人再给你解释也解释不清楚。建议还是应该先看看书,用到了自然就了解了。

#7 楼 @cantin ; 很好理解,就是一个本地 (独立) 变量,就像方法的形参一样。上面的代码,puts x 时,x 是未初始化状态,不受 x = 2 影响,所以输出为 nil.

#8 楼 @fsword 对了,我想继续请教个问题,ruby 中 closure 的变量绑定这些我基本清楚,但是我一直想知道闭包中这种延长外部变量的生命周期甚至在闭包中直接修改外部变量的值是怎么实现的。这个可能不单纯涉及 ruby,其他支持 closure 的应该也是一样;就我的理解,closure 的代码在执行时应该在一个新的对象中(或者说上下文)执行的,那么它是如何能够修改另外一个对象的某个变量甚至是方法中的局部变量的值的?我猜跟方法执行时的底层实现例如 stack 相关?一直想把这个弄明白。

#14 楼 @donnior 我们一般说对象是为数据添加了(相关)行为,而闭包就是为行为绑定了(关联)数据,所以它们都是某种“数据 + 行为”的编程结构,在实现层面上应该是很象的。不知道你会不会 java,java 中没有闭包,替代它的是匿名内部类,类似这样:

final int x = 100;
someCollection.sort(new Comparable(){
    public int compare(Object a, Object b){
        ...... // 这里可以使用x的值,因为被"复制"进来了
    }
}

可以看出,除了因为不支持 duck type 而多出的那些类型系统代码外,匿名内部类和闭包的唯一区别就是匿名内部类要 new 出一个对象,从这个角度看,闭包其实就象一个只有一个函数的对象。而所谓 binding,可以这么理解,构造这个(闭包)对象时,我们将需要用到的外部变量都复制进来以备今后使用,这就是所谓“延长了变量的生命周期”的含义,而如果这个变量是在引用一个对象,那么显然这个对象就可以在闭包中被影响。最后,具体对于 ruby 语言,由于一切变量都是对某个对象的引用,所以内部就应该能修改变量在外部的值

#12 楼 @ptmagic 基本上是这个意思,不过如果闭包里的局部变量与外部重名,ruby 会混淆,这时就要用 @donnior 提到的那个语法声明一下了 另外,估计有人会误解,特别说明一下,对于下面这段代码

def xx
  yield
end

xx{ puts 'something }

大括号里面的东西叫做闭包,xx 是个函数而已,只是这个函数接受了一个闭包作为参数

#15 楼 @fsword Java 我很熟,呵呵,java 里面没有闭包,匿名内部类在引用外部变量时必须 final,这个很好理解,因为不能在匿名内部类里面修改外部定义变量的值;但是闭包刚好是突破了这个限制,所以肯定不是简单的复制,如果是复制的话是做不到闭包执行完毕之后原外部对象的值也被修改了的(就像传值调用一样)

class A

    def closure_method
        @x = 1
        lambda do
            @x += 1
        end
    end

    def x
        @x
    end

end

a = A.new
m = a.closure_method
m.call
p a.x     =>  2




所以任何语言中 closure 最大的魔力在这,我猜这跟底层堆栈的实现相关,跟一般方法不一样,不过一直没有找到确却的资料

至于 ruby 中一切皆是对象,只是说可能提供了某种方便而已;毕竟闭包除了这点还有那复杂的上下文,跟传统的确实不太一样

@fsword 刚才仔细搜索了下,http://www.hokstad.com/how-to-implement-closures.html 这篇文章里面给出了可能的几种实现方式,确实有说明闭包对堆栈的特殊要求,也给出了类似你所说的复制式的方法,但是这是作者自己说实现一个 ruby 编译器的几种方法,仍然不清楚 ruby 自己是怎么做的

哎,有点底层。。。也许不应该深究了

匿名 #19 2012年05月07日

#17 楼 @donnior 忘了在哪里看的,闭包就是保持外部引用。 因为你修改的还是同一个对象。所以外部也是变化的,没你猜的这么复杂

#19 楼 @jjym @fsword 哦,如此说,就是简单应用了 ruby 中一切都是对象这个基础,可能是我把问题想复杂了;我是在因为看到 groovy 里面的闭包支持,而 groovy 是以编译成 java class 的方式在 java 之上运行的,而 java 里面是基本类型不是对象,所以就想了解下闭包的实现

多谢两位

#16 楼 @fsword #17 楼 @donnior

嘿~ 看了半天我都晕了,怎么我老觉得我理解的闭包和大家说的不是一回事儿呀!在我看来,Ruby 之中的普通 yield, 因为他的实现方式 (并行赋值,并且更像代码块), 所以给我的印象是,和闭包八杆子打不找嘛。我感觉你根本不可能通过 yield 来创建一个闭包。而 lambda 因为更像方法 (匿名方法), 闭包都是通过返回一个 lambda {... }实现的。

也许我理解不对,通过 yield 也能创建闭包?也可能因为以前看过 Matz 的一篇采访实录缘故吧。我印象很深刻,当时 Matz 说,在实现 Ruby 时,闭包这个概念来自于 Lisp 当中的 lambda 运算。

不管怎么说,拿 yield 的例子来谈闭包,是不是不妥?会误导新人?

#21 楼 @zw963 没人说 yield 创建闭包吧..只是说 yield 调用的是闭包,因为所有的 block 都是闭包,lambda 可以理解为一种特殊的 block

#22 楼 @aNdReW_Qx

那么按你的说法,所有 block 都是闭包,下面的语句,也算创建了一个闭包了?

def a(&block)
s = "hello, world!"
yield s
end

a {|x| puts x}    # => 这个一个方法调用差不多,  和闭包八杆子打不找嘛.





我怎么感觉咱们理解的闭包的范围好像不一致,你们所形容的闭包范围是不是太广了点?通过 yield 就算形成一个闭包,你也没办法在多个调用之间传递,既然讨论闭包,怎么着也得用 call 来讨论才靠谱。我记得咱社区的@skandhas曾经说过,闭包的一大特性是被闭包的语句达到延后执行的效果,没有这个效果又怎么能叫做闭包?

形成一个闭包的唯一办法就是:返回一个 Proc 对象,然后通过 block 设定一个上下文 binding, 并通过 call 延迟调用。我觉得这个帖子,楼主的标题绝对是有问题的。或者说,这帖子根本不是在讨论闭包...

#24 楼 @zw963 是的,楼主还没有理解闭包,他只是提了一个关于 block 的问题。 ruby 对闭包的支持体现在 Proc 对象上,不过为了编程方便,ruby 发明了 block 这种机制,我们说的时候也确实容易把它和闭包等同起来,不过就像你说的,如果这个 block 没有携带着当时的 binding 对象被传递出去,其实也不能算是闭包

#25 楼 @fsword

我刚才一直在想,可能我自己对闭包理解也有些问题,我感觉把闭包的概念和一个闭包对象的传递,貌似混淆到一起去了。不过,我倒是觉得 Ruby 中的闭包 (类似于 ECMAScript 中的闭包), 和 Java 中的闭包,分开来理解,其实是一个好主意?

@fsword A Proc.new, then, is not quite truly closed: it depends in the creating context still existing, because the "return" is tied to that context.

Not so for lambda.

甚至因为 return 的定义不同,Proc.new 都不算是真正的闭包。只有 lambda 才是闭包。

扁平化作用域,看看 ruby 元编程这本书,我看了 3 遍,最近终于明白了

#27 楼 @zw963 没错,所以说的时候可以稍微简单些,但是心理还是要分清楚 lambda、proc、Proc 以及 block 的概念

匿名 #31 2012年05月08日

#27 楼 @zw963 不明白闭包和 return 有什么关系 我觉得只要能保持外部引用就可以算作闭包

def foo
  x = "你好"
  ->{puts x}
end
foo[]

def foo
  x = "你好"
  Proc.new{
    puts x
  }
end
foo[]

为什么第二个不是'真正的闭包'?

#31 楼 @jjym

好吧,我承认,连我自己现在对于闭包的准确定义也说不清是清楚还是糊涂了。

准确的来讲,上面的两个示例都没有闭包,你只不过调用了两个方法而已,方法都有自己的局部变量,这和普通方法调用又有什么区别?

def foo
  x = "你好"        # => 你的代码中x的含义和这里又有什么区别?
  puts x
end

foo

关键是通过方法返回一个 Proc 对象,并且为这个 Proc 对象绑定一个上下文。

def foo(str)
  lambda {puts str}
end

foo("你好")        # => 通过这一步才形成一个闭包. 方法调用结束, str = "你好" 这个binding仍旧存在.

foo.call

至于上那段英文,我是从一个老外的帖子里摘取的。说实话,我也没太明白 return 和是否闭包有什么区别,不过如果你希望像方法那样使用一个闭包 (期望得到所需的返回值), 很显然,用 Proc.new 是有问题的。也就是说,站在调用角度考虑,闭包不应该是代码块 (yield) 的方式,而应该是方法 (call) 的方式。有关这部分,我觉得没必要细分了,就像 Matz 接受采访时说的那样,在 Ruby 中,闭包就是 lambda. OK!

匿名 #33 2012年05月08日

#32 楼 @zw963 我觉得我那两个例子都是闭包,并且和 js 中的模拟 oop 的比较像。本来调用完方法 x 对象在栈上就已经没有引用了,所以可以被 GC 回收,但是因为在 lambda 和 proc 中保持其引用所以不会被回收,而这种延长其生命周期的情况我认为就是闭包。你的两段代码实际都是一样的。只是一个作为参数而已,实际上都是延长了对象的生命周期,在闭包这一方面至少我认为没什么本质的不同。proc 可能行为上更接近代码块,和其他语言的 lambda 有些区别,不过我认为在闭包这一方面他们是差不多的。 以上只是说出我个人看法,不保证其正确性,Matz 的采访有机会一定要看下。

#33 楼 @jjym

广义上来说,你可以说返回一个{}就形成一个闭包。就如同@fsword 说的,心里分清楚就行了。

翻了半天,还是没找到那片 matz 的采访实录,不过我印象挺深,没错的。

#23 楼 @zw963 所有的 block 都是其定义环境的闭包,在默认状态下 block 可以调用其创建环境的局部变量,这和异步同步无关

def a(&block)
s = "hello, world!"
yield s
end

a {|x| puts x}  

你这个例子不典型,但是不代表就不能体现 传给 a 的 block 里能调用其定义环境的局部变量,也就是外部,在这里是 object 的 class 内部..

比如最简单的例子


def method
  msg = 'hello'
  3.times.each {puts msg}
end

上例中调用 method,3 作为一个 num 对象的 times 函数,本身执行过程中是不能访问 method 的局部变量 msg 的 (也没有通过参数传入),但是通过 yield 调用了外部创建的 block 以后就能访问到这个变量,这里 times 是一个同步方法,不存在延迟调用的问题,但他确实在自身执行过程中间接访问了 block 定义环境中的变量,也就是说 block 记住了自己被创建的环境,他永远运行在自己被创建的环境下,而不是调用他的环境(这里是 FixNum 对象 3) 而 block 如果转为 Proc 或者 lambda,作为变量,能够延长局部变量的生命周期,使他们在创建他的函数执行完毕以后能够继续存在不被回收,这是闭包的另一个特性。

Daniel_Xu [该话题已被删除] 提及了此话题。 04月03日 10:58
需要 登录 后方可回复, 如果你还没有账号请 注册新账号