みどりねこ日記

よくわからないけど、頑張りますよ。

Scalaは関数型プログラミング言語ではない

面白い記事があったので読んでみました。

翻訳です。ライセンスはCreative Commonsです。




しばらくScalaで仕事をして、疑う余地なく以下のことが断言できるようになりました。

Scala関数型言語ではありません。クロージャを持ち、静的な型を持つオブジェクト指向言語です。”


なので、みんなが嘘の売り文句を延々と言うのをやめてくれないかなと思っています。
これがどういうことなのかを理解するためには、ScalaのメーリングリストにJon Harropが流した挑発的な文を見てみてください。そのスレッドで、Martin O.が
オブジェクト指向こそが唯一の”美しい”解である”
と主張しています。のちにMLで、もう少し丁寧にこのことについて述べていて、それによると、アプリケーションの基幹のデータ構造を何度も変更しなくてはならないようなとき、オブジェクト指向が最適な解であると述べてみます。最終的にその議論は泥沼化してしまったのですが、終盤は非常に興味深いものでした。

私が特に気になったのは、Martin O.からのこのポストです。
”abstractである定義、値、型の実装はそのスーパークラスの他のメンバも参照できる。しかし、高階関数やファンクタに対する引数として適用の結果を参照することはできない。だから、抽象の入ったopen recursionはオブジェクト指向プログラミングではサポートされているが、それを関数型で実現するには面倒なことになる(例えばTaPLでのクラスのエンコーディング)。”

さて、この文は少々おかしいです。Pierce本で定義されているように、open recursionは暗黙的にオブジェクト指向の考えであるため、これを関数型プログラミング言語で実現させるなら、違うパラダイムなので無理が生じるのは当たり前ですね。はたして、その技術は関数型プログラミング言語で必要なのか?使うに値するのか?という疑問が湧いてきます。関数型言語は同じ問題に対してもっと良い解をもっているのか、それともそういった馬鹿馬鹿しいことができないように制限されているのでしょうか。

Scala関数型プログラミング言語であるかを考えてみます。そもそもScalaはどのように問題を解決しているのでしょうか。Brian Hurtなら、言語の本質についてこう述べるでしょう。
"言語が何かを可能にするのではなくて、言語がなにを簡単にするかが問題なんだ。Scalaは何を簡単にしてくれるんだ?”

この発言に対して、Martin O.は否定的です。
”私達は、関数型とオブジェクト指向のどちらもを美しく高めることを第一としていた。関数はファーストクラスである必要があったし、関数リテラル、そしてクロージャも必要だった。また、他にも型や多相性、パターンマッチングといった機能も欲しかった。そして、Pizza言語で実現できたものよりも、関数型、オブジェクト指向型のどちらの面でも綺麗にしたかった。それこそが、したいことだったんだ。その後、それを実現するのがすごく簡単だってことに気づいた。関数型言語はきちんと体系化された機能をすでに持っていたからね。”
この考えによると、Scala関数型言語といえるでしょう。

まずは基本から見て行きましょう。
関数型言語では、当たり前ですが関数をプログラムしていきます。つまり、問題を関数に置き換えることが解となるといえます。OCamlでは、以下のように関数を定義します。

let f x = x + 1;;

しかしScalaでは、少し違った形で定義します。

object SomeHolderObject {
    def f(x:Int):Int = { x + 1 }
}

OCamlでは、関数は直接的に定義されています。Scalaでは関数に対してオブジェクティブな定義を与える必要があります。objectキーワードを使うことで、singletonであるオブジェクトであることを保証していますが、これはJavaでstaticキーワードを使った場合と大して変わりません。ですので、根本的なな構造からすでに関数型言語とは呼べず、むしろオブジェクト指向よりだということが言えます。

さて、Scalaによる抽象化を見てみましょう。Scalaでは、抽象化をするとき、インターフェースやtraitなどの形でするのが普通です。traitはScalaにおけるopen recursionですが、その名に反してオブジェクト指向よりの考えに基づいています。さて、Scalaの基本的な部分がオブジェクト志向よりであること、そしてScalaの抽象化がオブジェクト指向的であるということがわかりました。

ダメ押しとして、Scalaでの関数型プログラミングを見てみましょう。私の大好きなカリー化から行ってみます。

OCamlでは余裕ですね。

let x a b = a + b;;
let y = x 1;;
y 2;; (=> 3)

悪くないですね。
さて、Scalaでやってみます。

def x(a:Int, b:Int) = a + b
def y = x(1)

ところが上のコードではエラーを返してきます。
x: (Int, Int) Intなので引数が足りません…
もう一度やってみます。

def x(a:Int, b:Int) = a + b
def y = Function.curried(x _) (1)
y(2) // 3

…。
カリー化は可能です。一応。ただし、本当の関数型言語に比べてひどく面倒です。まあ、このへんはまだ良いとしましょう。ちょっとぐらい汚くてもいいです。さて、次はもっと代数的な関数型プログラミングとして基本的なことをしてみましょう。

関数型言語でよく使われる、OCamlのvariant型のような代数的データ型はどうでしょう。OCamlやF#、Haskellではパターンマッチングという強力な機能を提供してくれています。Scalaはどうでしょうか。

OCamlではこうかけますね。

type robert = Foo of int | Bar of string | Baz;;

Scalaだとこうなります。

sealed abstract class Robert;
sealed case class Foo(value:Int) extends Robert;
sealed case class Bar(value:String) extends Robert;
sealed case class Baz extends Robert;

…。私も最初は、こいつがcase classの継承のためにきちんと書かれたコードなんだろうなって思ってました。だからオプション的なあれかと。でもどうやら必ず書かなくてはならないようで、そうなるとOCaml版のものと比べてひどくうっとおしいコードなだけですね。

さて、ここでもう一度考えます。Scala関数型プログラミングを簡単にしてくれているだろうか?
いいえ、全く。全然。

じゃあなぜScala関数型言語だと言われたのでしょうか?関数がファーストクラスだから?PerlRuby、Groovyもそれは備えてるのに関数型言語とは呼ばれませんね。静的型付けのクロージャ?ならC#はどうなるのでしょうか。パターンマッチング?オブジェクト指向のスタイルで簡単に実現できますね。無駄のない実装で。イミュータブルな傾向があるから?それはいいことですが、ただそれだけなら関数型言語と呼ばれるには程遠いですね。

Scala関数型言語ではありません。なぜなら、関数型のプログラミングスタイルを全然簡単にしてくれないからです。可能ではありますし、関数型らしいところもありますが、根本的にScalaは関数型ではありません。OCamlオブジェクト指向のプログラミングを可能にしますので、オブジェクトな部分を持ちます。しかしOCamlは根本的にはオブジェクト指向ではありません。それは全然問題無いです。…でもここは引き分けにしておきましょうか。

ただ、勘違いしてほしくないので。
ScalaJVM上で動く言語として、大きな飛躍です。今C#Javaを正真正銘、はるかに上回っていますし、ScalaはC#を超えてJVMを舞台の先頭に引き戻すための素晴らしい試みであると言えます。しかし、Scalaが王道的な”オブジェクト指向型言語”であることを踏まえると、”関数型プログラミング言語”と呼ぶのはやめたほうが良いのではないでしょうか。欺瞞に聞こえますし、なにより「関数型プログラミング言語とはなにか」を押し下げているように見えます。