[AD] Scalaアプリケーションの開発・保守は合同会社ミルクソフトにお任せください
この記事ではメソッドと関数について説明します。
メソッドを定義する
メソッドとは、オブジェクト指向プログラミングにおけるオブジェクト内のデータを操作するためのコードの集まり(サブルーチン)です。
ここではメソッドを定義する方法を説明します。
メソッドはclass
、object
、trait
内にdef
キーワードを使用して定義します。
形式は以下のとおりです。
def メソッドの名前(パラメーターリスト): 戻り値の型 = 式 or ブロック
以下はメソッドの定義例です。
2つのInt
のパラメーターを受け取り、第一引数から第二引数を引いたInt
型の結果を返しています。
object Math: def sub(x: Int, y: Int): Int = x - y
それでは定義したメソッドを呼び出してみます。
メソッドはレシーバーとなるオブジェクトを指定して呼び出します。
先ほどのメソッドは、Math
オブジェクトに定義したので、Math
オブジェクトに対してメソッドを呼び出します。
val result = Math.sub(3, 1) println(result)
実行結果は以下のとおり第一引数から第二引数の値を引いた値が表示されます。
2
printやprintlnもメソッドです
先ほどメソッドを呼び出すにはレシーバーを指定すると説明しました。
しかし、先ほどの例でもprintln
は、レシーバーを指定せずに呼び出しています。
では、println
はメソッドではないのでしょうか?
実はprintln
はPredef
オブジェクトのメソッドです。
Predef
オブジェクトのメンバーは暗黙的にimport
されるため、レシーバーを指定する必要はありません。
コンパイラはすべてのソースファイルに以下のインポートを追加します。
import java.lang.* { import scala.* { import Predef.* { // your code } } }
すべてのコードからjava.lang
/ scala
パッケージのクラスやオブジェクト、Perdef
オブジェクトのメンバーをimport
を書かなくても使用することができます。
Predef
オブジェクトについては以下の記事を参照してください。
メソッドの中に定義する
メソッドはメソッドの中に定義することもできます。
メソッドの中に定義したメソッドはそのメソッド内からのみ呼び出すことができます。
以下の例では引数で指定した数のフィボナッチ数列を作成するメソッド「fibonacciSequence」の中にメソッド「go」を定義しています。
「fibonacciSequence」の最終行で「go」を呼び出しフィボナッチ数列を返します。
def fibonacciSequence(n: Int): List[Int] = def go(x1: Int, x2: Int, n: Int): List[Int] = if (n == 1) List(x1, x2) else x1 :: go(x2, x1 + x2, n - 1) go(0, 1, n)
定義したメソッド「fibonacciSequence」を呼び出してみます。
println(fibonacciSequence(10).mkString(", "))
実行結果は以下のとおり、0から55のフィボナッチ数列が表示されます。
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55
関数を定義する
関数とは、値を受け取りなんらかの処理をするコードの集まり(サブルーチン)です。
ここでは関数を定義する方法を説明します。
関数は以下のように右辺のパラメータリストと=>
、左辺の式またはブロックで構成されます。
(パラメーターリスト) => 式 or ブロック
「メソッドを定義する」で定義したメソッドを関数で定義すると以下のようになります。
以下の関数は、右辺の「x」「y」を左辺の式で評価した値(x - yの結果)に変換することを意味しています。
(x: Int, y: Int) => x - y
定義した関数を呼び出してみます。
以下の例では定義した関数を呼び出すために変数「sub」に定義した関数を保管しています。
関数を変数に保管するとはどういうことなのかは後ほど説明します。
val sub = (x: Int, y: Int) => x - y val result = sub(3, 1) println(result)
実行結果は以下の通り、メソッドの実行結果と同じです。
2
関数はFunctionトレイトの無名サブクラスのインスタンス
先ほどは定義した関数を変数「sub」に保管していました。
なぜこのようなことが出来るのでしょうか?
Scalaの関数はFunction
トレイトの無名サブクラスのインスタンスです。
「関数を定義する」で説明した形式で関数を定義するとFunction
トレイトの無名サブクラスのインスタンスが作成されます。
先ほどの例では「sub」に保管されていたのは、Function2
トレイトの無名サブクラスのインスタンスです。
先ほどの例で定義した関数をFunction2
トレイトで定義すると以下のようになります。
Function2[Int, Int, Int]
は、2つのInt
型の引数を持ちInt
型の値を返す関数ことを意味しています。
作成したインスタンスに引数を指定するとapply
メソッドが呼び出されます。
val sub: Function2[Int, Int, Int] = new Function2[Int, Int, Int]: def apply(x: Int, y: Int): Int = x - y
定義した関数を呼び出してみます。
val result = sub(3, 1) println(result)
実行結果は以下の通り「関数を定義する」の例と同じ結果になります。
2
Function
トレイトは引数の数に応じてFunction0
からFunction22
が定義されています。
関数は引数や戻り値にできる
関数は他の関数やメソッドの引数に指定したり、戻り値にすることができます。
このような関数を第一級関数(First class function)と呼びます。
関数を引数に指定する
関数を引数に指定する方法を説明します。
以下の例では、1から5の数値を要素に持つList
のmap
メソッドにInt
を受け取りString
を返す関数を指定しています。
val list = List(1, 2, 3, 4, 5) val result = list.map(x => x match { case 1 => "One" case 2 => "Two" case 3 => "Three" case _ => "Over Four" }) println(result.mkString(", "))
実行結果は以下のとおりです。
One, Two, Three, Over Four, Over Four
Scalaのコレクションクラスにはこのように関数を引数に指定するメソッドが多く定義されています。
このように関数を引数に指定する関数のことを「高階関数」と呼びます。
関数を戻り値にする
次に関数を戻り値にする方法を説明します。
以下は、関数を戻り値にするメソッドの例です。
「init」は引数で初期値を受け取り、呼び出されるたびに1加算する関数を返しています。
def init(i: Int): () => Int = var count = i () => { count = count + 1 count }
実行してみます。
val increase = init(10) println(increase()) println(increase()) println(increase())
以下のように「init」の引数で指定した値を呼び出されるたびに1加算されていることがわかります。
11 12 13
メソッドは第一級関数ではない
関数は第一級関数であることを説明しました。
一方、メソッドは第一級関数ではありません。
メソッドは第一級関数ではありませんが、引数や戻り値に指定することができます。
メソッドを引数や戻り値に指定した場合は、コンパイラが自動的に関数に変換します。
そのため、メソッドも引数や戻り値に指定することができます。
では、メソッドを引数と戻り値に指定する例を見てみます。
メソッドを引数に指定する
以下の例では、「関数を引数に指定する」の例のようにList
のmap
メソッドの引数にInt
をString
に変換する以下のメソッドを指定してみます。
def int2string(x: Int): String = x match case 1 => "One" case 2 => "Two" case 3 => "Three" case _ => "Over Four"
val list = List(1, 2, 3, 4, 5) val result = list.map(int2string) println(result.mkString(", "))
実行結果は以下の通り、「関数を引数に指定する」の例と同じ結果になりました。
One, Two, Three, Over Four, Over Four
メソッドを戻り値にする
以下の例では、「関数を戻り値にする」の例で使用した「increase」をメソッドで定義しています。
def init_2(i: Int): () => Int = var count = i def increase(): Int = count = count + 1 count increase val increase_2: () => Int = init_2(10) println(increase_2()) println(increase_2()) println(increase_2())
実行結果は以下の通り、「関数を戻り値にする」の例と同じになりました。
11 12 13
メソッドと関数の違い
引数を受け取って値を返すという点では、メソッドと関数に違いはありません。
メソッドと関数で大きく違う点は第一級関数であるかどうかです。
メソッドも第一級関数に変換できるので混乱しやすいですが、上記の違いを覚えておくと良いでしょう。