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

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

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

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

共收到 35 条回复

是指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的问题,所以就只是告诉楼主另一种对付重名问题了, 呵呵

#9楼 @donnior 明白了,thx

#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自己是怎么做的

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

#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才是闭包.

binding?

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

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

#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!

#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,作为变量,能够延长局部变量的生命周期,使他们在创建他的函数执行完毕以后能够继续存在不被回收,这是闭包的另一个特性。

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册