糖衣がけのMoonScriptはいかが?

MoonScriptが気に入ったので、構文について色々書き散らしたいと思う。解説記事とか入門記事じゃないよ。文法知りたい人はリファレンス読みましょう。 (MoonScript v0.2.2 - Language Guide)

MoonScriptとは

Luaにコンパイルするスクリプト言語。現時点での最新バージョンは0.2.2。

MoonScriptは、JavaScriptにコンパイルするCoffeeScriptにかなり似ている。(もともとJavaScriptLuaが似た言語だしね。)CoffeeScriptを知っていれば、MoonScriptも何となく分かると思うけど、JavaScriptLuaの相違点が、CoffeeScriptとMoonScriptの違いに表れている。

インデントによるブロック(レイアウト)

PythonHaskell、CoffeeScriptでお馴染みのレイアウト。最初にPythonで見たときはびっくりしたよ。色々な役割があるけど、閉じ括弧地獄が始末されるなど、色々な場面で活躍したりしなかったりします。

関数リテラル

CoffeeScript譲りの関数記法。

-- MoonScript
-> f()
-- Lua
function()
  return f()
end

ただ->とタイプするだけで関数になるので、かなり中毒性がある記法だと思う。今までいやいやfunction() return endって打ってきてうんざりしていたところが、これだけでもう無名関数いくらでも作れるよって気になるでしょ?

do 式

基本的にLuaの明示的なブロックだが、式なので代入したり式中の部分式として使える。

括弧の省略

-- MoonScript
f a, b, c
f a, g b, c
f a, b,
  g c, d,
  h e, f
-- Lua
f(a, b, c)
f(a, g(b, c))
f(a, b, g(c, d), h(e, f))

こんなふうに解釈される。
次のようには書けないのかな? と思ったけど、無理でした。

-- MoonScriptでは通らない
-- f(a, b, c)のつもり
f
  a
  b
  c

-- f(g(a, b), h(c, d))のつもり
f
  g a, b
  h c, d

! 演算子

-- MoonScript
f!
-- Lua
f()

MoonScriptは関数を呼び出す際に括弧を省略できる。しかし、引数を取らない関数の場合は、何も書かないと関数自体への参照になってしまう。そんなときは!演算子を使うと、括弧を省略できるよ!という意欲的な(?)機能。でも省略した文字数はたったの1文字なので、存在意義は微妙。好意的に取れば、引数を取らない関数ということは、何らかの副作用を期待している場合、あるいは遅延評価で値を評価させる場合だろうから、ビックリマーク(bang)を使うってのは合っている気もする。でももっといい記号の使い方を考えたほうがいい気がするなー。

テーブルリテラル

-- MoonScript
scrollbar = Mx.ScrollBar
  adjustment: Mx.Adjustment
    lower: 0, upper: 10
    page_increment: 1, page_size: 1
-- Lua
local scrollbar = Mx.ScrollBar({
  adjustment = Mx.Adjustment({
    lower = 0,
    upper = 10,
    page_increment = 1,
    page_size = 1
  })
})

括弧が省略できる。

オブジェクト指向

LuaのメソッドはJavaScriptのように、予約語thisがオブジェクトにバインドされるのではない。テーブルに格納されている関数の第一引数にテーブル自身をとることで、メソッドを実装する。

// JavaScript
obj.method(x, y, z);
-- Lua
obj.method(obj, x, y, z)
-- 上のシンタックスシュガー
obj:method(x, y, z)

MoonScriptではシンタックスシュガーの記法が何故かコロンではなくバックスラッシュに変更されている。おそらく、コロンをテーブルリテラルに使っているため。

-- MoonScript
obj\method x, y, z

Function Stubs

第一引数をテーブルにバインドした関数を、簡単に取り出せる。

-- MoonScript
func = obj\method
-- func(...) は obj\method(...) と同じになる
-- Lua
local func = (function()
  local _base_0 = obj
  local _fn_0 = _base_0.method
  return function(...)
    return _fn_0(_base_0, ...)
  end
end)()

ファット・アロー記法

CoffeeScriptのfat arrows記法は、thisを関数の外側のスコープのオブジェクトにバインドする記法だが、MoonScriptのfat arrows記法は単に暗黙の第一引数selfを持つ関数だ。つまり(...) =>(self, ...) ->と同じ。CoffeeScriptとMoonScriptではfat arrowの意味が全く異なることに注意すること。CoffeeScriptではメソッドには->を使うが、MoonScriptではメソッドに=>を使うことになる。

with 式

-- MoonScript
combo = with Mx.ComboBox!
  \append_text 'item'
  .index = 0
  .on_notify.index = =>
    print "selected continent: #{@active_text}"
-- Lua
local combo
do
  local _with_0 = Mx.ComboBox()
  _with_0:append_text('item')
  _with_0.index = 0
  _with_0.on_notify.index = function(self)
    return print("selected continent: " .. tostring(self.active_text))
  end
  combo = _with_0
end

オブジェクトを作るときなど、一つのオブジェクトに対して沢山メソッドを呼んだりプロパティを設定する時に便利。ピリオドかバックスラッシュから始まるのがプロパティやメソッドであることが明らかなので、JavaScriptのwith文のような問題は起こらないし、作ったオブジェクトをそのまま関数に渡すときは余計な変数を宣言せずに済む。

import 文

テーブルから関数やメソッドを抜き出してくる。単にlocalの変数に代入しているだけで、必要ではないが、同じ識別子を二度書かずに済む。

using 宣言

localを省略できるようになった代わりに、間違えて外側のスコープの変数に代入してしまう問題に対処するためなんだろうけど、明示的にlocalを使って書けばいいだけの気もする。スコープの問題なのに関数リテラルの括弧の中に書くのも一貫性がないし、廃止しちゃったほうがいいのでは。LiveScriptみたいに、変数の宣言と代入とで別の演算子を使うようにすればいいんじゃないかな?

欠点

動的型付け言語一般に言えるかもしれないけど、書き間違えたときが大変。そこら辺は実行時エラーメッセージから原因をエスパーする訓練が必要だと思います。

https://gist.github.com/4325247

mxwidgets.lua をMoonScriptに移植、一部改変したもの。

まとめ

  • 括弧を減らすことで、見苦しい閉じ括弧の連続がなくなり、読みやすくなった気がする。
  • シンタックスシュガーはタイプ数を減らすだけでなく、無駄な識別子を減らす働きもある。
  • 全体的に好みだけどミスった時大変かも。
  • MoonScriptは楽しい