この記事はAizu Advent Calendar 2015 @ababup1192の記事です。
innocentyknrさん <- 前の人 ・ 次の人 -> masapontoさん
記事の内容
AltJSの一種であるScala.jsをオススメする記事です。以下の点を軸として紹介をしていきます。ScalaとScala.jsの魅力を語り続ける記事なので、環境構築資料としては役に立たないと思います。
- Webフロントエンド開発の現状と環境
- 言語の強み
- Scala.jsを使ったモダンな開発
Scala.jsとは
名称から分かる通りAltJSの一種でScalaからJavaScriptへと変換するものです。Scala.jsの公式サイトを見ると非常に丁寧に、他言語との比較・チュートリアル・使用可能なライブラリ・コミュニティへのリンクなどが説明されています。初心者の方でも簡単にScala.jsを始められると思います。また、某日本語のScala.js紹介サイトを見ると非常に怪しいですが、確かなインパクトを得れると思います。
Webフロントエンドの重要性
Web1.0の時代から流れを知っていると、Webの在り方や多様性ってだいぶ変わってきましたよね。当初は、HTMLはWeb上で共有するための統一フォーマットを持った文書、JavaScriptはHTMLを操作することに特化した言語だったはずが、今や多彩なコンテンツを表現するための機能性に優れたプラットフォーム、様々な現場(ブラウザプラグイン・サーバーサイド・組み込み・ゲームエンジンプラグインなど)で使われる汎用言語となっています。私の最近の衝撃はマルチプラットフォームなGUIフレームワークのElectron (旧Atom-Shell)でしょうか。Webはブラウザから解き放たれて(実のところChromiumですが・・・)非常に便利なアプリケーションプラットフォームになったと言えるでしょう。
Webフロントエンド開発環境の複雑化
Webが出来ることが増えることは非常に喜ばしいことですが、開発の規模が大きくなるにつれ開発環境が重要になってきます。以前はブラウザとメモ帳で充分!だったWebフロントエンドの開発環境ですが今はそうは行きません。思いつく限りでは以下のことを考慮しなければなりません。
- シンタックスハイライトや補完機能を備えたエディタ
- Node.jsによる
- ファイル監視
- モジュール管理
- テンプレートエンジン・AltJS・AltCSSの変換
- Minify・難読化
- ホットリロード
- タスクランナー
- (TypeScript型定義ファイルの管理)
もちろん全てのことを揃える必要はありませんが、快適な開発環境を求めるならば、上に挙げた項目以上に努力が必要だと思われます。一度構成を作ってしまえば大丈夫だと思われがちですが、Web開発の流れは非常に早く1年に2,3回は新しい技術が出ていて構成を変えざるを得ない(もちろん追従は必須ではないですが)状況が出てきてしまいます。私は既にタスクランナーについては、Grunt->Gulp->Webpack。AltJSではCoffee->TypeScript、他にも細かい環境変化を体験しており非常に辛いと感じました。環境構築に関しては最新情報とベストプラクティスの追従、個々人の開発目的の差異を埋めるコストが大きすぎると思いました。環境構築は必須ではありますが、やりたいことの本質ではないのでここで力を消費し過ぎるのは体力的、精神的、時間的にも勿体無いと個人的には思います。
Scalaの開発環境
前置きが長くなりましたが、Scala.jsの環境構築の話に移りたいと思います。まずはじめに
、Scalaを経験したことがない読者も多くいると思いますのでScalaの開発環境について話したいと思います。ズバリ言うとScalaの開発ツールに関しては以下の3つを導入すれば大方済みます。
- Scalaコンパイラ(REPL同梱)
- Typesafe Activator
- お好みのエディタ or IntelliJ IDEA
Scalaコンパイラはご自分の環境のパッケージマネージャや公式ページから落としてお使いください。ScalaコンパイラはREPLを備えているので(今時備えてない言語のほうが少ないですよね・・・)文法チェックや学習の際にお使いください。とにかくコンパイル時間が遅いことで有名ですが、Scalaのいくつかの主要ではない機能を簡略化したdottyというコンパイラ(プラットフォーム?)がブートストラップ可能になったらしいので、興味ある方は触ってみると良いでしょう(私はまだ触れていません)。
Typesafe Activatorは、Scalaを開発リリースしているTypesafe社から出ているTypesafe Reactive Platformです。要するに開発環境キットのようなものです。Typesafe Activatorは以下のものを備えています。
- sbt
- Activator UI
- Scala開発テンプレート
sbtはScala用のビルドツールで、設定ファイルはbuild.sbtファイルに記述し、コンソールでいくつかの命令をタイプすることで、ビルド・テスト・パッケージングなどが行えます。設定ファイル自体もScalaで記述するので、文法やエラーチェックは厳密に行ってくれます。設定ファイルやプロジェクトのディレクトリ構成は一から用意する必要はなく、activatorがコンソール上で対話的に行ってくれます。sbtが優れている点は細かいタスクを自分で記述する必要はほぼ無く、目的にあったプラグインをimportするだけで必要なタスクは自動生成してくれます。
$ activator new
Fetching the latest list of templates...
Browse the list of templates: http://typesafe.com/activator/templates
Choose from these featured templates or enter a template name:
1) minimal-akka-java-seed
2) minimal-akka-scala-seed
3) minimal-java
4) minimal-scala
5) play-java
6) play-scala
(hit tab to see a list of all templates)
> 4
Enter a name for your application (just press enter for 'minimal-scala')
> test
OK, application "test" is being created using the "minimal-scala" template.
.
├── LICENSE
├── activator
├── activator-launch-1.3.2.jar
├── build.sbt
├── project
│ └── build.properties
└── src
├── main
│ └── scala
│ └── com
│ └── example
│ └── Hello.scala
└── test
└── scala
└── HelloSpec.scala
name := """test"""
version := "1.0"
scalaVersion := "2.11.7"
// Change this to another test framework if you prefer
libraryDependencies += "org.scalatest" %% "scalatest" % "2.2.4" % "test"
コマンドが覚えきれないときはActivator UIを使うとブラウザ上でマウスのみを使って指示を出すことが出来ます。アプリケーションのsbtテンプレートが非常に豊富で、やりたいことのテンプレートは大体存在します。最低限の環境を整えたいときはSeeds、サンプルを見ながら開発したい場合はTutorialsからテンプレートを落としてくると良いでしょう。
Activator UIから行えるタスクは非常に豊富で、ビルド・コーディング・実行・テスト・なんとサーバのモニタリングまでが出来てしまいます。ここまで公式のサポートが厚いと開発意欲が湧いてきます。
開発環境の最後になりましたが、Scalaを使ってアプリケーションをコーディングする際にはIDE (統合開発環境)を強くオススメします。この記事ではお伝えし切れないほど多くの利点がありますが、私が大きく便利だと思う点は以下の点です。
- コードの補完
- 型エラー
- コード修正(Java->Scalaの変換)
- 変数・メソッド参照
- Local History
多くのIDEやテキストエディタはコードを補完する機能を持ちあわせていますが、IntelliJ IDEAのコード補完はかなり強力です。Scalaは静的型付け言語で、コンパイル時に既存の型を暗黙的に新しいクラスに変換しメソッドを拡張するImplicit Classesという機能があります。この機能は乱用すると非常に危険なためシングルトンオブジェクトに包み、使用したいスコープのみにimportして使用することができます。IntelliJ IDEAはスコープ単位でのImplicit Classesに対応して、適切にメソッドの補完と型情報を表示してくれます。以下のスクリーンショットはRubyのtimesメソッドを再現したものです。Helpersオブジェクトがimportされた範囲のみでキチンとtimesメソッドの補完が出ていることがわかります。
型のエラーも同様にImplicit Classesなどの暗黙の型変換や複雑な型構造に関するエラーまで、IntelliJ IDEAは警告してくれます(もちろん漏れはありますが)。
コーディング時により良い書き方(実行速度ではなくコーディングの行数などが目安)がある場合にはIntelliJ IDEAが警告を出して修正をしてくれます。例えば1から100の数字を畳み込み演算などで書いている場合には、sumメソッドに変換をしてくれます。IntelliJ IDEAを使用しているだけで効率的な書き方や正しい文法が覚えれるので使わない手はないと思います。機械的ではありますがJavaのファイルやコピペコードなどをScalaのコードに変換してくれる機能も備えています。
(1 to 100).foldLeft(0){_ + _}
// ↓以下のようにIntelliJ IDEAが修正
(1 to 100).sum
変数やメソッドなどに特定のボタン(コマンドやコントロール)を押しながらカーソルを合わせるとメソッドの説明が出るだけでなく、クリックすると定義部までジャンプしてくれる機能があります。これは外部ライブラリとして参照している場合もジャンプすることができます。また、逆に定義したメソッドや変数が使われている部分を逆検索する機能があります。古い自分のコードや他人のコードを閲覧するときには、この2つが必須です。
IDEの説明の最後になりましたが、皆さんはGitなどでソースコードを管理するときに、こまめにコミットをするようにしていると思われますが、コミットからコミットまでの範囲内でソースコードの変更が追いきれなくなってしまうケースがあるかと思います(もちろん、そうならないためのソースコード管理や設計だと思いますが人間なのでミスはあります)。IntelliJ IDEAはソースコードは自動的にセーブされ自身にローカルな差分履歴を持ち続けます。非常に細かい単位なのでうっかり、間違った変更をしてしまった場合は落ち着きながら差分履歴を見ながら状態を戻すことできます。手探りでソースコードを編集しているときには、非常に便利な機能です。
以上がScalaをコーディングする上で必要な開発ツールと説明になります。ユーザが開発に必要なものをあれこれカスタマイズしやすい、ということは非常に大切だと思いますが、自由すぎる中で良い環境を用意するのは難しと思います。Scalaは公式でベストな環境を提供し続ける姿勢を怠らないので、ユーザは開発だけに集中し続けれると思います。
Scala.jsの開発環境
Scalaの開発環境を抑えたところで、本番のScala.jsの開発環境構築について話したいと思います。ただ、先ほど説明した通り標準で開発するための道具は揃っています。Scala.jsの場合でも例外ではありません。ActivatorでScala.jsのサンプルを持ってきてしまう方法もありますが、ミニマルな環境を作るときはActivatorでScalaのプロジェクトテンプレートを生成します。次にprojectディレクトリにplugins.sbtを用意して一行追加します。
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.5")
続いてbuild.sbtにプラグインを適用する一行を追加します。
enablePlugins(ScalaJSPlugin)
name := "Scala.js Tutorial"
scalaVersion := "2.11.7"
ベースは以上になります。難しいタスク記述などは必要ありません。プラグインを適用するだけでScala.jsを始めれます。sbt上でそのまま書いたプログラムを実行することは推奨されておらず、Node.js上で実行することが推奨されています。その方法は以下のようにするとできます。
> set scalaJSStage in Global := FastOptStage
> run
このままパッケージングし.jsファイルを生成し、バックエンドのNode.jsやフロントエンドのHTMLに直接組み込むことも出来ます。JSExportアノテーションを利用して、特定のメソッドのみをjsとして外出しする何てことも可能です。いきなりScalaを利用してフルコーディングするということが厳しいという方は、小さな関数をScalaで書くところから初めてはどうでしょうか。
@JSExport
def addClickedMessage(): Unit = {
appendPar(document.body, "You clicked the button!")
}
ただ、実際にはブラウザを利用してホットリロードを利用しながら開発することが多いと思います。その場合にはWebフレームワーク(Play・Scalatra・Skinny)とScala.jsを併用することで実現が出来ます。個人的にはドキュメントや色んな人が既に実践済みであるPlayを利用することをオススメします(play-with-scalajs-example)。こちらもsbtプラグイン(sbt-play-scalajs)が用意されていて、インポート文と多少の設定拡張で環境を整える事ができます。併用環境では多くの場合、ソースコードは以下の様なディレクトリ構成になっています。
- client
- server
- shared
clientはフロントエンド(Scala.js)、serverはバックエンド(Play・Scalatra・Skinny)、sharedはフロントエンドとバックエンドで共有するデータ型などを置いておく事ができます。全てScalaで言語統一できるメリットに加えてデータ型をライブラリなどを外出しすることなく、簡単に共有できるのは非常に便利だと思います。
以上でScala.jsの環境構築の説明を終わります。以下に簡単にScalaを使って環境構築する利点をまとめておきます。
- Scalaは公式で環境のベストプラクティスを提供し続けている
- sbtの拡張はScalaで記述でき、多くの拡張はプラグインをインポートするだけで済む
- IDEを利用することでScalaの性能を十二分に引き出したコーディングができる
- WebフレームワークとScala.jsの併用で快適なフロントエンド開発環境が用意できる
もちろん細かい問題はいくつか努力して解決することになりますが、多くの開発環境を整えるという辛さからは解放されると思います。アプリケーション作りはロジックに集中することがより良いものを作るための条件なので、楽できるところは楽してしまいましょう。
言語の強み
ここからはScala言語の強みを紹介していきたいと思います。ただしこの記事ではScalaの言語の魅力を語り切れないので、TypeScriptには無い機能をいくつか紹介します。動的型付けであるJavaScript(ES5やES6)と比べると大きく違いが出てしまい過ぎるためTypeScriptとの比較とさせていただきます。
コンパニオンオブジェクト
オブジェクト指向を利用したプログラミングで非常に良くあるケースとして、あるクラスのインスタンスを一つに制限したいときはシングルトンパターンを実装すると思います。(参考: TypeScriptでSingletonパターンを実装する)
class Singleton {
private static _instance:Singleton = null;
constructor() {
if(Singleton._instance){
throw new Error("must use the getInstance.");
}
Singleton._instance = this;
}
public static getInstance():Singleton {
if(Singleton._instance === null) {
Singleton._instance = new Singleton();
}
return Singleton._instance;
}
}
Scalaではobjectキーワードを一つ書くだけでシングルトンオブジェクトを作ることが出来てしまいます。これは言語としてサポートしているか、していないかの違いだけでしょうか? いいえ違います。staticキーワード(言語によって意味は異なるかもしれませんが大体)を使うことによって純粋なオブジェクト指向は壊れてしまいます。Scalaではstaticキーワードを完全に排除し、シングルトンオブジェクトを徹底することで純粋なオブジェクト指向を保っています。Rubyも純粋なオブジェクト指向を保っていて、モジュールのミックスインでSingletonに出来ることは有名ですね。
object Singleton
シングルトンオブジェクトを便利に使う仕組みは、これだけではありません。Scalaにはコンパニオンオブジェクトという、同名のシングルトンオブジェクトとクラスを定義できるという仕組みがあります。コンパニオンオブジェクトは互いのprivateメンバにアクセスすることが出来ます。以下の例ではHogeクラスのコンストラクタをプライベートにしています。理由はインスタンスの生成を隠蔽し、ファクトリメソッドを実装するためです。Scalaではファクトリメソッド(applyメソッド)をシングルトンオブジェクトに任せるのが通例となっています。実際ファクトリメソッドを介して、Hogeクラスのインスタンスを生成しているところを見てみましょう。mainメソッドがちょうど、それを行っています。mainメソッドではnewキーワードを使わずしてHogeクラスのインスタンスを生成しています。これはapplyメソッドは呼び出しが省略可能であるというScalaの規則があるため、このようなことが出来ます(Hoge.apply(5)と明示することももちろん可能)。これはインスタンス化がリテラル操作のように見えるというScalaの配慮からなっています(例えば、線形リストの生成は List(1, 2, 3, 4) のように出来る)。Scalaは単純に純粋なオブジェクト指向を保つためだけにstaticキーワードを排除したわけではなく、それを補って余りある利点を出すために、このような言語体系をしています。クラスの構造とそれを生成するメソッドを上手に切り分けし、管理や拡張がしやすいという利点も得ています。
class Hoge private(val x: Int)
object Hoge{
def apply(x: Int): Hoge = {
new Hoge(x * 2)
}
}
object Main{
def main(args: Array[String]) = {
println(Hoge(5).x) // => 10
}
}
case class
次も良くあるパターンとして、いわゆるエンティティを定義するの例です。
TypeScriptではコンストラクタを以下のように定義すれば、メンバ変数firstNameとlastNameが自動的に初期化され、publicを付けることで各メンバのgetterも自動生成されます。TypeScriptでは、コンソール上でインスタンスを表示した場合には、オブジェクトの形で出力されてしまうため、toStringという表示のためのメソッドを別に定義しています。
class Person {
constructor(public firstName: string, public lastName: string) {}
toString() {
return `Person(${this.firstName}, ${this.lastName}`);
}
}
Scalaではcase classを使うとエンティティ定義するとき一行で以下のように書くことが出来ます。case classがしてくれることは、実はそれだけではありません。
case class Person(firstName: String, lastName: String)
new Person("first", "last") => Person(first, last)
先ほど説明したコンパニオンオブジェクトとファクトリメソッド(apply)メソッド、各フィールドの比較をし等価性を確かめるequalsメソッドのオーバーライド、ディープコピーを行うcopyメソッドなどの便利なメソッドを自動的に生成してくれます。この手の良く使うメソッドを外部ライブラリやアノテーションを利用せずに生成してくれるのは非常に嬉しく、時間と労力の節約になりますね。
Person("first", "last") == Person("first", "last")
パターンマッチ
先ほどのcase classの本領は便利メソッドの自動生成だけではありません。柔軟なパターンマッチ式に当てはめられることが出来るのです。Scalaのパターンマッチは非常に強力で単なる値による分岐だけではなく、型・クラス構造による分岐。クラス構造から値を抽出することも出来ます。さらにパターンマッチは式であり評価して値を返すことができます。下のような単純な例ではパターンマッチの真価を紹介しきれませんが、今回は簡単な例とさせていただきます。
def isTargetPerson(person: Person): Boolean =
person match{
Person("John", "Smith") => true // John Smithのときtrue
Person("John", _) => true // 名前がJohnのときtrue
Person(_, "Smith") => true // 苗字がSmithのときtrue
Person(first, _) if first.length > 5 => true // 名前が5文字より大きければtrue
_ => false // その他はfalse
}
}
「言語の強み」の最後になりますが、Scalaの強力な言語機能のごく一部ですが堪能できたでしょうか。Scalaは学習コストが掛かる言語だと思いますし、私自身もScalaを触り続けてはいますが到底使いこなせてはいません。しかし学習コスト以上に見返りは充分にあり、基本構文を抑えるだけで実用言語として大きな力を発揮します。奥が深い機能ばかりなので是非皆さん入門してScalaの力強さを感じてください。
Scala.jsを使ったモダンな開発
最後の大項目になりましたが、Scala.jsでモダンなWeb開発は可能なのでしょうか? 私自身Scala.jsを使用するに当たって熟考したテーマです。一部の機能(システムを直接操作するようなもの、Java言語の正規表現機能1)が制限されているものの、Scalaには大きな「言語の強み」があります。しかしWebに特化したJavaScriptの強力なモダンライブラリが使えなければ、あまり意味はありません。ですがご安心ください、Scala.jsの開発はとても活発で多くのScala.jsのためのライブラリが用意されています。今回は特に開発が活発なscalajs-reactを紹介したいと思います。
scalajs-reactのGithubの紹介文には以下の一文が書かれています。FacebookのReactをただ移植するわけではなく、Scala流にやりやすく正統進化させたライブラリだと言えます。
Lifts Facebook's React library into Scala.js and endeavours to make it as type-safe and Scala-friendly as possible.
折角なのでJavaScript・TypeScript・Scala.jsで実装した場合のReactのHelloWorldを順に紹介していきます。
この例ではpropsとしてname変数を受け取り、divタグを生成するコンポーネントを定義しています。その後、exampleのidを持ったタグを指定しレンダリングしています。括弧のネストが多くなってしまって少し複雑である、ということと変数nameの型を指定できないので型安全ではありません。
var Hello = React.createClass({
render: function() {
return (
<div>Hello {props.name}</div>
);
}
});
ReactDOM.render(
<Hello name="John" />,
document.getElementById('example')
);
TypeScriptではname変数にStringという型が付いているので、もし別な型が渡された場合はエディタやコンパイラがエラーを出してくれますね。
/// <reference path="../typings/react/react.d.ts"/>
/// <reference path="../typings/react/react-dom.d.ts"/>
import * as React from 'react'
import * as ReactDom from 'react-dom'
class Hello extends React.Component<{ name: string; }, {}> {
render() {
return (
<div>Hello {this.props.name}</div>
)
}
}
ReactDom.render(
<Hello name='John' />,
document.getElementById('example')
);
scalajs-reactではコンポーネントの型パラメータにStringを指定することで、型の制約を設けています。また、プレフィックス(<.)を使い内部DSLとしてjsxを表現しています。単なる構文ではなくメソッド表記によるDSLなのでSyntax errorを出してくれることも魅力の一つです。型に考慮しつつ、とてもスマートにReactコードを記述出来るところが気に入っています。
val Hello =
ReactComponentB[String]("Hello <name>")
.render(name => <.div(s"Hello $name")
.build
ReactDOM.render(Hello("John"), document.getElementById("example"))
上の例ではよく伝わらないと思いますが、再利用可能で状態に左右されないコンポーネントを組み合わせていくReactコーディングスタイルは関数型とオブジェクト指向をサポートしているScalaにとっても相性が良いです(それを表現しているscalajs-reactの質がとても高い)。
scalajs-reactを利用したライブラリやサンプルがいくつかありますが、個人的にはscalajs-reactを勉強するときには、TodoMVCサンプルがとても参考になります。このサンプルを参考に私が学習用に作ったサンプルもここに載せておきます。良ければ覗いてみてください。
最後に
本投稿では3つの点から私がWebフロントエンド開発の言語としてScala.jsを選択した理由をつらつらと語らせていただきました。決して色物のAltJSでは無いことが伝わっていただければ幸いです。お悩みの方がいれば、まずScalaという言語から触れてみることをオススメします。Scala.jsという素晴らしい技術が広まることを切に願っています。
-
Scala.jsには別途、jsの正規表現を利用するのためのクラスRegExpが用意されています。 ↩