Λlisue's blog

つれづれなるままに更新されないブログ

CoffeeScriptをまじめにかんがえてみる01

こんにちわ、有末です。某ゲーム製作者コミュニティの元幹部で、サイトの開発の指揮とったりしていました。そのKawazの現代表であるgiginet君にステマされたのではてブロを書くことにしました(笑。

最初の記事として、僕の大好きな言語CoffeeScriptについて真面目に考えてみたいと思います。まぁ絶対一回じゃ終わらないので番号振ってるのでまともに記事が揃うまで気長にお待ちください。

CoffeeScripttってなに?

さて、どんな人がここを読むかわからないのでまずはCoffeeScriptってなに?から始めます。

CoffeeScriptとは、一言で行ってしまえばJavaScriptのシンタックスシュガーです。CoffeeScriptはコンパイルされることにより完全に同等なJavaScriptへとコンバートされます。

なんだか無駄なことをしてるみたいですよね、わざわざ新しい表現方法を覚えて既存の方法にコンバートするなんて…でも、それなりの理由というのがあるのです。

JavaScriptの欠点

さて、コンバートという無駄なステップを踏むということは、それに見合っただけの見返りが存在するということです。それを理解するにはまずはJavaScriptの欠点を理解する必要があります。

JavaScriptの欠点なんて星の数ほどあるんですが、まぁ主に以下のようなことでしょう。

  • var 問題
  • セミコロン処理の問題
  • == の問題
  • 外部ファイルのインポート構文が存在していない

それぞれについて少し細かく見ていきます。なお"use strict"に関してはここでは無視しています

var 問題

JavaScriptでは一般的に変数を定義する場合はvarをつけます。しかし、これはあくまでプログラマの間での暗黙の了解であり、言語系としてはvarが存在しなくても変数の定義は可能です。

// 暗黙の了解による通常の変数定義
var foo = "foo";
// でも、以下のように定義もできる
bar = "bar";

わざわざvarなんて打たなくても定義できるなら、なぜvarをつけるのが標準になっているのでしょうか? まぁここを読んでいる人にはいう必要もないとは思いますが、グローバル変数として定義されてしまうからですよね。

グローバル変数として定義されることの何が問題なのかよくわからない人は下記コードをじっくりとご覧ください。

// myFunctionを{foo: 'foo', bar: 'bar'}という引数で呼ぶ関数
function callMyFunction() {
    args = {
        foo: 'foo',
        bar: 'bar'
    };
    myFunction(args);
}

// 関数を呼ぶための引数を保存しておく
args = {hoge: 'hoge'};
// 保存した引数でmyFunctionを呼ぶ
myFunction(args);
// 別の呼び方をする必要があるのでcallMyFunctionを使う
callMyFunction()
// もう一度先に保存した引数でmyFunctionを呼びたかったのだが…
myFunction(args);   // 実際には{foo: 'foo', 'bar: 'bar'}で呼ばれる

このように意図しない上書きや変数名の衝突などが起こるのでJavaScriptを正しく書く人は絶対にvarをつけます。 でも付けなくてもエラーが発生しないため気が付かないこともしばしば……

セミコロンの問題

JavaScriptでは行末にセミコロンがなくてもエラーになりません。しかし、セミコロンが存在しないJavaScriptは動いたり動かなかったりと不安定な挙動を示すことが多いです(ミニファイするとたいてい動かなくなる)。

エラーにはなりませんが、実は内部としてはJavaScriptはセミコロンを必要としています。セミコロンが存在しない行のパースを行うと内部的にはエラーが発生し勝手に適当な場所にセミコロンを挿入します。なのでセミコロンが存在しなくてもエラーにはなりませんが、意図したものと違う挙動になることも多々あります。

これもvarと同様に絶対につけるべきですが、エラーが発生しないためにつけ忘れが頻発する部分です。

==の問題

JavaScriptをまともに書ける人はまず使わないのが==です。JavaScript==型を勝手に変更します。 恐ろしい仕様ですが、仕様なので仕方ありません。普通の感覚の人は迷わず===を使用してください。

まず絶対に使わない==ですが、構文としては正しいのでエラーが出ません。これもまた(ry

外部ファイルインポートの構文が存在していない

node.js を始めとするCommonJSではrequireという機能がありますが、ブラウザ上で動くJavaScriptにはそんなハイカラなものはありません。 一応動的にJavaScriptを読み込む方法はありますが、速度および総ファイルサイズの問題などから大規模な場合を除いてあまり使用しません。

その代わりに、JavaScriptでは最終産物のファイルを結合して一つにしてしまうことが多いです。有名なjQueryなんかもこの方法をとっています。 ただし、手作業でやっていたのでは日が暮れるのでMakefileなどに自動化処理を記述して行うことになります。

CoffeeScriptで解決できる部分

本当はもっとJavaScriptに対して日頃の恨みを晴らしてもいいのですが、このへんで勘弁してやりましょう。 悪い言語ではないのですが、けっして書きやすい言語でもありません。ヒューマンエラーを気づかせない機能が満載過ぎます。

じゃぁCoffeeScriptはどうなんだよって?先にあげたクリティカルで迷惑な問題を全て解決できますよ!

var問題

CoffeeScriptでは、そもそもvarなんてものは存在しません。なかったことにされています。 宣言された変数はすべてJavaScript変換時にvarが付けられローカル変数として定義されます。 どうしてもグローバル変数を定義したい場合はwindow.globalVariable = 'foobar'のように windowなどを明示的に指定する必要があります。

ちなみに補足ですがCoffeeScriptではすべての関数が変数関数として定義されます。これは JavaScriptの定義関数(って呼び方であってたっけ?)が抱える問題を変数関数にするだけで ほとんど解決可能だからです。このへんの話は気が向いたらします。

セミコロン問題

CoffeeScriptではセミコロンもなかったことにされています(笑。コンパイル時に自動的に適切な位置に付加されるので プログラマはセミコロンで頭を悩ます必要もありません!

ちなみに補足ですがCoffeeScriptではリスト最後のコロンも正しく処理してくれます。このおかげで憎きIEさんでのくだらないエラーが激減します。以下参照

# このCoffeeScriptが・・・
books = ['Book A', 'Book B', 'Book C',]
# こんなJavaScriptに変換されます。最後要素のコロンが除かれてることに注意
var books = ['Book A', 'Book B', 'Book C'];

==の問題

これもなかったことにされています。CoffeeScript上で==と書くと、それはJavaScriptでは===と表現されることになります。 またCoffeeScriptではより自然言語に近づけるためにisisntなどで等号・不等号を表すこともできます。

# CoffeeScript
if "hello" == "hello"
# JavaScript
if ("hello" === "hello") {

# CoffeeScript
if "hello" is "hello"
# JavaScript
if ("hello" === "hello") {

# CoffeeScript
if "hello" isnt "morning"
# JavaScript
if ("hello" !== "morning") {

外部ファイルインポートの構文が存在していない

これに関しては解決するわけではありませんが、CoffeeScriptのコンパイラは最初からファイルをつなげる機能が存在しています。 また次回以降で書く予定のあるツールを使うとより簡単に(依存関係のある)ファイルをつなげることができます。

とりあえず最後!

とりあえず今日はここまで!CoffeeScriptの魅力が全然書けなかったので次回もたぶんCoffeeScriptのステマになると思います(笑。 まじめにCoffeeScriptを考えるのはまだ先の話になりそうです(笑