论坛首页 编程语言技术论坛

疑问:yield到底是怎么运作的?

浏览 31020 次
该帖已经被评为精华帖
作者 正文
   发表时间:2006-10-31  
thx,布娃娃同学。你的睿智能够照亮javaeye的每一个角落

0 请登录后投票
   发表时间:2006-11-01  
我觉得布娃娃同学在这个问题上被误导了,把简单问题复杂化了。如qiezi所说,yield在ruby里面就是个Proc#call。

用来解释coroutine的最好的例子就是producer/consumer问题。producer和consumer这两个coroutine是互相yield的,而iterator和block之间是单向的,block只有一个入口和一个出口,所以其实block是一个subroutine。

上面是从iterator和block的关系来说。从yield本身的能力来说,也没有办法用它实现来回切换context,换句话说不能用yield来实现coroutine。在ruby中coroutine必须用callcc来实现。这个似乎和其他语言不一样。我对别的语言并不很清楚,但是从读到的一些伪代码来看,似乎别的语言是可以用yield来做coroutine的。

yield的涵义更宽一些,是和coroutine关联的;call的涵义要窄一些,是和subroutine关联的。本来这个地方用call是很容易理解的,但是用了yield就让人想太多。

另外,threading里面也有个yield概念,就更容易让人困惑了。multi-threading是操作系统、虚拟机的概念,关注的是程序如何“同时”运行;而coroutine的yield概念是语言层面的,关注的是语句执行的顺序。threading的yield,就是“让”,把CPU资源让出来,让给谁了,不知道。而Ruby的yield,虽然不带参数,我们很清楚是让给block了,而且block一定会返回回来,并且带个返回值。

说完概念再看实际例子吧
def fibUpTo(max)
  i1, i2 = 1, 1        # parallel assignment
  while i1 <= max
    yield i1
    i1, i2 = i2, i1+i2
  end
end
fibUpTo(1000) { |f| print f, " " }


不用yield的等价实现就是
def fibUpTo(max,&block)
  i1, i2 = 1, 1        # parallel assignment
  while i1 <= max
    block.call i1
    i1, i2 = i2, i1+i2
  end
end
fibUpTo(1000) { |f| print f, " " }
end


在第二个实现中,block.class 实际是Proc。这一点可以加个 puts block.class来证实。

所以,很简单,yield就是Proc#call,就是调用block这个没有名字的subroutine。
0 请登录后投票
   发表时间:2006-11-01  
布娃娃:context switch不足以表明coroutine,一般的方法调用(subroutine)也是context switch的,只有能保留上次context的(continuation)才是coroutine。Ruby的yield只是种匿名函数调用的syntax sugar。你用的yield 1, yield2, yield3的例子是用1,2,3去初始化块变量, block本身不记得上次的context:
>> def test2                 
>>   yield                   
>>   yield                   
>>   yield                   
>> end                     
=> nil                       
>> test2{x ||= 0; puts x+= 1}
1                            
1                            
1                            
=> nil


对比一下python的yield:
def fib():
  x, y = 0, 1
  while 1:
    x, y = y, x+y
    yield x

g = fib()
for i in range(9):
  print g.next(),  

显然python的generator是lazy的,不是简单的调用/返回。
ruby要完成同样的lazy效果的话,得借助于callcc实现的coroutine,光靠yield是办不到的。

*发完才发现楼上把我想说的先说了,所见略同呼呼。
0 请登录后投票
   发表时间:2006-11-01  
aardvark和cookoo的回复,突然让我想到了coroutine和yield在c++项目中应用的可能,google下,还不少。在这里感谢各位。

0 请登录后投票
   发表时间:2006-11-01  
yield机制更像是匿名lamda
就像C++中成员函数隐含this参数
ruby函数也有一个隐含参数
然后语法要求这个参数必须是lamda
yeild相当与调用这个匿名函数

用起来倒是不错
但个人感觉他太愚蠢
lamda已经非常优雅了
没必要使用这种折中模式
0 请登录后投票
   发表时间:2006-11-01  
cookoo 写道
布娃娃:context switch不足以表明coroutine,一般的方法调用(subroutine)也是context switch的,只有能保留上次context的(continuation)才是coroutine。Ruby的yield只是种匿名函数调用的syntax sugar。你用的yield 1, yield2, yield3的例子是用1,2,3去初始化块变量, block本身不记得上次的context:
>> def test2                 
>>   yield                   
>>   yield                   
>>   yield                   
>> end                     
=> nil                       
>> test2{x ||= 0; puts x+= 1}
1                            
1                            
1                            
=> nil


对比一下python的yield:
def fib():
  x, y = 0, 1
  while 1:
    x, y = y, x+y
    yield x

g = fib()
for i in range(9):
  print g.next(),  

显然python的generator是lazy的,不是简单的调用/返回。
ruby要完成同样的lazy效果的话,得借助于callcc实现的coroutine,光靠yield是办不到的。

*发完才发现楼上把我想说的先说了,所见略同呼呼。


ruby的yield跟python的yield根本就不是一个东西吧
0 请登录后投票
   发表时间:2006-11-01  
whisper 写道
yield机制更像是匿名lamda
就像C++中成员函数隐含this参数
ruby函数也有一个隐含参数
然后语法要求这个参数必须是lamda
yeild相当与调用这个匿名函数

用起来倒是不错
但个人感觉他太愚蠢
lamda已经非常优雅了
没必要使用这种折中模式


不太明白你的意思,你说c++中没有必要用yield还是ruby中?
0 请登录后投票
   发表时间:2006-11-01  
yield就是一个context saver+jump。没有多少神秘的地方,而最复杂的就是这个context了。

任何一段代码,其运行时,都有自己的context,提供这段代码中所有可以看到的变量,资源句柄,PC……,而这些东西一旦被保留下来,就可以把它认为是一个label了,可以实施非局部的jump了,而不再是stack式的后进先出,层层递进和消退的方式。

可以参见http://home.macau.ctm.net/~kewei/youbing/coroutine-iterator-in-c.html,里面有非常详细的说明。
0 请登录后投票
   发表时间:2006-11-01  
jack 写道
whisper 写道
yield机制更像是匿名lamda
就像C++中成员函数隐含this参数
ruby函数也有一个隐含参数
然后语法要求这个参数必须是lamda
yeild相当与调用这个匿名函数

用起来倒是不错
但个人感觉他太愚蠢
lamda已经非常优雅了
没必要使用这种折中模式


不太明白你的意思,你说c++中没有必要用yield还是ruby中?

我是觉得ruby没必要用这语法糖
如果一个函数内部yield了
那么证明他需要一个外部给定的参数来完成其内部工作
也就是说yield也是这个函数接口的一部分
如果是通过显示的多一个参数似乎更加的明确了函数的行为
而不是依赖于文档和约定俗成

像haskell那样的函数式语言已经很好的诠释了lamda的使用
其优雅程度比ruby有过之而无不及
所以这yield用起来虽爽
但心里总是有个疙瘩

map :: [a] -> (a -> b) -> [b]
map [] f = []
map (x: xs) f = (f x) : (map xs f)
不觉得这是神的语言么?
0 请登录后投票
   发表时间:2006-11-01  
aardvark和cookoo的 ruby yield 代码有些不同。

cookoo 的 yield 后面没有返回值。
aardvark 的 yield 后面带一个返回值。

从 aardvark 给出的两段对比代码来看,fibUpTo 好像记住了上次的 context。

right ?

根据 whisper 的回复,有些理解 aardvark 给出的两段对比代码。

ruby yield 的意思好像是等待一个 block 参数?

表面上看来是一个 iterator (不需要block参数,但是需要记住状态),实际上是一个 visitor(需要一个block参数,不需要记住状态)。

不过,我觉得,按照 iterator 来理解 yield 的表面意思,从用法上来说,也说得通。
0 请登录后投票
论坛首页 编程语言技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics