Scala(スカラ)の紹介 (上)


こんにちは、k_oomoriです。今回は、タイトルの通りプログラミング言語Scalaについて書いてみたいと思います。

1/俯瞰風景

 早速ですが、Scalaの特徴を俯瞰していきましょう。

・オブジェクト指向と関数型のハイブリッド言語である

 Scalaは、関数型言語とオブジェクト指向(命令型)言語の特徴を統合したマルチパラダイムなプログラミング言語です。どちらのスタイルを用いるかはプログラマに委ねられており、関数型言語にすでに親しんでいる人はvalやイミュータブル(不変)オブジェクトを用いて関数型の記述を行えばいいし、命令型の方が適している問題に対してはvarとミュータブル(可変)オブジェクトによる命令型の書き方ができます。

・静的 型月 型付け言語である

 静的な型システムを持っているため、コンパイル時(というかIDEの力を借りればコーディング時)に型に関するエラーを確実に検出できます。さらに強力な型推論システムも併せ持っているため、至る所に型情報を書いて回ったりする手間はかかりません。

・Javaと容易に連携できる

 ScalaプログラムはコンパイルするとJVMバイトコードに変換され、JVM上で動作します。そのため、既存のJavaコードと容易に共存させることができます。実際、Scalaの文字列はScala独自のものではなく、java.lang.Stringをそのまま用いているそうです。また、Scala用ライブラリが提供されていなくても、Java用のライブラリがあればそれをそのまま使うことができます。(例:AWS SDK for Java)

・Scalaの構文要素は値を返す、つまり式である

 ここから具体的なScalaコードを使って話を進めていきますが、Scalaの基本的な文法に関する説明はここでは省かせていただきます。そのあたりはやはりScalaの設計者であるOdersky氏(@odersky)らによる”Scalaスケーラブルプログラミング”(通称コップ本)[1]がバイブル的な位置づけになります。Scalaのインストールについては日本語情報サイトのインストールページなどを参考に、ご自身の環境に合ったものを入れてください。なお、本記事のサンプルはScala version 2.10.2で実行しています。
 閑話休題。プログラミング言語における構文要素には文(Statement)と式(Expression)があります。文は手続きを表すもので結果値を持たないのに対し、式は評価された値を持ちます。この意味において、Scalaでは定義文以外のものは全て式であるということが言えます。例えば、ifも値を返すので次のような記述が可能になります。

scala> val str = "hoge"
str: String = hoge

scala> val result = if (str.nonEmpty) str else "default"
result: String = hoge

従ってこれは「if文」ではなく「if式」と呼ばれます(これが可能なため、PHPやJavaなどでよく使われる三項演算子 条件?値1:値2 はScalaには存在しません)。同様にforも「for文」ではなく「for式」です。このようにScalaではほとんどのものが式です。ではなぜそうなっているかというと、それが関数型プログラミングのスタイル-引数を受け取って、結果値を返す-だからです。つまり、「Scalaの構文要素は全て式である」という特徴はScalaの関数型言語としての側面の表れであると言えます。

・演算子はメソッドであり、メソッドも演算子になれる

 ScalaにはJavaのようなプリミティブ型はなく、IntやBoolean等も含めて全てがクラスとして実装されています。これはScalaがオブジェクト指向言語で(も)あるという事実を表しています。そのため整数リテラルからも次のように直接メソッドを呼ぶことができます。

scala> 1.toString
res0: String = 1

この特徴を利用して、Scalaでは演算子(+, *など)は全てメソッドとして実装されています。つまり、整数同士の足し算 5 + 6 は、実際には (5).+(6)、つまり5というInt型オブジェクトの+メソッドを呼び出し、引数にIntの6を与えているのです。同様に、&&も||も==も全てBool値を返すメソッドです。
 逆に、通常のメソッドも演算子記法で書くことが可能です。例えば list.contains(1) はまるで通常の英語のように list contains 1 と書くことができます。ただし、複数の引数を取る場合には括弧を省略することができません。

scala> "substring" substring (3,9)
res1: String = string
・いろいろな文字がメソッド名に使える

 上で見たように、算術演算子の正体はメソッドです。つまり、Intクラスには次のようなシグネチャのメソッドが定義されています。(一部のみ抜粋)

def +(x: Int): Int
def -(x: Int): Int
def *(x: Int): Int
def /(x: Int): Int
def %(x: Int): Int
def <(x: Int): Boolean
def >(x: Int): Boolean
・・・

もちろん自分で作ったクラスにも+や*を定義することができるので、コップ本で例示されているRational(有理数)クラスのような自前のクラスの間の演算でもx.add(y)やx.multiply(y)ではなくx + y, x * yのように書くことができるようになります。
 他にも:?~!#&などは全て演算子識別子を構成するのに有効な演算子文字です。例えば、よくある問題として、与えられたURLの末尾にGETパラメータを付与する際、すでに?が含まれていれば&で、なければ?でつなぐということを誰しも一度はやったことがあると思います。Scalaではこの判断を行うメソッドに ?& のような名前を付けて定義することができます。

scala> class StringUtil(str: String) {
     |   def ?&(params: String): String = str + (if (str contains "?") "&" else "?") + params
     | }
defined class StringUtil

さらにStringからStringUtilへの暗黙の型変換(implicit conversion)を定義しておけば、あたかもStringのメソッドであるかのように呼び出すことができます。

scala> implicit def srtToStrUtl(str: String) = new StringUtil(str)
srtToStrUtl: (str: String)StringUtil

scala> "http://example.net/" ?& "foo=bar&x=1"
res2: String = http://example.net/?foo=bar&x=1

scala> "http://example.net/?user=hoge" ?& "foo=bar&x=1"
res3: String = http://example.net/?user=hoge&foo=bar&x=1

なお、通常の英数字の後に記号を付ける場合、アンダースコア(_)を挟まなければなりません。

scala> def exists?(): Boolean = true
<console>:1: error: '=' expected but identifier found.
       def exists?(): Boolean = true
                 ^

scala> def exists_?(): Boolean = true
exists_$qmark: ()Boolean

 さらに、Java同様Scalaでも識別子にUnicodeを使用できるので、メソッド名に日本語のようなマルチバイト文字を使うことも可能です。その上、「使用が可能な」だけでなく、あらかじめ用意されているマルチバイト識別子すらあるのです!それは→,⇒,←で、それぞれ->,=>,<-に対応します。従って次のようなコードも普通にコンパイラを通り、実行することができちゃいます。

scala> def 無駄に計算(): List[Double] = {
     |   for (i ← List(1,2,3,4,5)) yield (x ⇒ Math.sqrt(x))(Map(i → (i*i)).apply(i))
     | }
無駄に計算: ()List[Double]

scala> 無駄に計算
res4: List[Double] = List(1.0, 2.0, 3.0, 4.0, 5.0)

なお、メソッド名だけでなくクラス名、変数名にも日本語を使うことができるため、日本語によるプログラミングも可能です[3]。業務案件で積極的に使うべきか、と問われるとたぶんNoですが…。

・メソッド名の末尾がコロン(:)だと…

 コロンも有効な演算子文字だという話をしました。しかし、演算子名の末尾がコロンだった場合、メソッド呼び出しの元となるオブジェクト(レシーバーといいます)と引数の位置が逆転します。つまり、a + bはa.+(b)の意味でしたが、a :: listはlist.::(a)になるのです。ちなみに::(cons)はListクラスで定義されたメソッドで、既存リストの先頭に要素を追加した新たなリストを返します(ScalaのListはimmutable(不変)なので、操作によって元のリストが変更を受けたりはしません)。なぜこのルールが採用されたかについてはコップ本にも特に言及はなさそうなのですが、おそらく「リストの先頭に追加する感」を出したかったのではないかと推測されます。なお、末尾が:になっているメソッドは::の他には:::(2つのリストを結合したリストを返す)や/:(fold left、左畳み込み)などがあります。

・リテラルの種類が豊富

 整数リテラル1、浮動小数点リテラル3.14F、文字リテラル’A'、文字列リテラル”scala”、論理値リテラルtrue、Nullリテラルnull、配列リテラルArray(1,2,5,3)などは特に説明不要と思いますが、Scalaには他にもいくかのリテラルが存在します。まずはシンボルリテラル。

scala> val s = 'symb
s: Symbol = 'symb

シングルクォートを使うことはCharと一緒ですが、なんと閉じていない!このリテラルは、実際には以下のようなscala.Symbolのファクトリーメソッド呼び出しに展開されているそうです。

scala> Symbol("symb")
res5: Symbol = 'symb

nameメソッドを使うとそのシンボルの名前を文字列として取り出すことができます。

scala> s.name
res6: String = symb

では文字列と何が違うかというと、文字列では同じ内容でもインスタンスとしては異なる場合があるのに対し、シンボルは同じシンボルリテラルを複数書いても全く同一のSymbolオブジェクトを参照することが保証される、とのことです。

scala> val str1 = new String("A")
str1: String = A

scala> val str2 = new String("A")
str2: String = A

scala> str1 eq str2
res7: Boolean = false
---------------------------------
scala> val symb1 = Symbol("A")
symb1: Symbol = 'A

scala> val symb2 = Symbol("A")
symb2: Symbol = 'A

scala> symb1 eq symb2
res8: Boolean = true

ここでeqはインスタンスとして同一かどうかを判定するメソッドで、Javaのオブジェクト型に対する==に相当します。ただ、Scalaで文字列を比較する場合は普通==で「値として」同じかどうかを判定するので、Symbolの必要にかられた経験は今のところないです…。
 次はXMLリテラルです。Scalaではなんと(そろそろみなさんこんなことでは驚かなくなってきていると思いますが)XMLもプログラム中に直接書くことができます!

scala> val xml = <languages>
     |   <language>
     |     <name>PHP</name>
     |     <extension>php</extension>
     |   </language>
     |   <language>
     |     <name>Scala</name>
     |     <extension>scala</extension>
     |   </language>
     | </languages>
xml: scala.xml.Elem =
<languages>
  <language>
    <name>PHP</name>
    <extension>php</extension>
  </language>
  <language>
    <name>Scala</name>
    <extension>scala</extension>
  </language>
</languages>

textメソッドでタグを取り除いたり、\\メソッド(!)で要素を抽出したり、toStringメソッドで文字列に変換したりできます。

scala> xml.text
res9: String = PHPphpScalascala

scala> xml \\ "name"
res35: scala.xml.NodeSeq = NodeSeq(<name>PHP</name>, <name>Scala</name>)

scala> xml.toString
res37: String =
<languages>
  <language>
    <name>PHP</name>
    <extension>php</extension>
  </language>
  <language>
    <name>Scala</name>
    <extension>scala</extension>
  </language>
</languages>

一つ注意しなくてはいけないのは、2つの変数の大小を比較するとき、次のように書いてはいけません。

scala> b <a
<console>:1: error: ';' expected but $XMLSTART$< found.
       b <a
         ^

もうお気付きのことと思いますが、aタグリテラルが開始されたまま閉じてないと認識されてエラーになっています。
 Scalaの関数は多くの場合決まった型の結果値を返しますが、副作用を起こすことだけが目的で特に結果値を返す必要のない関数・メソッドもあります。この場合の結果型はUnitとなり、「意味のある値を返さない」ことを意味します。Javaのvoidのようなものではありますが、値がないわけではなく、ScalaのUnit型はただ一つの値「()」を持っています。プログラム中に()と書けばそれはUnitリテラルになりますが、まず役に立つことはないでしょう。一方、クラス階層の最下層、つまり全てのクラスのサブクラスとしてNothingクラスがあるのですが、これには本当に値がありません。
 他にも関数リテラルというものもあるのですが、それは次の章で説明します。
 最後に、これは文字列リテラルに含まれるのですが、生の文字列(raw strings)と呼ばれるものがあり、ダブルクォートを3個続けて(“”")書きます。他の言語で言うところのヒアドキュメントみたいなものと思えばよいでしょう。この中ではダブルクォートや\などをエスケープせずにそのまま書くことができます。よって

scala> """"""" == "\""
res10: Boolean = true

となります。

・Tuple22問題が存在する

 ListやMapのようなコレクションには、特定の型の要素しか持たせることができません。1と”2″を要素に持つListを作成しようとすると、その型はList[Any]になってしまいます。

scala> List(1,"2")
res11: List[Any] = List(1, 2)

これを元の型を維持しつつまとめて保持したいと思ったときに便利なのがタプル(Tuple)です。

scala> val tpl = (1, "A", List(1,2), true, 'xyz)
tpl: (Int, String, List[Int], Boolean, Symbol) = (1,A,List(1, 2),true,'xyz)

これは実際のところTuple5というケースクラスのファクトリーメソッドを呼んでいることに相当しています。

scala> Tuple5(1, "A", List(1,2), true, 'xyz)
res12: (Int, String, List[Int], Boolean, Symbol) = (1,A,List(1, 2),true,'xyz)

しかし、Scala 2.10時点では23個以上のパラメータを持つケースクラスを定義することができません。

scala> case class Hoge(a1: Int, a2: Int, a3: Int, a4: Int, a5: Int, a6: Int, a7: Int, a8: Int, a9: Int, a10: Int, a11: Int, a12: Int, a13: Int, a14: Int, a15: Int, a16: Int, a17: Int, a18: Int, a19: Int, a20: Int, a21: Int, a22: Int, a23: Int)
<console>:7: error: Implementation restriction: case classes cannot have more than 22 parameters.
       case class Hoge(a1: Int, a2: Int, a3: Int, a4: Int, a5: Int, a6: Int, a7: Int, a8: Int, a9: Int, a10: Int, a11: Int, a12: Int, a13: Int, a14: Int, a15: Int, a16: Int, a17: Int, a18: Int, a19: Int, a20: Int, a21: Int, a22: Int, a23: Int)
                  ^

この制限は当然タプルにも受け継がれ、Tuple23以上は存在しません。(※Scala 2.11ではこの数が大幅に増えるという噂も…)まあ普通はこんなに使うことはないんですが、データベースのレコードをタプルにマッピングする実装では(テーブルはカラムごとに型が異なるのが一般的なのでタプルが便利!)、カラムが23個以上あるテーブルを扱うことができなくなってしまいます。このようにTuple22までしかないことに起因する困難は「Tuple22問題」と呼ばれ、Scala界隈ではかなり有名なようです。
 同様に関数についても”Function22問題”が存在するのですが、23個以上の引数をフラットに持つ関数を定義しなければならない状況はあんまりなさそうなので、こちらはそれほど表面化しないような気がします。
 なお、タプルから値を取り出す方法は主に2通りあります。

scala> val (a,b,c,d,e) = tpl
a: Int = 1
b: String = A
c: List[Int] = List(1, 2)
d: Boolean = true
e: Symbol = 'xyz

scala> tpl._3
res13: List[Int] = List(1, 2)

前者は特に説明の必要はないと思います。後者は._NでN番目の要素にアクセスしているのですが、Nは0ではなく1スタートであることに注意してください。これはコップ本(第2版では67ページ)で解説されているように、HaskellやMLなど関数型言語の伝統を受け継いだ結果だそうです。
 またタプルはimmutableなので、一度アサインされた要素を書き換えることはできません。(varにすると全体の再代入はできるようになりますが各要素ごとの書き換えは不可能です)

2/関数考察()

 プログラミング言語においてfirst-class object(日本語では”第一級オブジェクト”とか”一人前の存在”とか訳しているようです)とは、生成、代入、演算、引数・戻り値としての受け渡しなどその言語における基本的な操作ができる対象のことで、文字列や数値などはその代表的なものです。関数型言語においては関数も第一級オブジェクトとして扱えること、即ち第一級関数の存在が必要不可欠になります[4]。関数は通常、

def 関数名(引数1: 引数1の型, 引数2: 引数2の型, ...): 戻り値の型 = 関数本体

と定義しますが、このままではこの関数を別の関数に引数として渡したり、変数に格納したりすることはできません。PHPなどでは

function doSomething($method) {
    $method();
}
doSomething('some_method');

とすれば some_method() 呼び出しを実現できますが、動的言語ではないScalaでは引数部分にdefで定義した関数名を渡せないのです。ではどうするかというと、関数値を使います。その関数値の設計図とも言えるものが関数リテラルです。

・関数リテラル、関数値とは

 関数リテラル(function literals)とはいわゆる無名関数を表すリテラル式のことで、具体例を挙げると

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

これはxとyという2つの整数引数を取り、それらの和(x+y)を返す「関数」です。”hoge”は文字列リテラルでその型がStringであるのと同様に、(x: Int, y: Int) => x + yは関数リテラルでその型は(Int, Int) => Intになります。値が<function2>となっているのは、Function2トレイト(引数を2個取る関数のインターフェイス的なもの)を継承したクラスのインスタンスであることを意味しています。(できないことを承知で)Function2トレイトをnewしてみると、

scala> new Function2[Int, Int, Int] {}
<console>:8: error: object creation impossible, since method apply in trait Function2 of type (v1: Int, v2: Int)Int is not defined
              new Function2[Int, Int, Int] {}
                  ^

applyメソッドが未定義だと怒られましたので実装してみます。

scala> new Function2[Int, Int, Int] {def apply(x: Int, y: Int): Int = x + y}
res15: (Int, Int) => Int = <function2>

インスタンス生成に成功し、型が(Int, Int) => Intになりました。実際、(x: Int, y: Int) => x + yは上記Function2を用いた書き方のシンタックスシュガーのようです。
 さて、これは関数なので次のように適用することが可能ですが、見るからに不便です。

scala> ((x: Int, y: Int) => x + y)(2,4)
res16: Int = 6

scala> (new Function2[Int, Int, Int] {def apply(x: Int, y: Int): Int = x + y}).apply(2,4)
res17: Int = 6

後者の書き方からわかるように、関数リテラルは実行時にはオブジェクトになっているため、変数に格納することが可能です。すると、自然な形で関数を呼び出すことができるようになります。

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

scala> add(2,4)
res18: Int = 6

このような関数オブジェクトのことを値としての関数または関数値(function values)といいます。
 ではこれをどのようにして「別の関数に引数として渡す」のか、例を用いて見てみましょう。ここでは与えられたURLのリストの中から、ある条件(パターンに対して前方一致や正規表現など)に合致するものを取り出す関数を考えます。

scala> def filterUrl(urlList: List[String], pattern: String, howToMatch: (String, String) => Boolean): List[String] =
     |   for (url <- urlList if howToMatch(url, pattern)) yield url
filterUrl: (urlList: List[String], pattern: String, howToMatch: (String, String) => Boolean)List[String]

第3引数の型が(String, String) => Boolean、つまり文字列型引数を2つ取って論理値を返す関数となっています。これは次のように使います。

scala> val urlList = List("http://www.example.net/index.html", "http://www.example.net/login.php", "http://example.net/company.html")
urlList: List[String] = List(http://www.example.net/index.html, http://www.example.net/login.php, http://example.net/company.html)

scala> filterUrl(urlList, "http://www.example.net/", (x, y) => x.startsWith(y))
res19: List[String] = List(http://www.example.net/index.html, http://www.example.net/login.php)

startsWithは、レシーバー文字列が引数文字列で始まっていればtrueを、そうでなければfalseを返すメソッドです。この例ではURLが http://www.example.net/ で始まるものを抽出するという意味になっており、実際wwwサブドメインでないものは除かれています。このような、引数に関数をとる関数のことを高階関数(higher-order functions)といいます。
 ところで (x, y) => x.startsWith(y) を見るとx,yという名前に特に意味はなく、(foo, bar) => foo.startsWith(bar)でもなんでもいいことがわかるでしょう。この場合、Scalaでは_(アンダースコア)をプレースホルダとして用い、 _.startWith(_) と書くことができます。演算子記法で書くと _ startsWith _ です。

scala> filterUrl(urlList, "http://www.example.net/", _ startsWith _)
res20: List[String] = List(http://www.example.net/index.html, http://www.example.net/login.php)

ただし、各変数が高々1回までしか使われない時に限ります。_には番号がついていないため、複数回使われるとどこに入れたらよいかわからなくなるからです。
 ではここで仕様が変わり、前方一致ではなく正規表現でマッチさせるようにしなければならなくなったとします。この場合でもfilterUrl関数自体に修正を加える必要はなく、引数として与えるパターンや関数値を別のものにすればいいだけです。

scala> filterUrl(urlList, """http://(.*)example\.net/(.+)\.html""", _ matches _)
res21: List[String] = List(http://www.example.net/index.html, http://example.net/company.html)

この例ではexample.netとその任意のサブドメインにおいて拡張子がhtmlとなっているURLを正規表現で抜き出しています。同様に部分一致なら_ contains _、完全一致なら _==_ にするだけです。これなら部長から急に「URLのバリデーションルール変えたいんだけど」って言われてもすぐに対応できますね!
 このようにリテラルとして書き下したり、変数への代入や引数・戻り値として扱うことができるので、Scalaの関数値は第一級オブジェクトです。
 ここまで関数値を見てきましたが、ではdefで定義した関数とはどのような関係があるのでしょうか?それを見るために、関数の部分適用という概念について説明します。

・関数への部分適用

 上で定義したfilterUrlは、パターン文字列もそのマッチの仕方も引数として与えることができるという意味でそれなりに汎用です。一方で、検査したいリストはたくさんあるものの検査方法は同じ(http://example.net/ で前方一致、とか)というケースもあるでしょう。その場合、同じ値を何度も渡さなければならないのは面倒なので、特定のマッチング方式に特化した関数を生み出せると便利です。それができるのが関数の部分適用と呼ばれるテクニックで、次のように使います。

scala> def filterStartsWithExample = filterUrl(_: List[String], "http://example.net/", _ startsWith _)
filterStartsWithExample: List[String] => List[String]

scala> filterStartsWithExample(List("http://example.net/index.php", "http://example.com/"))
res22: List[String] = List(http://example.net/index.php)

つまり、第1引数を_で未適用のまま残しておき、それ以外の引数には具体的な値を与えるのです。その結果得られるものは引数を1つだけ取る関数値になります。一般に、n引数関数のうちm個(0≦m<n)だけに値を与えたものを部分適用された関数(partially applied function)と呼びます。※1
 では、defで定義したfilterUrlに引数を0個適用してみましょう。

scala> val f = filterUrl _
f: (List[String], String, (String, String) => Boolean) => List[String] = <function3>

filterUrlと同じ個数、同じ型の引数を取る関数値が得られました。この章の最初で挙げた、defで定義された関数を別の関数に渡したりできない問題は、このように部分適用(本当は0個なので適用してないけど)によって関数値に変換するというテクニックによって解決できます。

・カリー化

 次はPHPやJavaなどではあまり聞き慣れないと思われる、カリー化(currying)についてです。Wikipedia[5]によるとカリー化とは

『複数の引数をとる関数を、引数が「もとの関数の最初の引数」で戻り値が「もとの関数の残りの引数を取り結果を返す関数」であるような関数にすること』

とありますが、これですぐに「なるほど!」と思える人はあまりいないと思われます。幸い、FunctionNトレイトにはカリー化を行うメソッド curried が定義されているので、四の五の言わずに試してみましょう。

scala> def func3 = (a:Int, b:String, c:Boolean) => a.toString + b + c.toString
func3: (Int, String, Boolean) => String

scala> func3.curried
res23: Int => (String => (Boolean => String)) = <function1>

scala> func3.curried(0)
res24: String => (Boolean => String) = <function1>

scala> func3.curried(0)("A")
res25: Boolean => String = <function1>

scala> func3.curried(0)("A")(true)
res26: String = 0Atrue

つまり、もともと1個のパラメータリスト(パラメータ数は3つ)だったものを3個のパラメータリスト(パラメータ数はそれぞれ1個)にばらした格好になっており、パラメータを1つ与えるたびに残りのパラメータを引数とする関数が現れてきて、最終的に全てのパラメータを与えたときに一つの値が得られる、そんなイメージです。
 上記の例ではカリー化されていない関数からcurriedメソッドによってカリー化された関数を導きましたが、def形式を用いて最初から以下のように表現することもできます。

scala> def func111(a:Int)(b:String)(c:Boolean): String = a.toString + b + c.toString
func111: (a: Int)(b: String)(c: Boolean)String

scala> func111 _
res27: Int => (String => (Boolean => String)) = <function1>

scala> func111(1)_
res28: String => (Boolean => String) = <function1>

scala> func111(1)("B")_
res29: Boolean => String = <function1>

scala> func111(1)("B")(false)
res30: String = 1Bfalse

 ここでタイムスタンプとフォーマット文字列を与えるとそれに応じた時刻文字列を返す以下の(未カリー化)関数を考えます。

scala> def formatTimestamp(timestamp: Long, format: String) = format.format(new java.util.Date(timestamp))
formatTimestamp: (timestamp: Long, format: String)String

scala> formatTimestamp((new java.util.Date).getTime, "%tF %<tT")
res31: String = 2014-03-02 00:45:03

今、与えられた一つのタイムスタンプに対し、それをいろいろなフォーマットで取得したいという要望があったとします。その場合、次のようなカリー化された関数を用意するとよりスマートに対応できます。

scala> def formatTimestampCurried(timestamp: Long)(format: String) = format.format(new java.util.Date(timestamp))
formatTimestampCurried: (timestamp: Long)(format: String)String

scala> val specializedFormatter = formatTimestampCurried((new java.util.Date).getTime)_
specializedFormatter: String => String = <function1>

scala> specializedFormatter("%tF %<tT")
res32 String = 2014-03-02 00:47:52

scala> specializedFormatter("%tY/%<tm/%<td(%<ta)")
res33: String = 2014/03/02(日)

scala> specializedFormatter("%tY/%<tm/%<td_%<tH")
res34: String = 2014/03/02_00

ただ、このような2引数関数の場合、特にカリー化を経なくても元のformatTimestamp関数に部分適用することによっても似たような特化関数を得ることはできます。

scala> val specializedFormatter2 = formatTimestamp((new java.util.Date).getTime, _: String)
specializedFormatter2: String => String = <function1>

scala> specializedFormatter2("%tF %<tT")
res35: String = 2014-03-02 00:55:08

そもそもカリー化とはパラメータリストを組み換えることであって、引数への値の適用まで意味するものではないため部分適用とは概念として全く異なるものなのですが[6]、「関数をカリー化した後に一部引数に値を適用して得られた残りの関数」と「元の関数に部分適用して得られた残りの関数(をカリー化したもの)」の間に本質的な違いがあるのか、それとも単に導出過程が違うだけで結果的に等価なものが得られているのかについては、筆者の勉強不足により解答が得られていません。(きっと同じもの、のはず…)
 なお、カリー化という名称は論理学者のハスケル・カリー(Haskell Curry)氏に因んだものだそうで、飲み物のカレーとは関係ないそうです、先輩。この方、苗字(⇒カリー化)も名前(⇒純粋関数型プログラミング言語Haskell)も後世に残していてすごいですね。

・名前渡し引数

 これまで見てきたような形式でdefにより関数を定義すると、引数は値渡し(pass by value)(値呼び出し(call by value)とも言われる)になります。つまり、呼ばれた関数側で実際にその引数を使うかどうかに関わらず、呼ぶ側で式を評価して値を計算してから関数に渡さなければなりません(これを正格(strict)な評価と言います)。しかし、その評価が重いものだった場合、もし使わないのであれば評価せずに済ませたい、仮に使うとしても本当に必要となるときまで計算したくない、と思うかもしれません(対してこちらを非正格(non-strict)な評価と言います)。それを実現する方法の一つとして、関数にラップするという方法があります(関数であれば定義しただけでは実行されないし、Scalaでは引数として関数値を渡すことができる!)。でも、その関数の引数って何?それを見るために0引数関数、つまりFunction0をインスタンス化してみましょう。

scala> new Function0[String]{def apply = ""}
res36: () => String = <function0>

型が「() => String」になりました※2。なので

scala> def logger(message: () => String) {
     |   if (debugEnabled) debugLog(message())
     | }
logger: (message: () => String)Unit

のように定義しておけば、たとえmessageが文字列連結まみれであったとしても、debugEnabledがtrueでない限りはその連結が実行されることはありません。しかしこのままだとlogger(() => “This “+”is “+”a “+”debug “+”message.”)のような呼び出し方をしなければならず、なんか嫌です。Scalaはこれをすっきりとlogger(“This “+”is “+”a “+”debug “+”message.”)のように書きつつ、かつ使われるときまで評価を行わないようにできる手段を提供しています。それは、型を「=> String」とすることです。

scala> def logger(message: => String) {
     |   if (debugEnabled) debugLog(message)
     | }
logger: (message: => String)Unit

「空」引数関数を「無」引数関数にすることによって関数としての性質も残しつつ、呼び出し()なしで使えるようにしている感じですね。
 この「(呼び出し時点ではなく)実際に使用されたときに値を評価する」ような渡し方ですが、複数回使われたときの動作の違いによって2つに分類されます。引数が参照されるたびに毎回式の評価を行う方式を名前渡し(pass by name)、最初に必要とされたときに評価した値を取っておき、2度目からはその値を使う方式を必要渡し(pass by need)と言います[7]。上で紹介したScalaの「=> 型」を用いるやり方は名前渡しになるので、この機能は名前渡し引数とか名前渡しパラメータと呼ばれます。Scalaは標準では必要渡しをサポートしていないので、そのような動作を実現するためには関数の中で評価した値を変数に格納して取っておくとか、呼ぶ側で一旦lazy valに格納してから名前渡し引数として渡すといったことをする必要があります。
 なお、呼び出すその場で評価をしないということから非正格評価全体を指して「遅延評価」と呼んでいるものもあるようですが、名前渡しのように何度も式の評価が走ってしまうものは遅延評価と呼ぶべきではないといった見解もあるようで[8,9]、ここでは明言は避けておきます。

Summary

 We have seen some characteristic features of Scala programming language, which include: integration of object-oriented and functional language concepts, strong static type system, interoperation with Java, and some intriguing topics about methods and literals.
 In the second chapter we have taken a quick tour of some notions concerning the first-class functions which form the basis of the functional programming. I hope this article helps you to get started with programming in Scala.
 In the forthcoming blog entry I’m planning to describe some Scala libraries which are essential in constructing actual enterprise applications.

References

Scalaスケーラブルプログラミング第2版
Scalaメモ(Hishidama’s Scala Memo)
Scalaで強引に日本語プログラミングしてみた – ゆるよろ日記
第一級関数 – Wikipedia
カリー化 – Wikipedia
カリー化 != 部分適用 – kmizuの日記
いろいろな引数渡しの方式 — 値呼び・参照呼び・名前呼び・必要呼び
Scalaの名前渡しは遅延評価ではない – ぼっち勉強会
遅延評価ってなんなのさ – ぐるぐる~


※1. ^ Scalaには部分関数(Partial functions)というものもありますが全くの別物です。これはまた別の機会に。

※2. ^ この()は最初Unit値かと思いましたが、どうやら違うようですね(その場合型は「Unit => String」になるはず)。なんだろうこれ…。

Add a Comment

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です


*