Posted at

Scalaにおけるメソッドと関数の違いを端的に説明する


はじめに

この話は、私が折に触れてすることなのですが、Scalaではメソッドと関数は見た目は似ているし使い方も似ているものの、違うものです。そして、区別できないと、複雑なScalaプログラムを読解するときに困ることもあります(どうせコンパイラが指摘してくれるので、それが原因でバグを埋め込むことはまずないのですが)。この違いに混乱している人を今もよく見かけるので、改めて書いておきたいと思ったので、記事にすることにしました。


メソッドと関数の違い

これは本当は非常に簡単なもので、

def foo1(v: Int): Int = v

みたいに def で始まる定義があったら、誰がなんといおうとその foo1メソッドです。コップ本ですら、あえてこの両者をきちんと区別せずに書いていますが、あくまで便宜的な説明です(厳密にいうと、関数と呼ぼうがメソッドと呼ぼうが間違いではないんですが、foo1の型はメソッド型になることは変わらないです)。

一方、

val foo2: Int => Int = (v) => v

という定義があったとき、これは、 foo2Int => Int 型の 関数 オブジェクトが代入されていることになります。

では、この二つの違いはどこにあるのかというと、たとえば、以下のようなプログラムがあったとします。

def foo1(v: Int): Int = v

val bar1 = foo1

しかし、これは以下のような型エラーになります。

scala> val bar1 = foo1

^
error: missing argument list for method foo1
Unapplied methods are only converted to functions when a function type is expected.
You can make this conversion explicit by writing `foo1 _` or `foo1(_)` instead of `foo1`.

このエラーメッセージが非常に紛らわしいのですが、foo1のような定義はメソッドであり、かつ、メソッドは値ではない型(non-value type)であるメソッド型を持つので、そのままでは値として代入できないために起こるエラーです。このエラーを取り除くには、おおまかに二つの方法があります。一つは、エラーメッセージに書かれているように、

def foo1(v: Int): Int = v

val bar1 = foo1 _

とすることです。Scalaの言語仕様上、eta-expansionと呼ばれる概念があって、これは、メソッド型を対応する関数型(はvalue-type)に変換する操作のことです。

上の例だと、foo1のメソッド型は(Int)Int(これはScalaプログラム上では明示できない点に注意)なのですが、対応する関数型はInt => Intであるという風に言語仕様として定められています。そのような変換が行われた結果として、

def foo1(v: Int): Int = v

val bar1: Int => Int = (v: Int) => foo1(v)

と書いたのと同じになります。

もう一つは、

def foo1(v: Int): Int = v

val bar1: Int => Int = foo1

のように、bar1の型を明示する方法です。Scalaでは、関数型が必要な箇所にメソッド型が来た場合、eta-expansionが行われることになっているので、これは、

def foo1(v: Int): Int = v

val bar1: Int => Int = foo1 _

と同じ意味になります。引数の位置に関数型が来る場合(以下)も、基本的にこれと準ずる扱いになります。

def foo1(v: Int): Int = v

def hoge(f: Int => Int): Int = f(1)
hoge(foo1) // hoge(foo1 _) と同じ


メソッド型と関数型の対応

先ほど、メソッド型と関数型という用語を用いましたが、基本的に、メソッド型は

(A1,A2,A3...,An)R

のように表記されます(A1-Anは引数の型で、Rは返り値の型です。

一方、対応する関数型は、

(A1,A2,A3...,An) => R

のようになります。


部分適用とかカリー化とか

全く関係がないとも言い切れないのですが、メソッド型から関数型への変換が必要に応じて行われているだけなので、この辺の挙動を把握するのに、別に部分適用とかカリー化とかいう概念を理解する必要は全然ないです。

(実のところ、複数の引数リストが出たきたときには、多少関係しますが、複数の引数リストを持つメソッドを関数に変換したいことはあんまりないので、実用上は困らないと思います)


おわりに

今回、あらためて、メソッドと関数の違いについて端的に説明してみました。別に、関数と呼んでもメソッドと呼んでもいいのですが、関数型メソッド型があって、defで定義したものはメソッド型を持つ、と理解しておけば、不要な混乱を避けられるだろうというのが私の意見です。

めちゃくちゃ端的に書いたのでわかりにくいという意見や、誤りなどがあればご指摘いただければと思います。