連載
» 2012年10月04日 00時00分 UPDATE

スケーラブルで関数型でオブジェクト指向なScala入門(11):カリー化、遅延評価などScalaの文法総まとめ&今後 (1/3)

Scalaの特徴を紹介し、基本構文や関数、クラスなど、Scalaの基本的な機能について解説する入門連載

[中村修太,クラスメソッド]

前回のおさらいと今回の内容

 前回の記事「Scalaの並行処理とアクター、並列コレクション」では、Scalaが標準で持つ「並行処理ライブラリ」と「並列コレクション」の基本的な使い方を紹介しました。

 今年2月から10回に渡ってやってきた「スケーラブルで関数型でオブジェクト指向なScala入門」も、いよいよ今回が最終回です。今回は、いままでの連載で紹介しきれなかった内容や、今後のScalaを紹介します。

 第1回記事「EclipseでScalaプログラミングを始めるための基礎」では、Scala標準のREPLScala IDEで動作を確認してみました。本記事のサンプルコードは、どちらで確認しても問題はありませんが、対話的に実行でき、1文ごとにコードの結果が分かって便利なので、基本的にはREPLを用いて説明していきます。

 Scala IDEを使用する場合、第1回記事の『Scala IDE for Eclipseで「Hello Scala!」』を参照してプロジェクトを作成し、実行してください。REPLを使用する場合は、コンソール上でscalaコマンドを実行し、REPLを起動してください。

関数の名前付き引数

 連載第4回「基本的なパターンマッチとScalaで重要な“関数”」で紹介した通常の「関数呼び出し」においては、渡された引数は関数が定義しているパラメータの順番に渡されます。

scala> def division(x:Int, y:Int) = x / y
division: (x: Int, y: Int)Int
 
scala> division(50,10)
res3: Int = 5

 上記の関数では、1番目の引数がx、2番目の引数がyに代入されます。しかし、名前付き引数を使うことで、関数呼び出し時に引数名を指定して呼び出せます。

 名前付き引数を使うときは、関数呼び出し時に「引数名 = 値」の形式で引数を指定します。こうすることで、引数の順番を任意の順番に変えられます。

scala> division(y = 10, x = 50)
res8: Int = 5

 1番目にy(2番目の引数)へ10を代入、2番目にx(1番目の引数)に50を代入しています。結果は同じになっていますね。引数が多い場合などでは、名前付き引数を使うことでプログラムが分かりやすくなります。

 また、下記のように、デフォルト引数と組み合わせて使えることも多いです。

scala> def birth(year:Int,month:Int,day:Int,msg:String = " Your BirthDay is ") = { 
         msg + "%d/%d/%d".format(year,month,day)
       }
 
scala> birth(2012,10,5)
res6: java.lang.String = Your BirthDay is 2012/10/5

 途中まで引数順に値を記述し、残りの引数を名前指定にすることも可能です。

scala> birth(1980,day=5,month=10)
res12: java.lang.String = Your BirthDay is 1980/10/5

 メソッドの引数すべてに初期値が指定されている場合、任意の部分だけ値を上書きして呼び出せます。

scala> def birth(year:Int = 1980,month:Int = 1,day:Int = 1,msg:String = " Your BirthDay is ") = { 
         msg + "%d/%d/%d".format(year,month,day)
       }
 
scala> birth()
res6: java.lang.String = Your BirthDay is 2012/10/5
 
scala> birth(month=8)
res16: java.lang.String = " Your BirthDay is 1980/8/1"

 名前付き引数をよく利用するケースとしては、下記のようにcopyメソッドを使ってcase classの一部プロパティだけ変更した新しいインスタンスが欲しい場合に使えます。

scala> case class X(a:Int,b:String)
defined class X
scala> val x = X(1,"hello")
x: X = X(1,hello)
scala> val x2 = x.copy(b = "copy hello") //bだけ違う値にしたインスタンスを作成
x2: X = X(1,copy hello)

関数の部分適用を簡単にできる「カリー化」

 連載第4回で、「関数の部分適用」を紹介しました。部分適用は、複数の引数の中で一部分だけ具体的な値を与え、残りは穴になっているような関数に変換することです。

「関数の部分適用」を復習

 ここで部分適用について、もう1度確認してみましょう。sum関数は3つの引数をとり、それらを足した値を返します。

scala>  def sum(a:Int,b:Int,c:Int) = a + b + c
sum: (a: Int, b: Int, c: Int)Int
 
scala> sum(1,2,3)
res10: Int = 6

 sum関数を関数オブジェクトに変換して使ってみましょう。こちらも問題なく使えます。

scala>  val fObj = sum _ //関数オブジェクトに変換
fObj: (Int, Int, Int) => Int = <function3>
 
scala> fObj(1,2,3)
res20: Int = 6

 別の変数に代入する際に「sum(_:Int,2,3)」のように、一部の引数だけ値を入れて、一部の引数を未定にしておくことが可能です。これが関数の部分適用です。

scala> val fObj2 = sum(_:Int,2,3) //部分適用
fObj2: Int => Int = <function1>
 
scala> fObj2(1)
res22: Int = 6

「カリー化」とは

 次にカリー化について紹介します。カリー化とは「引数が元の関数の最初の引数で、残りの引数を取って結果を返す関数を返す」ことです。

 具体的にいうと、「(A, B) => C」(AとBという引数をとり、Cを返す)という関数を「A => B => C」(Aを引数にとり、「Bを引数にとってCを返す関数」を返す)という関数に変換することです。仮に、この「A => B => C」型の関数をfとした場合、以下のようにした際にB=>C型の関数を取得できます。

f(a) ※aはA型

 例を見てみましょう。先ほど定義したsum関数は、以下のいずれかのように定義できます。

def sum(a:Int)(b:Int)(c:Int) = a+b+c
def sumCurry(a:Int) = (b:Int) => (c:Int) => a + b + c

 Scalaでは引数リストを複数持つことができます。最初のsum関数は引数1つずつを()で区切って記述しています。部分適用するときは、以下のいずれかのように記述します。

sum(1)_
sum(1)(_:Int)(_:Int)

 勘違いしやすいのですが、これはカリー化とは関係ありません。sum関数が複数の引数リストを持っているだけです。

scala> def sum(a:Int)(b:Int)(c:Int) = a+b+c
sum: (a: Int)(b: Int)(c: Int)Int
 
scala> val fObj = sum(1)_ //もしくは val fObj = sum(1)(_:Int)(_:Int) 
fObj: Int => Int => Int = <function1>
 
scala> val fObj2 = fObj(2)
fObj2: Int => Int = <function1>
 
scala> fObj2(3)
res25: Int = 6

 以下は、sumCurry関数がカリー化した状態です。引数を1つとると、引数を1つとる関数を返すようになっています。sumCurry関数は以下のように使えます。

scala> def sumCurry(a:Int) = (b:Int) => (c:Int) => a + b + c
sumCurry: (a: Int)Int => Int => Int
 
scala> val fObj1 = sumCurry(1)
fObj1: Int => Int => Int = <function1>
 
scala> val fObj2 = fObj1(2)
fObj2: Int => Int = <function1>
 
scala> fObj2(3)
res26: Int = 6

 カリー化しており、引数が1つなので、呼び出すときに後ろに「_」を付ける必要はありません。また、「sumCurry(1)(2)(3)」のように、チェインさせて1度に呼び出すこともできます。

普通の関数をカリー化して使うには

 普通の関数をカリー化して使うこともできます。「_」で関数を関数オブジェクト化し、「curried」メソッドでカリー化します。

scala> def sum(a:Int,b:Int,c:Int) = a + b + c
sum: (a: Int, b: Int, c: Int)Int
 
scala> val currySum = (sum _).curried
currySum: Int => Int => Int => Int = <function1>

複数のパラメータをとる関数を1つのパラメータをとる関数のチェーンに変換

 いかがでしょうか。少々ややこしい部分もありますが、「カリー化とは、複数のパラメータをとる関数を1つのパラメータをとる関数のチェーンに変換する」方法であることが分かったかと思います。

 カリー化を行うと部分適用を簡単にできますが、0番目以外の任意の場所の引数だけを与えるのはカリー化だけでは扱えないので、場合に応じて使い分けてください。

       1|2|3 次のページへ

Copyright© 2017 ITmedia, Inc. All Rights Reserved.

@IT Special

- PR -

TechTargetジャパン

この記事に関連するホワイトペーパー

RSSについて

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

メールマガジン登録

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