CoffeeScriptからBabel(ES6)へ乗り換えは早とちり?CoffeeScript2の概要

無名関数・クラス・テンプレート文字列・ジェネレータ関数・Destructuring・デフォルト引数・・・

CoffeeScriptで先行して導入されていた便利な機能が大幅に導入されたECMAScript2015(ES2015/ES6)

そしてそのES6をES5に変換して互換性を吸収し古いブラウザで使うための6to5トランスパイラであるBabelの普及。

2014年付近のCoffeeScriptの開発の停滞が指摘される中、CoffeeScriptからES6に乗り換えを検討している方は多いのではないでしょうか。

確かにCoffeeScriptで先行していた上記の機能はES6でもう実装されていますし、さらに次世代のES7においてはasync/awaitというCoffeeScriptの変換だけでは対応できなさそうな便利な機能まで採用されています。

CoffeeScriptはES6に橋渡しの役目を終えて消える運命なのでしょうか!?

ブラケット地獄には戻りたくない

}}})})}}

JavaScriptでよく見る表記です。ここからブロックを一つ抜くとなるとどれを抜いたらいいのかわかりません。

結局高機能になったES6においてもインデントブロックを採用していない以上、上の様なブラケットの連続地獄は避けられません。

また、短く書くのが正義のCoffeeScripterにとっては行末尾のセミコロンも非常に苦痛です(書かなくても動作はしますが)

結局トランスパイルするなら同じ

インデントとブラケット。改行とセミコロン。

どうして一つの意味を表現するにに2回もタイプしなければいけないのでしょうか。

プログラマの一生でこのオーバーヘッドを積み重ねると膨大な時間になるのは容易に想像できます。

その時間を他のクリエイティブな時間に使えれば、それは寿命が延びたのと同じくらい価値があることではないでしょうか。

結局ES6も現状Babelでトライスパイルする以上、結果は同じはずです。

CoffeeScript2

2017年現在、CoffeeScriptのGitHubに「2」というブランチができています。

そう、これがCoffeeScript2。

停滞していたかと思われたCoffeeScriptがここにきて大きな進化を遂げようとしています。

その正体はCoffeeScript->ES6トランスパイラ。つまりそこからさらにBabelでES5に落とし込むことが主な運用となるでしょう。

ここではその概要を紹介させていただきます。開発中の資料によるものですので、変更になる可能性、また、すでにv1にマージされているものもありますのでご注意を。

クラス

CoffeeScript1においてはクラスと継承はメソッドとメンバのコピーとしてやや複雑な方法で実現されていましたが、CoffeeScript2ではネイティブサポートされたES6のクラスに変換されます。

class Snake extends Animal
  move: ->
    alert "Slithering..."
    super 5
Snake = class Snake extends Animal {
  move() {
    alert("Slithering...");
    return super.move(5);
  }

};

CoffeeScript1の変換結果(下記)と比べると随分すっきりとしたのがわかります。

Snake = (function(superClass) {
  extend(Snake, superClass);

  function Snake() {
    return Snake.__super__.constructor.apply(this, arguments);
  }

  Snake.prototype.move = function() {
    alert("Slithering...");
    return Snake.__super__.move.call(this, 5);
  };

  return Snake;

})(Animal);

Staticメソッドもサポートしています。

class Teenager
    @say: (speech) ->
Teenager = class Teenager {
  static say(speech) {

デフォルト引数

CoffeeScript2でのデフォルト引数はそのままES6のデフォルト引数に変換されます。

fill = (container, liquid = "coffee") ->
fill = function(container, liquid = "coffee") {

振る舞いの違いとして、上記例をCoffeeScript1でfill(null, null)と呼ぶとliquidには”coffee”が入りますが、CoffeeScript2ではliquid=nullとなります。

テンプレート文字列(CoffeeScript1.10.0よりサポート)

CoffeeScript2のテンプレート文字列もES6のテンプレート文字列に変換されます。

sentence = "#{ 22 / 7 } is a decent approximation of π"
sentence = `${22 / 7} is a decent approximation of π`;

ES6ではテンプレート文字列にバッククォートとドラーが使用されていますが、CoffeeScript2ではCoffeeScript1と同じくシャープを使った構文がそのまま継承されています。

ジェネレータ関数(CoffeeScript1.9.0よりサポート)

perfectSquares = ->
  num = 0
  loop
    num += 1
    yield num * num
  return

window.ps or= perfectSquares()
perfectSquares = function*() {
  var num;
  num = 0;
  while (true) {
    num += 1;
    yield num * num;
  }
};

window.ps || (window.ps = perfectSquares());

forループでジェネレータをiterateする場合はfor..from構文が使われます。ES6のfor..of相当ですが、もともとCoffeeScriptではfor..ofがオブジェクトメンバの列挙に使われていたので、互換性維持の為に名前が違います。

fibonacci = ->
  [previous, current] = [1, 1]
  loop
    [previous, current] = [current, previous + current]
    yield current
  return

getFibonacciNumbers = (length) ->
  results = [1]
  for n from fibonacci()
    results.push n
    break if results.length is length
  results

なお、ジェネレータ関数内ではBound(=>)は使えない制約があります。

async/await

ES7の重要な拡張機能async/waitへの対応です。ES7だとasyncを関数につける必要があるのですがCoffeeScript2では自動的に判断されるのもポイントです。

sleep = (ms) ->
  new Promise (resolve) ->
    window.setTimeout resolve, ms

countdown = (seconds) ->
  for i in [seconds..1]
    await sleep 1000 # wait one second
sleep = function(ms) {
  return new Promise(function(resolve) {
    return window.setTimeout(resolve, ms);
  });
};
countdown = async function(seconds) {
  var i, j, ref;
  for (i = j = ref = seconds; ref <= 1 ? j <= 1 : j >= 1; i = ref <= 1 ? ++j : --j) {
    await sleep(1000);
  }
};
​

モジュール(CoffeeScript1.12.1よりサポート)

ES6のimport構文がサポートされます。

import 'local-file.coffee'
import 'coffee-script'

import _ from 'underscore'
import * as underscore from 'underscore'

import { now } from 'underscore'
import { now as currentTimestamp } from 'underscore'
import { first, last } from 'underscore'
import utilityBelt, { each } from 'underscore'

Splats…

そのままES6構文に変換されます。

awardMedals = (first, second, others...) ->
awardMedals = function(first, second, ...others) {

Destructuring

Destructuring代入はES6でサポートが加わりましたが、CoffeeScript2の変換は分割代入のままです。

[theBait, theSwitch] = [1000, 0]
theBait = 1000;
theSwitch = 0;

ES6風の名前付きパラメータがサポートされています(CoffeeScript1.10.0より)

function r({x, y, w = 10, h = 10}) {
  return x + y + w + h;
}
r({x:1, y:2}) === 23

対応なし

無名関数についてもES6でサポートが加わっていますが、CoffeeScript2においてはdestructuringと同じく、それらを使わない変換がされます。もともと糖衣構文的な性格が強い機能なので、特に必要性がないのでしょう。

square = (x) -> x * x
square = function(x) {
  return x * x;
};

CoffeeScriptは不滅

ES2015の機能追加で役目を終えたかのように見えたCoffeeScript。ここへ来て、その立ち位置を探っているようにも見えます。

CoffeeScript2->ES2015(ES7)->ES5の運用が主になるのか、はたまた一部の短記法信者だけのカルトな言語になるのか。

世の中の動きは神様にしかわかりませんが、CoffeeScript2により少なくとも我々、短記法信者の貴重な人生の時間がつまらないセミコロンとブラケットのタイプに浪費される心配はしばらくはなさそうです。

ヾ(*ΦωΦ)ノ

カナシスコム > 節約テクノロジ > CoffeeScriptからBabel(ES6)へ乗り換えは早とちり?CoffeeScript2の概要