連載
» 2012年04月05日 00時00分 公開

スケーラブルで関数型でオブジェクト指向なScala入門(4):基本的なパターンマッチとScalaで重要な“関数” (2/3)

[中村修太,クラスメソッド株式会社]

Scalaの関数の定義と呼び出し

 ご存じのとおり、「関数」とはある値(引数)を与えると、何らかの値(返り値)を返す処理のまとまりです。

 Scalaで関数を定義するための構文は、下記のようになっています。また、関数本体が複数行になる場合は「{}」で囲います。

def 関数名[型パラメータ](引数名:引数の型名,……):返り値 = 関数本体

 REPLで関数を定義してみましょう。Int型の引数を2つ取り、Int型を返す「add」という関数を定義します。

scala> def add(x:Int, y:Int):Int = x + y
add: (x: Int, y: Int)Int

 関数の返り値として、2つの引数を足した値を返しています。このように、Scalaでは値を返すときに「return」を記述する必要はなく、最後に評価された式の値が返ります(※returnを記述することもできます)。

 もう1つ関数を定義してみましょう。今度は引数を取らず、戻り値もない関数printを定義します。

scala> def print():Unit = println("hello")
print: ()Unit
 
scala> def print:Unit = println("hello")
print: Unit
 
scala> def print = println("hello")
print: Unit

 上記関数の定義は3つとも同じ関数を定義しています。引数がない場合、「()」を省略することが可能です。戻り値も型推論によって省略できますが、return文を明示的に記述している場合など、省略できないケースもあるので、注意してください。

補足 関数の定義における注意点

Scalaでdefを用いて関数を定義するには、クラスやオブジェクトのメソッドとして定義しなければなりません(後述する「ローカル関数」を除く)。

REPLでは、そういった必要はなく、そのままdefで定義できますが、Eclipseを使用してサンプルの動作を確認している方は、「main」メソッドが定義してあるクラスのメソッドとして関数を定義してください。

object Main {
 
    def main(args: Array[String]) = {
        println(add(1, 2))
        print
    }
 
    /** add関数定義 */
    def add(x: Int, y: Int): Int = x + y
    /** print関数定義 */
    def print: Unit = println("hello")
}
Main.scala

 次に、定義した関数を呼び出してみましょう。関数名に続けて「()」で引数を囲めば呼び出せます。引数がない場合、「()」を省略して関数を呼び出すことも可能です。

scala> add(1,2)
res6: Int = 3
 
scala> print
hello

 なお、先ほどの例のprint関数のように、引数を取らない関数を「()」なしで定義した場合、呼び出し時に「()」を付けるとエラーになるので、注意してください。

scala> print()
<console>:9: error: Unit does not take parameters
              print()
  
scala> def print() = println("hello")
print: ()Unit
  
scala> print()
hello

関数を「オブジェクト」として扱うには

 本連載中に何度か「Scalaが関数をファーストクラスオブジェクトとして扱える」ということを説明してきました。ここでは、実際に関数をオブジェクトとして扱ってみましょう。

 まずは、関数オブジェクトを変数に代入してみます。そのためには、「関数リテラル」を使用するのが簡単な方法です。関数リテラルの構文は、以下のようになっており、「()」で囲った名前付きパラメータのリストと「=>」、関数の本体から構成されます。

(変数名:型名,……) => 関数本体

 例では先ほど定義した「add」関数と同じ内容を関数リテラルで定義し、「func」変数に代入しています。

scala> val func = (x:Int, y:Int) => x + y
func: (Int, Int) => Int = <function2>
 
scala> func(1,2)
res10: Int = 3

 変数「func」の型を明示的に宣言することもできます。

scala> val func: (Int,Int) => Int  = (x:Int, y:Int) => x + y
func: (Int, Int) => Int = <function2>

 記号がいろいろあって、少しややこしいですが、「val func: (Int,Int) => Int」の部分が、func変数を関数の型として宣言している部分です。func変数はInt型引数を2つ取り、Int型を返す関数オブジェクトであるということを示しています。

補足 Function「トレイト」は「インターフェイス」?

先ほど例で使用したfunc変数は「(Int,Int) => Int」という型を宣言していましたが、次のように記述しても同様の関数オブジェクトが定義可能です。

scala> val func:Function2[Int,Int,Int]  = (x:Int, y:Int) => x + y
func: (Int, Int) => Int = <function2>

この「Funtion2」とは、何でしょうか? Scalaでは、関数の引数に応じて「トレイト」(いまのところ、Javaでいう「インターフェイス」のようなものと思っていてください)が用意されており、Function0からFunction22まであります。

そのため、Function2は引数を2つ取る関数のトレイトを表しています。例の型パラメータは最初の2つが引数の型を示しており、最後の1つが返り値の型を示しています。

関数の型を使用した形式は、Functionトレイトを使用した記述の糖衣構文です。


 次は、関数を引数に取る関数を定義してみましょう。

 「calc」という関数は、「Int型の引数を2つ取り、Int型を返す関数」と数値の2つの引数を取ります。呼び出すときに関数リテラルを使用し、第1引数の関数の中身を設定しています。下の例では、calc関数を呼び出す際、第2引数で渡された値同士を足して返す関数を設定しています。

scala> def calc(f:(Int,Int) => Int, num:Int) :Int = f(num,num)
calc: (f: (Int, Int) => Int, num: Int)Int
 
scala> calc((x,y) => x + y,10)
res13: Int = 20

 関数リテラルを使用せず、事前に関数オブジェクトを定義して、それを渡すことも可能です。

scala> val add = (x:Int, y:Int) => x + y
add: (Int, Int) => Int = <function2>
 
scala> calc(add, 10)
res14: Int = 20

返り値に関数オブジェクトを返す「高階関数」

 今度は、返り値に関数オブジェクトを返す関数を定義してみましょう。例では、「getFunc」という、String型引数を1つ取り、「Int型引数を2つ取り、String型を返す関数」を返す関数を定義しています。

scala> def getFunc(str:String):(Int,Int) => String = (x:Int,y:Int) => str + (x + y)
getFunc: (str: String)(Int, Int) => String
 
scala> val f = getFunc("result is ")f: (Int, Int) => String = <function2>
 
scala> f(1,2)res17: String = result is 3

 getFunc関数に文字列を渡し、返り値として関数オブジェクトを受け取っています。受け取った関数を呼び出すと、最初に指定した文字列と引数2つを足された値が結合された文字列が返されます。

 ここでは、関数オブジェクトを使って、いろいろな動作を確認してみました。このような、関数を引数として受け取ったり関数を戻り値として返したりする関数を「高階関数」と呼び、Scalaでは多用されます。

補足 「関数」? 「メソッド」?

Javaの場合、オブジェクトを操作するための手段として、メソッドを定義していました。このメソッドを「関数」と呼ぶこともありますが、本連載では、Scalaにおける関数とメソッドを区別して呼びます。

Scalaの関数は「ファーストクラスオブジェクト」ですが、「def」を使用して定義された関数はファーストクラスオブジェクトではありません。

関数オブジェクトはFunctionトレイトを継承したクラスのオブジェクト(関数リテラルなど)です。そのため、本連載ではクラス/オブジェクト/トレイト(今後の連載で紹介します)直下にdefで定義したものを「メソッド」と呼び、それ以外のものは「関数」と呼びます。


 ここまでは、関数の基本的な使い方を紹介してきましたが、Scalaの関数には、まだまだいろいろな機能があります。次ページでは、比較的よく使用される関数の機能を紹介します。

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。