`
jack
  • 浏览: 392790 次
  • 来自: 温州
社区版块
存档分类
最新评论

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

阅读更多
  最早看到yield时,就用c/C++中的概念对比了一番,发现最接近就是“宏代码的展开”。粗看起来,这样的理解是可以的。不过马上就有个问题出来了,就是定义域。宏代码的展开,要求展开后的代码处于被展开的位置同一个定义域,否则,相关变量和函数就会出现没有定义的错误。 不过从下面的ruby代码来看,yield没有这样的问题存在

class A
def self.test
yield
end
end
class B
def test0
puts "call from class B"
end
def test1
A.test {test0}
end
end
b = B.new
b.test1


如果以代码展开的角度来理解上面代码的运行,那么就应该出现test0 不是A的函数这样的错误提示。不过运行下来很成功,没有出现任何错误。

还有一种可能是ruby的函数调用,就像C++一样,默认附带一个this对象。

def test1
A.test {test0}
end


这段代码在执行时是不是被修改成了

def test1
A.test {@this.test0}
end


ruby如果真有这个功能,上面代码的执行似乎能够说的通。

不过上面都是猜测,看下《Programming Ruby - 2nd》第一次提到yield的时候得说明
引用
First, a block may appear only in the source adjacent to a method call; the block is written starting on the same line as the method call’s last parameter (or the closing parenthesis of the parameter list). Second, the code in the block is not executed at the time it is encountered. Instead, Ruby remembers the context in which the block appears (the local variables, the current object, and so on) and then enters the method. This is where the magic starts.
Within the method, the block may be invoked, almost as if it were a method itself, using the yield statement. Whenever a yield is executed, it invokes the code in the block.When the block exits, control picks back up immediately after the yield.1 Let’s start
with a trivial example.

def three_times
yield
yield
yield
end
three_times { puts "Hello" }
produces:
Hello
Hello
Hello


这段文字提到了3点。
第一,保存block相关的context
第二,block调用像函数本身的一部分
第三,有个神秘的control在操纵这一切
如果第一点,可以看作ruby给test0附加了个this对象,但是第二第三个就说不通了,如果只是代码展开,第二第三就不需要说明了。看来关于yield是代码展开的想法是完全错的。

那么从第三点来看,yield的确有个代码运行块(先这样叫着)切换的动作,最后运行完了又切换回来.
加入代码切换的说法之后,给出的例子代码似乎是这样运行的

1.运行A.test的上半部分直到遇到yield
2.写入block的context,同时使用block的context和A.test运行到一半的context,作为block的运行环境来运行block代码,
3.block运行完毕,清除block的context,然后继续运行A.test的下半部分。

古怪阿古怪,yield是真的这样运行的吗?

从前面yield的翻译讨论来看,yield带有切换,退让,让步这类的意思。
yield如果有执行代码切换的功能,多少带有超级微型线程的意思了。而且是主动切换,执行代码主体。yield完全是一个控制代码执行顺序的关键字了。真神奇。

分享到:
评论
11 楼 aardvark 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。
10 楼 jack 2006-10-31  
thx,布娃娃同学。你的睿智能够照亮javaeye的每一个角落

9 楼 buaawhl 2006-10-31  
看到那个“yield是占位符”的帖子我就有些疑惑了。
在我的印象中,Ruby Yield, Python Yield, C# Yield 都是用来实现 coroutine的。都可以用来实现 iterator, generator。既然是 coroutine,自然是 context switch。

不过,我还记得 cookoo 的 python vs ruby 帖子,里面说,ruby yield 和 python yield 不是一回事,python yield 是用来产生 generator的。

看了这个帖子上面的讨论,发现所有语言的yield 原来还都是一回事。都是用来表示 coroutine。
想想也是。yield 这个词也不是随便用的。都已经约定俗成表示coroutine了。

-------------------------

yield的大致含义:

yield 基本等于 return。

用在一串顺序指令中间。

yield 1;
yield 2;
yield 3;

需要注意的是,
return 1;
return 2;
return 3;

return这种写法是无法通过的。
yield 就可以。因为 yield 允许在顺序指令中主动中断。
yield 还同时保存了当前的 runtime stack & context。所以,是可以重入的。下次context切换回来,stack/context 就恢复了。可以继续执行。

yield 的用途就是用来 产生序列,一串顺序的yield可以构造一个发生器/generator/ iterator。

所以,在发生器方面,ruby yield 和 python yield / c# yield 的含义是相同的。

所不同的方面是接受器方面。ruby yield 的接受器方面要求一个 block 来接受 yield 产生的每一步序列。

这是一个 iterator lazy fetch 的过程。

receiver  执行 next, 切换到 generator。
generator 执行 yield 切换到 receiver。
这样一步一步合作执行。

关于 coroutine, yield。参见这个帖子的回复
http://www.iteye.com/topic/21293

buaawhl 写道

关于coroutine, yield, iterator, ajoo有些文章和代码可以作为很好的参考。

http://jroller.com/page/ajoo?entry=yield_support_in_java
这个blog讲述了基本思想。

http://www.sourceforge.net/projects/jfunctional
这个项目的代码包括了一个 yield package,是 yield coroutine iterator的实现。

-------

另外, c#支持coroutine yield iterator。
我找到了一篇CLR关于这方面支持的ppt文档,有兴趣可以参阅。

http://research.microsoft.com/workshops/SSCLI2005/presentations/Ierusalimschy-Mascarenhas.ppt


还有原贴

buaawhl 写道

聪明的人们把目光转向了Coroutine。
Coroutine本来是一个通用的概念。表示几个协同工作的程序。
比如,消费者/生产者,你走几步,我走几步;下棋对弈,你一步我一步。
由于协同工作的程序通常只有2个,而且这两个程序交换的数据通常只有一个。于是人们就很容易想到用Coroutine来实现Iterator。
这里面Iterator就是Coroutine里面的生产者Producer角色,数据提供者。所以,也叫做Generator。
每次Iterator程序就是等在那里,一旦用户(消费者Consumer角色)调用了iterator.next, Iterator就继续向下执行一步,然后把当前遇到的内部数据的Node放到一个消费者用户能够看到的公用的缓冲区(比如,直接放到消费者线程栈里面的局部变量)里面,然后自己就停下来(wait)。然后消费者用户就从缓冲区里面获得了那个Node。
这样Iterator就可以自顾自地进行递归运算,不需要自己管理一个栈,而是迫使计算机帮助它分配和管理运行栈。于是就实现了幸福得像花儿一样,简单得像Visitor一样的梦想。

比如,这样一段代码,遍历一棵二叉树。
public class TreeWalker : Coroutine {
private TreeNode _tree;
public TreeWalker(TreeNode tree) { _tree = tree; }
protected override Execute() {
Walk(_tree);
}
private void Walk(TreeNode tree) {
if (tree != null) {
Walk(tree.Left);
Yield(tree);
Walk(tree.Right);
}
}
}

其中的Yield指令是关键。意思是,首先把当前Node甩到用户的数据空间,然后自己暂停运行(类似于java的thread yield方法或者object.wait方法),把自己当前运行的线程挂起来,这样虚拟机就为自己保存了当前的运行栈(context)。
有人说,这不就是continuation吗?
对。只是Coroutine这里多了一个产生并传递数据的动作。
实现原理如果用Java Thread表示大概就是这样。当然下面的代码只是一个示意。网上有具体的Java Coroutine实现,具体代码我也没有看过,想来实现原理大致如此。

public class TreeIterator implements Iterator{
TreeWalker walker;
// start the walker thread ..
Object next(){
walker.notify();
// wait for a while so that walker can continue run
return walker.currentNode;
}
}

class TreeWalker implements Runnable{
TreeNode currentNode;

TreeWarker(root){
currentNode = root;
} void run(){
walk(curentNode);
}

private void Walk(TreeNode tree) {
if (tree != null) {
Walk(tree.Left);
currentNode = tree;
this.wait();
Walk(tree.Right);
}
}
}

我们看到,Iterator本身是一个Thread,用户也是一个Thread。Iterator Thread把数据传递给User Thread。
说实话,我宁可自己维护一个Stack,也不愿意引入Coroutine这类Thread Control的方式来实现Iterator。
总结

8 楼 jack 2006-10-31  
charon 写道
qiezi 写道
context切换是肯定要的,因为反过来就不自然嘛,你写的代码你根本不知道运行在何种context下,你得去猜测那种context下有些什么东东。

不过这里是用例子证明了这一样,但我还是愿意反过来想:如果不切换context,那么block所处的context将是不自然的、不完整的,所以ruby开发者设计成需要context切换。。。似乎所有语言中类似的玩意都是这么设计的,可以想像成其它语言的委托。


所以,有人用yield也能实现类似微线程之类的东西。


超微线程,yield的确有这样的含义存在,和普通线程切换不同,就是一个主动,一个被动切换。能够用yield完成这个,的确也够强的。
7 楼 jack 2006-10-31  
qiezi 写道
context切换是肯定要的,因为反过来就不自然嘛,你写的代码你根本不知道运行在何种context下,你得去猜测那种context下有些什么东东。

不过这里是用例子证明了这一样,但我还是愿意反过来想:如果不切换context,那么block所处的context将是不自然的、不完整的,所以ruby开发者设计成需要context切换。。。似乎所有语言中类似的玩意都是这么设计的,可以想像成其它语言的委托。


所以yield就是一个说明什么时候切换context的关键字,call block那是接下来的事情了.
6 楼 charon 2006-10-31  
qiezi 写道
context切换是肯定要的,因为反过来就不自然嘛,你写的代码你根本不知道运行在何种context下,你得去猜测那种context下有些什么东东。

不过这里是用例子证明了这一样,但我还是愿意反过来想:如果不切换context,那么block所处的context将是不自然的、不完整的,所以ruby开发者设计成需要context切换。。。似乎所有语言中类似的玩意都是这么设计的,可以想像成其它语言的委托。


所以,有人用yield也能实现类似微线程之类的东西。
5 楼 qiezi 2006-10-31  
context切换是肯定要的,因为反过来就不自然嘛,你写的代码你根本不知道运行在何种context下,你得去猜测那种context下有些什么东东。

不过这里是用例子证明了这一样,但我还是愿意反过来想:如果不切换context,那么block所处的context将是不自然的、不完整的,所以ruby开发者设计成需要context切换。。。似乎所有语言中类似的玩意都是这么设计的,可以想像成其它语言的委托。
4 楼 jack 2006-10-31  
cookoo 写道
test0和test1在一个类里,test1当然知道test0。 你如果在test0里puts self就会看见输出的是B的实例。所以yield运行时动态切换context的,不是宏那样静态替换掉的。

BTW, 你竟然用了code框却不缩进?哼哼。另外有个手误:putc

哈哈,看到你这样说,看来最后我的理解应该没有错.关键还是在于切换context. block中的代码才能顺利运行.
还真像thread的切换,不过这里的切换是主动引起的.

手误已改 谢谢.


3 楼 cookoo 2006-10-31  
test0和test1在一个类里,test1当然知道test0。 你如果在test0里puts self就会看见输出的是B的实例。所以yield运行时动态切换context的,不是宏那样静态替换掉的。

BTW, 你竟然用了code框却不缩进?哼哼。另外有个手误:putc
2 楼 jack 2006-10-31  
qiezi 写道


你可以把省略的&block参数理解为你说的隐藏的this,但本质上和c++的this有区别的,ruby的self才和c++的this相似。


那么block中定义在class B中的函数test0,运行时,是怎么找到test0的函数体,然后正确调用的呢?
1 楼 qiezi 2006-10-31  
想得太复杂了吧,yield应该是简化block语法用的:
class A
  def self.test(&block)
    block.call
  end
end


class A
  def self.test
    yield
  end
end

是一样的。这样看不是很明白了嘛。

你可以把省略的&block参数理解为你说的隐藏的this,但本质上和c++的this有区别的,ruby的self才和c++的this相似。

相关推荐

    Pb中Yield()函数的使用[文].pdf

    Pb 中 Yield() 函数的使用 Yield() 函数是 PowerBuilder 中一个非常有用的函数,它可以将控制权转移给其他图形对象,包括非 PowerBuilder 对象。该函数检测消息队列,如果有消息,就把消息取出。该函数返回布尔型值...

    yield表达式.pdf

    Python中的yield表达式是一个极为重要的概念,它是生成器(generator)的核心组成部分。生成器在处理大量数据时显得尤为有用,因为它可以逐个产生数据项而不是一次性加载所有数据到内存中。这不仅可以节省内存,还能...

    java 线程让步(Yield)

    Java线程让步(Yield)是Java多线程编程中的一个重要概念,它涉及到线程调度和并发控制。在多线程环境下,线程让步意味着一个正在运行的线程主动放弃当前的时间片,以便其他就绪状态的线程有机会执行。这与线程的...

    yield:yield是Riot.js v4的补充补丁,缺少一些使用在Riot.js v3中

    pkg yield yield是Riot.js v4的补充补丁,缺少在Riot.js v3使用&lt;yield&gt;实现的某些功能。 安装 对于npm用户, $ npm install @creatorqsf/yield 对于纱线使用者 $ yarn add @creatorqsf/yield 用法 在注册防暴...

    haxe-yield:用于Haxe的跨平台类似C#的`yield`生成器

    任何@yield表达式可用于以:yield元数据注释的类,或者可用于所有扩展以:yield(Extend)注释的类的类。 @ :yield class MyClass { // ... } 以下示例显示了yield元数据的两种形式: @ yield return expression ; @...

    使用C# yield关键字来提高性能和可读性

    使用C# yield关键字来提高性能和可读性 C# 中的 yield 关键字可以提高代码的性能和可读性。yield 关键字可以让开发者使用惰性枚举(Lazy Enumeration),从而减少不必要的内存分配和数据处理。下面我们将详细介绍 ...

    AEC-Q002B1:2012 Guidelines for Statistical Yield Analysis - 完整英文

    《AEC-Q002B1:2012 Guidelines for Statistical Yield Analysis》是一份针对半导体行业统计产量分析的重要指导文档。这份文档详尽地阐述了如何在2012年的背景下,运用统计方法对生产过程中的良品率进行评估和优化。...

    Python库 | ffmpeg_progress_yield-0.1.2-py2.py3-none-any.whl

    Python库`ffmpeg_progress_yield`是用于处理多媒体文件的工具,特别是在视频和音频处理方面。它是一个基于Python的接口,能够与FFmpeg命令行工具进行交互,从而为用户提供更方便、更高级别的API来操作多媒体数据。`...

    Python yield的用法实例分析

    本文实例讲述了Python yield的用法。分享给大家供大家参考,具体如下: yield的英文单词意思是生产,刚接触Python的时候感到非常困惑,一直没弄明白yield的用法。 只是粗略的知道yield可以用来为一个函数返回值塞...

    yield总结与实例

    二、yield是一个语法糖,为方便开发者提供的一种方便方法 三、yield返回类型为IEnumerator、IEnumerable、IEnumerator、IEnumerable 四、如果返回类型为IEnumerator编译时会实现一个实现了IEnumerator接口的类 五、...

    JS_Yield:JS yield原始学习-js

    JavaScript中的`yield`关键字是Generator函数的核心特性,用于在异步操作中实现暂停和恢复执行。Generator函数是ES6引入的一种新的函数类型,它解决了回调地狱的问题,为异步编程提供了一种更优雅的方式。 ...

    Unity3D教程:后台资源加载与yield用法2

    Unity3D教程:后台资源加载与yield用法2 Unity3D教程:后台资源加载与yield用法2是 Unity3D开发中一个重要的知识点,涉及到后台资源加载和yield的使用。本文将详细介绍yield的核心功能和使用方法,以及在 Unity3D...

    基础算法-python打印杨辉三角

    yield b #使用yield a, b =b, a + b n = n + 1 N = int (input("请输入生成行数N: ")) def yanghui_triangles(): a = [1] while True: yield a #执行到yield b 时,fab函数返回一个迭代值 a = [sum(i) for i ...

    python异步编程 使用yield from过程解析

    `yield from`语句是Python 3.3引入的新特性,它在异步编程中扮演着重要角色,主要用于简化生成器(generator)的使用,尤其是处理嵌套生成器的情况。 首先,让我们理解`yield from`的基本作用。在Python中,生成器...

    Python 生成器,迭代,yield关键字,send()传参给yield语句操作示例

    本文实例讲述了Python 生成器,迭代,yield关键字,send()传参给yield语句操作。分享给大家供大家参考,具体如下: demo.py(生成器,yield关键字): # 生成器是一个特殊的迭代器。可以用for...in遍历。 # 带有...

    C#中Task.Yield的用途深入讲解

    C#中Task.Yield的用途深入讲解 Task.Yield是C#中的一个重要概念,它可以帮助开发者更好地管理线程资源,提高程序的性能和可维护性。本文将深入讲解Task.Yield的用途,通过示例代码和详细的解释,帮助读者更好地理解...

    yield curve modelling

    ### 收益曲线建模(Yield Curve Modeling) #### 知识点一:收益曲线背景 收益曲线(Yield Curve)是一种重要的金融工具,它显示了不同期限的债券收益率与其到期时间之间的关系。通常情况下,收益曲线是按照债券的...

    C#中yield return用法分析

    本文实例讲述了C#中yield return用法,并且对比了使用yield return与不使用yield return的情况,以便读者更好的进行理解。具体如下: yield关键字用于遍历循环中,yield return用于返回IEnumerable,yield break用于...

    Python yield 使用浅析

    初学 Python 的开发者经常会发现很多 Python 函数中用到了 yield 关键字,然而,带有 yield 的函数执行流程却和普通函数不一样,yield 到底用来做什么,为什么要设计 yield ?本文将由浅入深地讲解 yield 的概念和...

Global site tag (gtag.js) - Google Analytics