メソッドと関数
Scalaにはメソッドと関数リテラルという異なる形式で処理を記述できる。
オブジェクト指向でかつクロージャもある言語だと、だいたいそうみたいだ。
でも、それは混乱のもとではないか?
(詳しくないが、Rubyはもっとややこしい気がする。)
次の4つのパターンで、処理を定義してみた。
(01) def met01(x:Int, y:Int) = x + y // (Int,Int)Int
(02) def met02 = (x:Int, y:Int)=>x + y // (Int, Int) => Int
(03) val met03 = (x:Int, y:Int)=>x + y // (Int, Int) => Int = <function>
(04) var met04 = (x:Int, y:Int)=>x + y // (Int, Int) => Int = <function>
scalaインタープリタで実行した場合、ご覧の通り(01)~(03)で微妙に異なった型が表示される。ややこしい。
(03)と(04)は再代入できるかどうかの違いしかないだろう。
メソッドとしての通常の記述方法は(01)で、関数リテラルは(03)か(04)が普通だろう。
そうすると(02)の記述の位置付けは?こんなのエラーにするべきでは?
と思ったがそれは誤解のようだ。後ほど考察。
とりあえず、そのまま変数に代入してみる。
(05) var m = met01 // error: missing arguments for method met01 in object $iw;
(05)はmet01を実行し、その結果をmに代入しようとするらしい。で引数が合わないのでエラーになったようだ。
メソッドはそのままでは関数オブジェクトとして扱わないらしい。
met01を関数オブジェクトとして扱うには、部分適用の _ を使用する。
(06) var m = met01 _ // (Int, Int) => Int = <function>
これならOK。
ただし、関数オブジェクトしかあり得ないように限定すると、関数オブジェクトとして扱う。
(07) var m:(Int, Int)=>Int = met01 // (Int, Int) => Int = <function>
変数mの型を明示的に記述し、関数型に限定したので _ はなくてもOK。
(08) var m = met02 // (Int, Int) => Int = <function>
(09) var m = met03 // (Int, Int) => Int = <function>
(10) var m = met04 // (Int, Int) => Int = <function>
met03とmet04はもともと関数オブジェクトを代入した変数なので、mにはそれが代入されたらしい。
それはいいとして、met02も果たしてそうなのか?よくわからない。
今度は、met01~met04に再代入してみよう。
(11) met01 = (x:Int, y:Int)=>x * y // error: missing arguments for method met01 in object $iw; error: reassignment to val
2つエラーがでている。最初のは先ほどと同じで、左辺のmet01を実行しようとすることが原因のようだ。
2つめは何だ?met01がvalであるかのようにみえる。
インタープリタでは2つエラーがでたが、コンパイルの場合は前半のエラーはでない。意味不明。
(12) met02 = (x:Int, y:Int)=>x * y // error: value met02_= is not a member of object $iw
(13) met03 = (x:Int, y:Int)=>x * y // error: reassignment to val
当たり前だが、これみると明らかにdefとvalは別物だ。
defで定義したmet02はクラスやオブジェクトのメンバー(というよりはフィールドだと思う)にはならないらしい。
要するにJavaのメソッドになるのだろう。
よって、valで定義したmet03はインスタンス中に存在するが、met02は存在しないと思われる。
(12)のエラーはそういう意味だと思う。
ただ、そうすると(11)ででた2つめのエラーと矛盾するように思える。
(13)のエラーはvalに再代入しようとするためだろう。
(14) met04 = (x:Int, y:Int)=>x * y // (Int, Int) => Int = <function>
これはOK。
とりあえず、呼び出してみよう。(上記の代入後ではなく、最初の状態に戻して)
(15) met01(10, 20) // Int = 30
(16) met02(10, 20) // Int = 30
(17) met03(10, 20) // Int = 30
(18) met04(10, 20) // Int = 30
どれも普通に実行できる。違いは特に見当たらない。
引数として渡してみよう。まずメソッドを準備する。
(19) def call(x:Int, y:Int, f:(Int, Int)=>Int):Int = {f(x, y) * 2} // (Int,Int,(Int, Int) => Int)Int
実引数として渡す。
(20) call(10, 20, met01) // Int = 60
(21) call(10, 20, met02) // Int = 60
(22) call(10, 20, met03) // Int = 60
(23) call(10, 20, met04) // Int = 60
どれも同じ。ただし、(20)は本来は次のように書くべきなんだろう。
(24) call(10, 20, met01 _) // Int = 60
それが上記のように書けるのは、callの第3引数が関数型以外にないと限定されるため。
代入の理屈と同じようだ。
今度は、met01~met04を使用して新たなメソッドを定義する。
ただし、
(25) def m = met01 // error: missing arguments for method met01 in object $iw;
(26) def m = met01 _ // (Int, Int) => Int = <function>
(27) def m:(Int, Int)=>Int = met01 // (Int, Int) => Int = <function>
(28) def m = met02 // (Int, Int) => Int = <function>
(29) def m = met03 // (Int, Int) => Int = <function>
(30) def m = met04 // (Int, Int) => Int = <function>
は見掛け上だが(05)~(10)の単なる変数への代入と同じになってしまう。
これでは余り意味がないので、純粋なメソッド形式で定義する。
(31) def m(x:Int, y:Int):Int = met01 // error: missing arguments for method met01 in object $iw;
例によって右辺を実行しようとしている。
(32) def m(x:Int, y:Int):Int = met01 _ // error: type mismatch; found : (Int, Int) => Int required: Int
解釈が難しい。右辺はInt型を返す式でないといけないようだ。まあその通りか。
(33) def m(x:Int, y:Int):Int = met01(x, y) // (Int,Int)Int
OK。当たり前。
(34) def m(x:Int, y:Int):Int = met02 // error: type mismatch; found : (Int, Int) => Int required: Int
2つ上と同じ。まあその通りか?
(35) def m(x:Int, y:Int):Int = met02 _ // error: type mismatch; found : () => (Int, Int) => Int required: Int
met02 _ の型が() => (Int, Int) => Intになるのがややこしい。
結局met02は引数がなく、(Int,Int)=>Int型の関数を返すメソッドということでいいのかな?
非常にややこしいことに、Scalaはパラメーターなしメソッドと空括弧メソッドがあるそうだ。
met02はパラメーターなしに属するのだと思う。
(36) def m(x:Int, y:Int):Int = met02(x, y) // (Int,Int)Int
これもいいか。
(37) def m(x:Int, y:Int):Int = met03 // error: type mismatch; found : (Int, Int) => Int required: Int
(38) def m(x:Int, y:Int):Int = met03 _ // error: type mismatch; found : () => (Int, Int) => Int required: Int
(39) def m(x:Int, y:Int):Int = met03(x, y) // (Int,Int)Int
やはり02と同じ。
似たようなものを2つ作るとややこしくてしょうがない。
結局、関数リテラルやクロージャなど、関数オブジェクトのある言語は、メソッドなどという特別な概念はなくすべきではないか?
その点、JavaScriptが結構いい線いっているのではないか?
ではメソッドらしきものはどうやって実現するか?
とりあえずはフィールドに関数オブジェクトを代入すればすむ。
しかし、そうするとそのフィールド分、インスタンスは余計なメモリを消費するという問題もある。
インスタンス上にできるフィールドとは区別できるキーワードも欲しいところだ。
そうなると、結局Scalaのdef, val, varという3つのキーワードはいい線いっているのかもしれない。
が、メソッド定義の方法を(02)の方法に限定するべきじゃないかな?