Scala使用歴5年のプログラマが、この言語とその環境に関する神話を解き明かす

(注:2016/1/21、頂いたフィードバックをもとに記事を修正いたしました。)

Programming in Scala (Scalaでプログラミング)』の初版を読み始めた(でも読み終えていない)5年前からJavaの代わりにScalaを使うようになりました。最初はテストの時に使用していましたが、すぐにちょっとしたユーティリティクラスでも使用するようになり、気付いたらプロジェクト全てで使用するようになっていました。

Scalaに対する不満は多く存在しますが、この記事は違います。これは非難するものではなく、むしろ称賛するものです。

Scalaに興味ある開発者や聞いたことがあっても詳しく見たことがない人、「スムーズなプログラミングの妨げになる」と思い使用を先送りしていた人のために書きました。もちろんScalaファンに読んでもらうのも、他の人にも紹介してもらうのも大歓迎です。

この記事は3つのパートから構成されています。

  • 私の大好きな「素晴らしい機能」
  • JVMコミュニティでまだ信じられているScalaの神話の解明
  • Scalaを学びたい人へ

では始めましょう!

素晴らしい機能

NullPointerExceptionに遭遇せずに済んだ5年間

正確に言うとほとんど発生しなかったということです。あちらこちらでほんの少しは発生していましたが、私のサーバログと人生を汚染していた悪名高いjava.lang.NullPointerExceptionにつながるスタックトレースの山とは比べものになりません。

NullPointerException
訳:先生?nullPointerExceptionって聞いたことありますか?
(手術室での最近の出来事)

NullPointerExceptionの文字を見ただけでも、私あるいは他のプログラマの間違いを探すために無駄に画面を見つめ続けた記憶が戻るため、気分が悪くなります。人間だからこそ間違いはあり、参照先がnullだったときのことを忘れたとしても、プログラマを責めることはできません。言語はこの不運な出来事が発生しないように、設計そのものによってこれを回避できるようにする責任があります。
NullPointerExceptionを根絶するためのScalaの設計(option型と第一級関数の組み合わせ)について言及するつもりはありませんが、次の簡単なルールさえ守れば、ほぼ確実にScalaのプログラムで2度とNullPointerExceptionを見ることがないことを保証できます。

  • NPE根絶ルールその1:絶対に絶対に絶対に(絶対に!)変数をnull値で初期化しないこと。未定義の状態はOptionでモデル化されるべきである。
  • NPE根絶ルールその2:必ず最初から未定義の場合も考えること。Option型で要注意メソッドのgetを呼び出してごまかさないこと。
  • NPE根絶ルールその3:Javaライブラリを使用する際、多くの場合は警告なしにライブラリがnullを返すことがあるため、Option(unsafeJavaResult)で出される結果をラップすること。

これらのルールを守らずにScalaを使用すれば、無数のNullPointerExceptionに悩まされるのは当然でしょう。

コードの強力な組織化

1つのファイル != 1つのクラス

Scalaでは、1つのファイルに複数のクラスを置くことができます。多くの場合クラスは、Javaのそれに対応する部分よりも桁違いに小さいため、複数のクラスが同じ.scalaファイルで定義されていることはよくあります。反対に複数のクラスを定義しないと何か変な感じですし、時間を無駄にします。Scalaでもパッケージを使うことには変わりありませんが、階層構造はそれほど深くなく、エディタの左側のファイルブラウザもJavaの時のようにファイルでいっぱいになることはありません(ファイルブラウザを使っていればですが)。

このように、複数のクラスを1ファイルにまとめられることは、どのようなエディタを使用していたとしても、面白い結果を生み出します。それは、Scalaでは論理的にまとまった構成物を一つのファイルに入れることができるため、スクロールするだけで容易にそのまとまりの中を行き来できるようになることです。そのため、「ファイルからファイルへとジャンプする」という、最終的にコンテキストスイッチのようになってしまう行為を回避することができるのです。(ご存じのとおり、コンテキストスイッチは悪です)。

縮小、隠ぺい、具体化(SHE)

Reduce
ScalaはJohn MaedaのSHE(shrink縮小、hide隠ぺい、embody具体化)の法則の応用を可能にしてくれます。例えば、次のような関数のネストがあります。

def someComplexFunction(parameter: Parameter) = {
  def theFirstStep = {
    // do something, possibly with parameter
  }
  def anotherStep = {
    // do something else, perhaps with parameter
  }

  theFirstStep + theSecondStep
}

上記は下記の代替方法です。

def someComplexFunction(parameter: Parameter) =
  theFirstStep(parameter) + anotherStep(parameter)

private def theFirstStep(parameter: Parameter) = ...

private def anotherStep(parameter: Parameter) = ...

someComplexFunctionの外では、theFirstStepanotherStep(再利用されるものでありません)を気にする必要がないため、これらでクラスを汚す必要もありません。関数のネストを許すことでコードの複雑性を減らせますし、同時に、1つの関数の中の中間的なステップを文脈的に解釈しやすくできます。言い換えると、全体像の把握に集中したいため、余計なことを考えずにクラスを読めるようにしたいのです。

SHEを実行することは、「小さめ」の関数をクラスの最後の方に持ってくることやprivateを関数の先頭に追加する習慣より遥かに効果的です。なぜなら、この習慣的なやり方は、SHEと違って「コードを文脈的に記述する」という問題を解決できないからです。習慣的方法の場合、ある関数からある関数の関連を確認するのにIDE(統合開発環境)の「どこでこの関数が使用されるのか」機能を探す必要があります。

traitは善のためで悪のためではない

Scalaを取り巻く神話の中に、「複数の継承」を許すことでパンドラの箱を開いてしまい、どんな大きなプロジェクトでもほぼ確実に破滅できるというものあります。実際、Scalaのプロジェクトにおいて、階層構造が深いクラスをほとんど見たことがありません。traitは関連するプログラムを書く時のツールとして使用されます。良い例が/sourceに掲載されています。

trait Controller
  extends Results
  with BodyParsers
  with HttpProtocol
  with Status
  with HeaderNames
  with ContentTypes
  with RequestExtractors
  with Rendering

MVCアプリケーションのコントローラは多くのことに関心を持ちます。上のタイプ(Results以降のもの)はどれもMVCアプリケーションが関心を持つtraitです(実際のソースコードではこれらは全て1行で書かれます。しかし、ここでは見栄えが悪くなってしまうのでリストにしました)。

caseクラス、それともScalaのスイスアーミーナイフ

caseクラスはScala言語のまさに素晴らしい機能だと思います。特にごく普通のJavaオブジェクトを使用していた人にとっては。次はpointクラスをcaseクラスで書き直した例です。

case class Point(x: Int, y: Int, z: Int = 0)

これで、以下をフリーで手に入れました。

  • pointクラス
  • 最低2個のパラメータ使用するコンストラクタ。このコンストラクタはパラメータ名に引数が関連付けられているため、パラメータ名で呼び出すことが可能。
  • x、y、zのgetter
  • hashCodeメソッド
  • equalsメソッド
  • 不変オブジェクトのインスタンスをコピーするメソッド

次のREPL (Read Eval Print Loop)セッションを見てください(REPLについては後ほど説明します)。

scala> case class Point(x: Int, y: Int, z: Int = 0)
defined class Point

scala> Point(x = 1, y = 2)
res0: Point = Point(1,2,0)

scala> res0.copy(z = 42)
res1: Point = Point(1,2,42)

scala> res1.y
res2: Int = 2

不変性が未来の形ですから、caseクラスのメンバは不変です。目が回るぐらい素早くクラスメンバを定義することができます。もちろん、JavaのIDEでgetterやコンストラクタなど、コンピュータのキーを3つ続けて打つだけで生成することができます。しかし、これは気を散らせる雑音でしかなく、解決するべきビジネス問題には集中させてくれません。

さらに

実を言うと、長い間私はJavaでプログラミングをしていません。そのせいもあって、Javaで使用するセミコロンや型推論、タプルやパターンマッチがないといったScalaプログラミングを楽しくしてくれている些細な事実を忘れがちです。

ScalaからJavaに戻った瞬間に最高にイライラさせるものでその心情がうまく表現されています。

Scalaの神話の解明

「ScalaのためのIDEサポートは存在しない」

日常的にScalaを使い始めた時は、簡単な構文のハイライト以上の機能を持つ言語のIDEサポートと言えるものは、IntelliJ IDEAだけでした。古いMacBook Proでは、10分毎にクラッシュし、プレゼンテーションコンパイラは誤判定していました。IDEを10分毎に再起動することに慣れてしまい、「動く壊れたコード」と「壊れているコード」を区別できるようになりました。sbtとScalaコンパイラのみを信頼しました。

今ではだいぶ開発環境は改善されましたが、奇抜なことをしようとしない限り、IDEAがScalaプロジェクトに関しては最良です。EclipseがScala IDEを取り入れたことによって他のIDEでもScalaが取り入れられました。ENISIMEプロジェクトでは、数多くのテキストエディタ(emacs、vim、sublime、atom)がサポートされています。ATOMの良いサポートも提供しています。

「Scalaの機能は複雑なものが多すぎる。開発初心者が習得するには時間がかかる」

Scalaの批判の1つは、言語機能が複雑すぎて理解困難なことです。そのため、Scala開発の初心者はそう簡単には既存のScalaプロジェクトに参加したり作業したりできないとされています。しかし、私はScalaという言語の問題ではなく、プロジェクト担当者のアーキテクチャの選択ミスだと考えています。
最終的に導入する機能と、メンテナンス可能なコードベースを構築すること、これらをバランスよく実現するのは難しく、「技術リーダー」(技術責任者の呼び方は様々だと思います)は同時に複数のことに目を配らなければなりません。私の経験からすると、しゃれたライブラリや風変わりな言語機能の使用を控え、やり方を統一することで、多くの人の関わるプロジェクトで維持しやすいコードベースを開発することが可能になります。しっかりとした言語の理解はもちろんのこと、ツールが「使いやすい」のか、「しゃれている」のか、「変わっている」のか見極められる必要があります。プロジェクトメンバが全員、その言語の初心者であった場合、面白い展開になるでしょう。

技術的な壁はどの言語でもどのプラットフォームでも存在します。Javaでさえも。ロードタイムウェーバやバイトコードの更新、プロキシなどに十分時間をかけることをアノテーションで推奨されていても、初心者にとっては「魔法」を理解するのは難しため、問題に直面することになります。私は今Grailsで書くシステムのプロジェクトに携わっていますが、GROM(GrailsのORM(オブジェクトリレーショナルマッピング))。例えば、Hibernateの上にSpringを、その上にGrailsをマッピング)は多くの人を混乱させています。

Have you seen my Stacktrace?
Grailsとは反対に、実行時に何かを行うことが回避されているため、Scalaプロジェクトは初心者にもやさしいと思います。つまり、コードに間違いがあったとしても、非常に長く訳の分からないスタックトレースを事細かに見る必要はなく、コンパイラに指摘してもらうことができます。

「sbtビルドツールは変な形の矢印ジャングルである」

Scala用ビルドツールのsbtは素晴らしい進化をここ数年で遂げました。かつては、特に依存関係の競合の解決に時間のかかる大きなコードベースのプログラミングで使用した時にイライラさせられていました。幸いなことに、sbtには独自の依存関係解決エンジンがあり、とても悩ましい問題を解消してくれています。

変な形のsbtの構文も改善されました。そのおかげで、今のプロジェクトのビルドファイルでおかしな矢印を見つけることができないため、ここで紹介することもできません。SBTをさらに掘り下げて知りたい場合は、が出版されています。

「Scalaのコンパイラは遅すぎる。XKCDコミックでも馬鹿にされてるほどだ」

ここでこのXKCDコミックを見るつもりはありません。私はMacBook Pro Late 2013に乗り換えてからは、コンパイル時間は問題にならなくなりました。コンパイラ自体が高速化しているし、現在、sbtはインクリメンタルコンパイルをきちんとサポートしています。もちろん今でもJavaよりは遅いとはいえ、開発時の生産性に影響するほどの遅さではありません。最近のJavaプロジェクトの開始時に発生するSpring Beansやら何やら(実はJava事情には疎いのです)をすべて初期化することの方がずっと時間がかかります。

なぜ遅いのか疑問に思う人もいるでしょうが、Scalaコンパイラは、Javaコンパイラよりも、ずっと段階が多いのです。

「オブジェクト指向に関する知識を捨て去る気にはなれない」

Scalaが関数型プログラミング言語であるということは、オブジェクト指向プログラミングの優れた点をすべて破棄すべきであるという意味ではありません。逆に、Scalaは完璧なオブジェクト指向言語なので、オブジェクト指向の優れた点も全て使用することができ、しかも互いに排他的ではありません。

私が見たところ、Scala開発の初心者は関数型プログラミングに夢中になるあまり、開放/閉鎖原則(Open-Closed-Principle)のことを忘れてしまい、object(Scalaでstaticに該当するもの)に関数を押し込んで、そこらじゅうに固定してしまい、コードのテスト性を下げてしまっています。

「まず最初に数学者になってモナドを完全に理解する必要がある」

Monad, mooooonaaaaaad

これは神話です。これに関してはScalaコミュニティの過激な関数型信者を非難すべきです(彼らは善かれと思っているのでしょうが、人々は恐れをなして遠ざかるでしょう)。endofunctorやwhatsonotを理解しようと躍起になってモナドのチュートリアルを次から次へと読むような罠に誘われないでください。あなたが数学の専門家で、モナドにメープルシロップをかけて朝食にしているなら別ですが(噂では本当らしい)。

それでも、真面目に言うと、「カテゴリ型理論」のようなファンシーな響きの用語に恐れをなして逃げるのはやめましょう。Scalaを習得して使いこなせる開発者になるために、このような理論的背景が全て必要なわけではありません。なぜなら、私はこの言語を使い始めてから4年間は、このようなものに煩わされることがなかったからです。しかも、このようなものを扱うようになっても、突然何かがひらめいて、それまでに書いたコードを全部捨てたりはしませんでした。実際には、私は自分が構築して使っているものがどのように呼び出されているのかも知らずに、そこらじゅうでモナドしていたのです。

結局、難しそうなものは単なる用語に過ぎません。

Scalaを学びたい人へ

Scalaを習得しようとするなら、新しい言語を学ぼうとする心構えが必要です。

初期にScalaがJavaコミュニティで受けた批判の大部分は不公平なもので、多くのJava開発者がJava以外の別の言語の習得に煩わされたくなかったからだと思います。彼らは、Java言語の囲いの中で快適に過ごし、その心地よい空間から、危険を冒してまで外に出ることはありませんでした。

「言語の壁」を破るには、この本がとても興味深いと思います。

書籍を手に入れる

Scalaを学び始めたとき、私は最初にProgramming in Scalaの中で使えそうなものを探してみましたが、この本は厚くて少し詳細すぎるので大部分を読み飛ばし、Programming Scalaを読みました。これは私にぴったりでした。

Scalaについては良書がたくさんあります。Scala in ActionProgramming Scalaなどを選んでみてもよいでしょう。

本を1冊読み切るのが面倒なら、いつでもScala by example(PDF版)をダウンロードできます。

プロジェクトを始める

正直に言えば、新しいプログラミング言語を習得するには、かなり努力が必要ですが、小さなプロジェクトのような、もっと高い目標を立てると、ずっと簡単になります。新しい言語(話し言葉)を習得するのと全く同様に、その言語が話されている国に行って暮らせば、ずっと早く習得することができます。

多くの人が、Playフレームワークを使ってプロジェクトを開始した後でScalaを学び始めていますが、驚くべきことではありません。Webフレームワークは新しいプログラミング言語への入口の役割を担うものであり、Playフレームワークも例外ではありません。興味深いことに、PlayにはJavaとScalaの両方のAPIがあるので、目的の言語にゆっくりと移行することができます(ただし、アプリケーションのアーキテクチャを、特にデータベースアクセスに関して正しく構築することが条件です)。

REPLを使う

IDEを使い始める前に、REPL(Read Eval Print Loop)を使ってSclalaを研究すると、いろいろなことが簡単になります。いろいろなWebサイトから短いコードをコピー&ペーストして、コード全体を評価することができます。REPLには、コード補完ビルトイン(Tabキーを押す)もあり、型の研究もできます。ぜひ、Scalaをインストールして遊んでみましょう!

最初から完全に関数型でなくてもよい

自分にプレッシャーをかけすぎないようにしましょう。Reactive Web Applicationsの第3章では、命令型コードから始めて、ゆっくりと、不変な宣言型コードに移行するようにアドバイスされています。

時間をかけて構文に慣れてから、パラダイムシフトに挑みましょう。構文を熟知しておけば、まだ宣言的プログラミングが自分に適していない場合に命令型プログラミングに戻る場合も、ずっと快適な経験が得られるでしょう。

同時に多くを試みないこと

例えば、implicitパラメータ、implicit変換、trait、中置表記法、関数、高階関数、lazy変数、それから、型レベルプログラミングに関する特徴などの、Scalaの様々な特徴を初めから同時にたくさん使いたくなることでしょう。でも、1段階ずつ順を踏むべきだし、その方が得策です。

この記事は、ここまでです。結局Scalaは悪ではないのだと読者が納得し、大いに楽しんでくれるよう祈ります。