メソッド != オブジェクト

このページタイトルは、正確には、defで定義された関数っぽいもの != ファーストクラスオブジェクトであるべきなのですが、それだとタイトルだけ見たときによくわからないだろうと考え、このようなタイトルにしました。

まず結論だけ先に書くと、Scalaでは、メソッドおよびその他のdefで定義されたものはファーストクラスオブジェクトではありません。これは、次のような簡単なコードで確かめられます。

scala> object Foo { | def foo(x: Int) { println("foo" + x) } | } defined module Foo <scala> val fooMethod = Foo.foo // fooMethod変数をfooメソッドに束縛したい <console>:6: error: missing arguments for method foo in object Foo; follow this method with `_' if you want to treat it as a partially applied function val fooMethod = Foo.foo ^ scala> val fooMethod = Foo.foo _ fooMethod: (Int) => Unit = <function1>

Scalaでメソッドが(ファーストクラス)オブジェクトなら、//行の部分がうまく行くはずなのですが、エラーメッセージが出てしまっています。簡単に言うと、_付けないとオブジェクトにならないよ、ということです。

説明としてはこれで終了なのですが、メソッドなどがオブジェクトであるという誤解が度々見かけられるのは、Scalaの公式サイトの説明にも一因があります。まず、 Introducing Scalaから引用しましょう。

Scala is also a functional language in the sense that every function is a value. Scala provides a lightweight syntax for defining anonymous functions, it supports higher-order functions, it allows functions to be nested, and supports currying. Scala's case classes and its built-in support for pattern matching model algebraic types used in many functional programming languages.

箇条書きにすると

    • 全ての関数が値であるという意味で、Scalaは関数型言語である

    • Scalaは無名関数や高階関数を定義するための軽量構文を持っている

    • 関数はネストおよびカリー化可能である

    • ケースクラスとパターンマッチの組み込みサポートによって、多くの関数型言語における代数的データ型をモデル化できる

という感じになります。特に嘘が書いてあるわけではありません。ただ、メソッドやネストした関数(微妙に不適切な表現ですが)が関数に該当するのかどうかには触れられていません。Introductionだからわざわざそのような事に触れる必要は無いですし、これ単体では特に問題はありません。

さて、ここまではいいのですが、Scalaのドキュメントには、非常に紛らわしい記述が見かけられます。たとえば、Scala by Example(PDF)のChapter 5(p.21)から引用します。

A function in Scala is a “first-class value”. Like any other value, it may be passed as a parameter or returned as a result. Functions which take other functions as pa- rameters or return them as results are called higher-order functions. This chapter introduces higher-order functions and shows how they provide a flexible mecha- nism for program composition.

要はScalaでは関数は「ファーストクラスの値」だということを言っています。ここまではOKです。

ここからが問題なのですが、同ページ以降において、

1. Write a function to sum all integers between two given numbers a and b:

def sumInts(a: Int, b: Int): Int = if (a > b) 0 else a + sumInts(a + 1, b)

と言った記述が度々見かけられます。しかし、これらの"function"と書かれているものは、Scalaにおいて、「ファーストクラスの値」 ではありません。これは先ほどと同じ実験によって確かめられますので省略します。

では、全ての関数が「ファーストクラスの値」である、という説明は嘘だったのかというとそうでないのがややこしいところです。上記のsumIntsの例で言うと、(Scalaを離れた)概念的には、sumIntsは関数だと考えられるからです。

結局のところ、問題の本質は、説明者がScalaについて説明するときに、

    • 「ファーストクラスの値」ではないが、概念としては関数のように考えられるもの

    • Scalaにおいて「ファーストクラスの値」として扱われる関数(オブジェクト)

この二つを混ぜて書いているところにあります。この二者の違いを意識しなくても良いことがあるのですが、場合によっては思わぬ落とし穴にはまることがあります。

この二つを区別するのは容易ですので、判断基準を示しておきます。

    • defで定義された何か(メソッド定義、ネストした関数定義によって定義されるもの):ファーストクラスオブジェクトではない

    • defで定義されていない何かで、関数と呼ばれるもの:ファーストクラスオブジェクト

たったこれだけです。書籍やWebサイトの説明には紛らわしいものが度々見かけられますが、上記のルールさえ意識しておけば、間違うことは無いでしょう。