CoffeeScriptが1.9でgenerator構文をサポート

  • 69
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

追記: タイトル変更。v1.9 でリリースされました(2015/01/30)

ES6以降にやや慎重な対応をみせるcoffeescriptですが、やっとgenerator構文がサポートされたようです。

Add yield support · Issue #3073 · jashkenas/coffeescript · GitHub

色々と構文の候補がありましたが、関数ブロックの中にyieldキーワードが存在する場合は自動的にジェネレーター関数になるような仕様に落ち着いたみたいです。

generator概要(知ってる人は読み飛ばしてよい)

関数ブロックの中でyieldを使うと関数がgenerator化されます。yield化された関数は実行されるとgeneratorを返し、
generatorは.next()を叩くと次のyieldキーワードで渡された値が取得できます。もう一度叩くとその位置から次のyieldキーワード、またはreturnまで実行されます。

これを利用して、たとえば計算状態を保存したまま次のフィボナッチ数を返す関数を作ったりできるわけです。

応用として非同期関数を見た目上同期的に受け取ったりも出来ます(coの例を参照)

基本的な挙動としては、JavaScriptのgeneratorはPythonのgeneratorそのまま移植した感じです。

実行環境

今回は node v0.11 と coffeescriptの開発版で動かすとします

# node v0.11系をいれる
nodebrew install-binary 0.11
nodebrew use 0.11
npm install jashkenas/coffeescript -g # master の HEADをいれる

# 実行
coffee --nodejs --harmony main.coffee

yiledキーワードで関数をgenerator化

x = ->
  yield 0
  yield 1
  yield 2

gen = x() # generatorを生成
console.log gen.next()
console.log gen.next()
console.log gen.next()

結果

{ value: 0, done: false }
{ value: 1, done: false }
{ value: 2, done: false }
{ value: undefined, done: true }

yield式自体はundefinedと評価されているっぽいですね(最後のyieldが暗黙のreturnされてる)
とはいえ普通のユースケースを考えるとdo式使ってこう書く感じになりそう

x = do ->
  yield 0
  yield 1
console.log x.next().value
console.log x.next().value

yield from

あるgeneratorが別のgeneratorに依存するすることもできます

x = do ->
  yield from do ->
    for i in [3..4]
      yield i

イマイチ使いどころ

coで同期的に実行する

coを使うと非同期関数の呼び出しを見た目上同期的に書くことができます!!!
この例はPromiseを使います。(coの使用例見る限りピュアなcallbackとかもサポートしているみたいですが)

co = require 'co'

# sleep :: Int -> Promise Int
sleep = (ms) -> new Promise (done) ->
  setTimeout (-> done ms), ms

# serialize
do co ->
  console.log 'sleep 1'
  a = yield sleep(1000)
  console.log 'sleep 2'
  b = sleep(500)
  console.log 'sleep 3'
  console.log a, b

僕はこれがやりたいがためにgeneratorを待ってたといっても過言ではないです。

ところでgeneratorっていつ使えるの

Nodeは実行環境を限定できるので(オプション付きですが)使えるようになるでしょう
Firefoxは実は昔からサポートしていて、Chromeも開発チャンネルだと有効化されてるんでしたっけ。node-webkitやAtom Shell、Chrome拡張, Firefox拡張だと動作環境を限定できるのでとくに問題無いです。

generator使うにしろ使わないにしろ、非同期部分をPromise化しておくと嬉しいはずです。

で、書いたあとに気づいたけど http://qiita.com/pomutemu/items/3e0c02aa4391f38a2901 の記事があったけど勿体無いので貼っとく