LiveScriptでfLatなasyncコードをいじってみる (LL/ML Advent Calendar 5th day)

LL/ML Advent Calendar

_人人人人人人人人_  
> 突然のなごや <
 ̄^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 → cocoLiveScriptという順で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で良いとおもいます(´ω` )