LiveScriptでfLatなasyncコードをいじってみる (LL/ML Advent Calendar 5th day)
Posted: Updated:
LL/ML Advent Calendar
#LLAdventJP の参加意思確認をお願いします! @dico_leque @peccul @shin_semiya @ahomu @natsutan @k6ky @issm @clairvy @miguse @nagakenjs partake.in/events/9658f37…
— ぶれいろふ (@bleis) November 26, 2012
_人人人人人人人人_ > 突然のなごや <  ̄^Y^Y^Y^Y^Y^Y^Y^ ̄
何の前触れもなく、リストに指名してくるなごやすごい。(僕の知ってる名古屋ではない)
わてくしWeb系の平均的なJSerです。指名されたからには書くだけ書いてみます。(`・ω・´ )
平たいasyncふたたび
ということで、LiveScriptでfLatなasyncコードをいじってみる。
LLまたはMLが入ってれば良いらしいので、先日の大なごやJSで少し調べて話したLiveScriptの、平たいasyncについて改めて見てみたいと思う。ちなみに、このテキストを書いている時点で、ひねり出したこのお題がちゃんと着地できるか不安。:;(∩´﹏`∩);:
LiveScript
LiveScript is a language which compiles to JavaScript. It has a straightforward mapping to JavaScript and allows you to write expressive code devoid of repetitive boilerplate. While LiveScript adds many features to assist in functional style programming, it also has many improvements for object oriented and imperative programming. LiveScript - a language which compiles to JavaScript
CoffeeScript → coco → LiveScriptという順でfork進化したaltJSです。元の文法に加えて、prelude.lsというリスト操作APIのライブラリをあわせて使うことで、なんとなくHaskellっぽくなるという特徴を持ちます。
平たいasync
LiveScriptにおいて、平たい(fLat)なasyncとして紹介したのは下記のようなコードです。
data <-! $.get 'ajaxtest' $ \.result .html data processed <-! $.get 'ajaxprocess', data $ \.result .append processed
これが...
$.get('ajaxtest', function(data){ $('.result').html(data); $.get('ajaxprocess', data, function(processed){ $('.result').append(processed); }); });
このようにコンパイルされて展開されます。
正体
この処理の正体は単純に、->
なCallbackに対してのBackcallと呼ばれる<-
が正体です。なお、<-!
はreturn
を抑制します。!->
も同様。何もしないと最後の式の返り値をreturn
します(CoffeeScriptと同様)。
コールバック地獄に挑む
コールバックを浅くできるというのであれば、安直に有名なコールバック地獄に挑んでみます。
# ファイルを読み込む err, files <-! fs.readdir source if err then return console.log 'Error finding files: ' + err # ファイルリストを回す filename, fileIndex <-! files.forEach console.log filename # ファイルサイズを取得 err, values <-! gm source + filename .size if err then return console.log 'Error identifying file size: ' + err console.log filename + ' : ' + values that = @ aspect = (values.width / values.height) # widthリストを回す width, widthIndex <-! widths.forEach height = Match.round width / aspect console.log 'resizing ' + filename + 'to ' + height + 'x' + height # リサイズして保存 err <-! that.resize width, height .write destination + 'w' + width + '_' + filename if err then return console.log('Error writing file: ' + err)
、、、平たく書けました。キモい。いや、元がJSの非同期コードと分かってるからキモいであって、PHPとかの同期処理な関数使ってたら普通にこうなりますよね。うん。
長くなってしまうので、オリジナルのソースコードや、コンパイル後アウトプットは、LiveScriptでCallback Hellに挑む — Gistを別途ご覧ください。
使いどころの検討
このBackcall自体の使い所として、
# ファイルを読み込む err, files <-! fs.readdir source if err then return console.log 'Error finding files: ' + err
これは良いのですが、
widths.forEach (width, width Index)-> height = Match.round width / aspect console.log 'resizing ' + filename + 'to ' + height + 'x' + height
のがループの部分スコープが明確で良いように感じました。うーん、目の慣れの問題でしょうか?そもそもループ処理自体も、パイプ(|>
)してもっと格好良く処理できるのかもしれません。
カジュアルにdo
?
カジュアルにBackcallするぜ!と思っても、文法的な限界というか、制約があります。一度Backcallが出現すると、その先同じインデントレベルのスコープ内はBackcallに取り込まれます。それだとさすがに不自由なので、利用できるのがdo
ですが...
# do + backcall do data <-! $.get 'ajaxtest' $ \.result .html data # normal $.get 'ajaxtest' !(data)-> $ \.result .html data
1段くらいだとあんまり恩恵を感じませんね…。コールバック地獄のような極端なケースはともかく、普通にクライアントサイドJavaScriptを書いて、適切なコールバックの設計ができていれば、言うほど劇的な変化はなさそうです。
まあ、ネストを減らすことが目的なんじゃなくて、コールバックを伴う処理の捉え方が変わるという面のほうが重要なように思います。これはこれで何か問題になるわけではありませんし、Backcallの価値を損ねてるということにはならないと思います。
do
はたぶんIO in Haskellにあるようなdoをイメージしているとはずなので、これで十分に正しいんではないでしょーか。(?)
無念
サンプルコードをチラ見した時点での、最初の妄想では下記のような変換が可能になるのかと期待してました。
data <-! $.get 'ajaxtest' ... $ \#aaa .html data $ \#bbb .html data
たとえばこんなコードが...
// これは誤った変換結果(妄想)です $.get('ajaxtest', function(data){ $('#aaa').html(data); }); $.get('ajaxtest', function(data){ $('#bbb').html(data); });
ってなるみたいな妄想をしていたのですが…
$.get('ajaxtest', function(data){ $('#bbb').html(data); $('#aaa').html(data); });
こっちが現実でした(´・ω・`) デスヨネー。
data
というBackcallが何らかの形でポイポイ使い回せたら面白かったな〜とか。うーんうんうん。
Conclusion
素直にすごいH本読んできますね!!!
何事も半端はよくないな、と悶々とするオチでした。JavaScriptなら非同期どーこーをスマートにするのは、Promise実装とかasync.jsで良いとおもいます(´ω` )