淺談Ruby里的生成器特性

在老東家寫(xiě)了一段時(shí)間Python后,我被告知Python里面有個(gè)很經(jīng)典的被稱之為生成器的特性,而這個(gè)特性至今讓我難以忘懷。后來(lái)我接觸了Ruby,我就納悶了Ruby怎么就沒(méi)看到這個(gè)特性?

直到最近在讀《松本行弘的程序員世界》的時(shí)候我才意識(shí)到我錯(cuò)了,這個(gè)特性早在Ruby1.9就已經(jīng)具備,只不過(guò)是我資歷尚淺,察覺(jué)不到它的存在罷了。

松本行弘

1. Python的yield語(yǔ)句可用于構(gòu)造生成器

下面是一個(gè)簡(jiǎn)單的Python生成器例子,它的功能是遍歷0-max范圍內(nèi)的所有數(shù)字

def range(max):
    n = 0
    while n < max:
        yield n
        n += 1

if __name__ == '__main__':
    for i in range(100):
        print(i)

PS: 結(jié)果就是打印0~99這里就不貼出來(lái)了。

有沒(méi)有感覺(jué)這個(gè)函數(shù)很有梯度感?(這是我放棄Python的理由之一 )T_T。如果我們直接在REPL環(huán)境里面運(yùn)行range函數(shù),會(huì)得到下面的結(jié)果

In [8]: range(100)
Out[8]: <generator object range at 0x102f41460>

從字面上可以看出它是一個(gè)generator,也就是我們所說(shuō)的生成器。

當(dāng)然,我們也可以直接構(gòu)造一個(gè)數(shù)組,通過(guò)遍歷數(shù)組來(lái)完成以上的打印過(guò)程。但生成器的好處就是它是惰性的,也就是說(shuō)當(dāng)你訪問(wèn)某個(gè)元素的時(shí)候?qū)?yīng)元素才會(huì)被生成,而不是先生成一個(gè)完整的數(shù)組,然后我們?cè)偃ケ闅v它。

2. Ruby中的yield

現(xiàn)在我們看看Ruby的yield語(yǔ)句是用來(lái)做什么的,舉個(gè)簡(jiǎn)單的例子就能夠看出來(lái)

def handler(a)
  yield(a)
end

result = handler(100) { |i| i * 20 }

puts result

程序的運(yùn)行結(jié)果是

> 2000

簡(jiǎn)單來(lái)說(shuō),Ruby里面的yield可以用來(lái)接收方法塊,調(diào)用它就相當(dāng)于調(diào)用方法塊。上面的例子里我們的方法塊是{ |i| i * 20 },它接收一個(gè)參數(shù)i并返回i * 20的值,結(jié)果就是 100 * 20 = 2000

3. Ruby中的生成器

那問(wèn)題來(lái)了,yield這個(gè)語(yǔ)句已經(jīng)被用作方法塊的代理了,那我們?cè)赗uby里面要怎么實(shí)現(xiàn)如Python般可以返回生成器的函數(shù)?還是說(shuō)Ruby中壓根沒(méi)有生成器這種機(jī)制?

怎么可能呢?其實(shí)生成器就在我們身邊,只不過(guò)我們很多時(shí)候都察覺(jué)不到它的存在,具體我們可以參考一下這篇文章。在Ruby1.9之后就實(shí)現(xiàn)了Enumerator這個(gè)類,它的機(jī)制跟Python里面的生成器類似,可以用作惰性求值。使用方式如下

> a = (1..100000000000000000).each
=> #<Enumerator: 1..100000000000000000:each>
> a.next
=> 1
> a.next
=> 2
> a.next
=> 3
> a.next
=> 4
> a.next
=> 5

有點(diǎn)編程經(jīng)驗(yàn)的朋友都知道,我們不怎么可能活著看到我們的計(jì)算機(jī)生成長(zhǎng)度為100000000000000000的數(shù)組,所以這里面一定有什么黑魔法T_T。

本質(zhì)上它是通過(guò)Range#each方法返回了Enumerator的對(duì)象,我們可以通過(guò)依次調(diào)用Enumerator#next方法來(lái)獲取下一個(gè)元素,而這個(gè)Enumerator的對(duì)象就相當(dāng)于一個(gè)生成器。

4. 用Ruby實(shí)現(xiàn)可以返回生成器的函數(shù)

?
Ruby1.9之后Ruby提供了名為Fiber的類,可以用于構(gòu)建輕量級(jí)的協(xié)程,它讓我們可以手動(dòng)地去控制我們的線程是否被喚醒。那么這個(gè)玩意到底有什么用呢?畢竟,我們現(xiàn)在來(lái)談協(xié)程這些多線程的東西似乎有點(diǎn)早。

下面是一個(gè)簡(jiǎn)單的例子

fiber = Fiber.new do
  Fiber.yield 1
  2
end

puts fiber.resume
puts fiber.resume
puts fiber.resume

可見(jiàn)Fiber::yield的作用跟Python中的yield語(yǔ)法有點(diǎn)類似。這里我們通過(guò)Fiber#new語(yǔ)句來(lái)創(chuàng)建Fiber實(shí)例,并傳遞一個(gè)代碼塊。我們可以把Fiber::yield想象成類似于byebug或者binding.pry等調(diào)試語(yǔ)句,程序運(yùn)行到這里就會(huì)暫停,然后把控制權(quán)讓出去。

當(dāng)我們第一次運(yùn)行Fiber#resume方法的時(shí)候就會(huì)返回Fiber::yield語(yǔ)句后面的數(shù)值,而第二次執(zhí)行Fiber#resume就會(huì)繼續(xù)運(yùn)行代碼塊余下的代碼并返回2,第三次運(yùn)行Fiber#resume的時(shí)候由于我們塊已經(jīng)執(zhí)行完畢所以會(huì)報(bào)錯(cuò)

> fiber.resume
=> 1
> fiber.resume
=> 2
> fiber.resume
FiberError: dead fiber called
    from (irb):7:in `resume'
    from (irb):7

感覺(jué)這個(gè)Fiber類就是我們要找的生成器的類。下面是我YY出來(lái)的用Ruby的生成器特性來(lái)實(shí)現(xiàn)的函數(shù),它的功能類似文章開(kāi)頭用Python實(shí)現(xiàn)的函數(shù)。

def range(n)
  Fiber.new do
    i = 0
    while i < n
      Fiber.yield i
      i += 1
    end
  end
end

fiber = range(10)

p fiber.resume
p fiber.resume
p fiber.resume
p fiber.resume

上面腳本的輸出是

0
1
2
3

在Python里面含有yield語(yǔ)句的方法被調(diào)用之后會(huì)返回一個(gè)生成器對(duì)象,然后我們可以利用這個(gè)生成器來(lái)進(jìn)行惰性求值(個(gè)人感覺(jué)這種語(yǔ)法不夠清爽,沒(méi)有一個(gè)顯式的返回語(yǔ)句,把生成器對(duì)象返回)。另外,Python的版本里使用了for語(yǔ)句來(lái)迭代生成器,我這里只是簡(jiǎn)單地手動(dòng)去迭代。

Ruby的range方法返回了一個(gè)Fiber的對(duì)象,它就相當(dāng)于一個(gè)生成器(或者也可以說(shuō)是一個(gè)外部的迭代器),通過(guò)反復(fù)調(diào)用Fiber#resume方法就可以獲取下一個(gè)值,如果反復(fù)調(diào)用超出限制就會(huì)報(bào)錯(cuò)。我Fiber#resume10次之后就會(huì)得到下面的結(jié)果。

dead fiber called (FiberError)

5. 總結(jié)

瞎扯了一下Ruby的生成器特性。作為一個(gè)特性,它并不像Python那么明顯,但是它確實(shí)存在著。得益于Fiber這種類庫(kù)的出現(xiàn),已經(jīng)更多Ruby多線程相關(guān)的庫(kù)被開(kāi)發(fā)出來(lái)了。據(jù)說(shuō)Fiber性能方面的表現(xiàn)比Ruby原來(lái)內(nèi)置的Thread要好一些,具體的比較我想放在后面的文章再詳細(xì)討論,很感謝你能夠看到這里。

Happy Coding and Writing !!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 生成器(Generator)可以說(shuō)是在 ES2015 中最為強(qiáng)悍的一個(gè)新特性,因?yàn)樯善魇巧婕暗?ECMAScri...
    Will_Wen_Gunn閱讀 5,059評(píng)論 0 9
  • 我們?cè)趯W(xué)習(xí)web前端的路程起步時(shí)總是疑問(wèn),我們?nèi)绾胃玫谋闅v元素呢?迭代器和生成器是什么?今天為大家?guī)吓c精彩的E...
    儂姝沁兒閱讀 3,511評(píng)論 0 6
  • 一、異同對(duì)比選擇1、Python和ruby的相同點(diǎn): * 都強(qiáng)調(diào)語(yǔ)法簡(jiǎn)單,都具有更一般的表達(dá)方式。python是縮...
    沃倫蓋茨閱讀 4,296評(píng)論 2 24
  • 1. 迭代器協(xié)議 由于生成器自動(dòng)實(shí)現(xiàn)了迭代器協(xié)議,而迭代器協(xié)議對(duì)很多人來(lái)說(shuō),也是一個(gè)較為抽象的概念。所以,為了更好...
    KavinDotG閱讀 379評(píng)論 0 1
  • 娘子羞羞答答, 衙內(nèi)撞上心抓。 如此這般如此, 險(xiǎn)遭不測(cè)被殺。
    草原騎手閱讀 315評(píng)論 0 0

友情鏈接更多精彩內(nèi)容