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

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

[中村修太,クラスメソッド株式会社]
前のページへ 1|2|3       

関数の引数を固定しない「可変長引数」

 関数の引数の数を固定ではなく、任意の数の引数を渡したいときに使用します。可変長引数を指定するための構文は、以下のように、引数の型名の後に「*」を付加します。

def 関数名(引数名:引数の型名*) = 関数本体

 このようにすると、0個以上の引数を渡して呼び出せます。

scala> def showMessages(args: String*) = for(arg <- args) println(arg)
showMessages: (args: String*)Unit
 
scala> showMessages()
 
scala> showMessages("hello")
hello
 
scala> showMessages("hello","Scala")
hello
Scala

 関数内では、この可変長引数は指定した型の「Array」になっています(上記例では、「Array[String]」)。しかし、そのまま「Array[String]」の変数を渡しても、うまくいきません。

scala> val array = Array("hello","Scala")
array: Array[java.lang.String] = Array(hello, Scala)
 
scala> showMessages(array)
<console>:10: error: type mismatch;
 found   : Array[java.lang.String]
 required: String
              showMessages(array)

 「Array[String]」型の変数を直接渡したい場合、引数の後に「:」を付加し、スペースを入れて「_*」と指定してください。こうすることで「Array[String]」を1つの引数として渡すのではなく、「Array[Stirng]」の要素が個々の引数として渡されます。

scala> showMessages(array: _*)
hello
Scala

 なおList型の変数を使用しても、同じように渡せます。

Scalaの関数には「デフォルト引数」が使える

 Scalaでは、関数の引数にデフォルト値を指定可能です。デフォルト値が指定してある場合、その引数は省略できます。

 例では、引数で指定した文字列を指定した回数分表示します。両方ともデフォルト値が指定してあるので、引数を渡さずに呼び出せますし、引数を指定して呼び出すこともできます。

scala> def show(message:String = "hello", count:Int = 1) = {
     |  var i = 0
     |  while(i < count) {
     |   println(message)
     |   i += 1
     |  }
     | }
show: (message: String, count: Int)Unit
 
scala> show()
hello
 
scala> show(count = 2)
hello
hello
 
scala> show(message = "bye",count = 3)
bye
bye
bye

関数リテラルの記述を単純にする「プレイスホルダ構文」

 「関数を『オブジェクト』として扱うには」では、関数リテラルを「引数名:型名」という形式で引数を定義し、関数本体で使用していました。

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

 しかし、引数が関数リテラルの中で一度しか使用されない場合、「プレイスホルダ構文」を使用することで、この関数リテラルの記述を、もっと単純にできます。以下の例では、プレイスホルダ構文を使用し、引数名を「_」にして使用しています。

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

 プレイスホルダ構文を使用するには、引数を「_」(プレイスホルダを示す記号)として推論できるだけの情報が必要です。関数オブジェクトの型を指定するか、下のように関数リテラル内でプレイスホルダの型を指定すれば使用できます。

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

「部分適用」された関数とは

 「部分適用」された関数とは、引数の一部分だけをパラメータ化した関数オブジェクトのことです。引数を2つ取る関数があったとして、その内の1つの引数を指定した状態の新しい関数オブジェクトを作れます。

 実際に例を見てみましょう。まずは、defで関数を定義し、それを関数オブジェクトに変換します。

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

 defで定義した関数を「_」付きで呼び出すと、関数オブジェクトに変換できます。

 次に、func変数の第2引数に5を指定し、部分適用しています。

scala> val func2 = func(_:Int, 5)
func2: Int => Int = <function1>
 
scala> func2(2)
res37: Int = 7

 第1引数に「_:Int」と指定して部分適用していますが、部分適用した関数の型を明示的に指定すれば、下記ようにも記述できます。

scala> val func3:(Int) => Int = func(_, 5)
func3: Int => Int = <function1>

関数リテラルを閉じる「クロージャ」

 「クロージャ」とは、その関数のスコープにおける引数以外の変数を参照できる関数です。関数内で使用する引数以外の変数を、関数を定義した境界(静的スコープレキシカルスコープ)から参照可能で、その変数の生存期間はクロージャと同じです。

 例として呼び出すたびに値がインクリメントされる「counter」関数を作成してみましょう。

scala> def counter() = {
     |   var count = 0
     |   () => {
     |     count += 1
     |     count
     |   }
     | }
 
counter: ()() => Int

 counter関数は「その関数内で定義された変数を1増やす関数」を返します。その関数をc1変数に代入し、何度か呼び出してみましょう。

scala> val c1 = counter()
c1: () => Int = <function0>
 
scala> c1()
res50: Int = 1
 
scala> c1()
res52: Int = 2

 「c1」関数を呼び出すたびに、「counter」関数内で定義された「count」変数がインクリメントされています。counter関数はcount変数を取り込み、関数リテラルを閉じるので「クロージャ」といいます。counter関数は何度でも呼び出し、そのたびに新しいカウンタが生成されます。

scala> val c2 = counter()
c2: () => Int = <function0>
 
scala> c2()
res53: Int = 1
 
scala> c1()
res54: Int = 3

関数の中で関数を定義する「ローカル関数」

 プログラムは、なるべく小さく分割し、個々の役割を明確化した方が分かりやすくなります。しかし、関数を細かく分割し過ぎると、クラス内が多くのヘルパ関数であふれてしまいます。汎用的な関数であれば、まだいいのですが、特定の関数内でしか使用できない場合、たとえ、それがprivateメソッドだとしても冗長になります。

 Scalaでは、関数の中で関数を定義できるので、そういった場合に、ほかの関数から「ヘルパ関数」を隠すことが可能です。

 例では、printLangローカル関数を定義し、showLanguagesに渡されたリスト内容を順番に表示しています。

scala> def showLanguages(title:String,langList:List[String]) = {
     |   def printLang(item:String) = {
     |     println(title + ":" + item)
     |   }
     |
     |   for(lang <- langList) printLang(lang)
     | }
 
showLanguages: (title: String, langList: List[String])Unit
 
scala> showLanguages("Programming Language",List("Java","Scala","Ruby"))
Programming Language:Java
Programming Language:Scala
Programming Language:Ruby

 printLangローカル関数の中では、title変数にアクセスしています。このように、ローカル関数ではその外側の関数に渡された引数にアクセス可能です。

次回は、Scalaのオブジェクト指向的側面

 今回はパターンマッチの使い方と、関数のさまざまな使用方法も紹介しました。特に、Scalaにおける「関数」は、非常に重要です。関数の定義方法や関数オブジェクトの使い方、クロージャや部分適用も利用する機会は多いと思います。

 なお、今回紹介しきれなかった機能もありますが、今後の連載の中で随時紹介していきます。次回は、Scalaのオブジェクト指向的側面を紹介する予定です。

筆者紹介

クラスメソッド株式会社

中村修太(なかむら しゅうた)

クラスメソッド勤務の新しもの好きプログラマーです。昨年、東京から山口県に引っ越し、現在はノマドワーカーとして働いています。好きなJazzを聴きながらプログラミングするのが大好きです。



前のページへ 1|2|3       

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

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

メールマガジン登録

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