Scala勉強会@東北第52回ピックアップ

http://sites.google.com/site/scalatohoku/dai52kai-bennkyoukai

Programming Scala(獏本)Chapter 2 Type Less Do More:"Inferring Type Information" から読んでいきました。

以下走り書き。


・メソッドのパラメータは必ず型を指定する。
・メソッドの返り値の型をつけなければいけないとき。4つ列挙。
a.returnを使った時

//aの例
def upCase(s: String) = {
  if (s.length == 0)
    return s    // ERROR - forces return type of upCase to be declared.
  else
    s.toUpperCase()
}
//この場合 def upCase(s: String): String = { としなければいけない。

b.再帰的に処理するとき

//bの例
def factorial(i: Int) = {
  def fact(i: Int, accumulator: Int) = {
    if (i <= 1)
      accumulator
    else
      fact(i - 1, i * accumulator)  // ERROR
  }

  fact(i, 1)
}
//def fact(i: Int, accumulator: Int):Int = { とする。

c.overloaded(多重定義)の時

//cの例.joinerというメソッドが多重定義されている。
object StringUtil {
  def joiner(strings: List[String], separator: String): String =
    strings.mkString(separator)

  def joiner(strings: List[String]) = joiner(strings, " ")   // ERROR
}
import StringUtil._  // Import the joiner methods.
//def joiner(strings: List[String]): String = joiner(strings, " ") とする

d.型推論された戻り値が、本来意図したものよりgeneralなAnyのような値であったとき。

//dの例。String* は可変長引数。
def makeList(strings: String*) = {
  if (strings.length == 0)
    List(0)  // #1
  else
    strings.toList
}
//makeList: (String*)List[Any]
//メソッド定義すると返り値はList(Any)と推論される。
//なぜならば、#1の場合はList[Int]となり、else節ではList[String]だからである。
//#1でList()としていれば、返り値は本来意図した通りのList[String]と推論してくれる。

・パブリックなAPIはちゃんと型を返しましょう

//StringUtilというAPI
object StringUtil {
  def joiner(strings: List[String], separator: String): String =
    strings.mkString(separator)

  def joiner(strings: List[String]): String = strings.mkString(" ")

  def toCollection(string: String) = string.split(' ') //Array[String]を返している
}

// code-examples/TypeLessDoMore/string-util-client.scala
//StringUtilを利用しているクライアントコード
import StringUtil._

object StringUtilClient {
  def main(args: Array[String]) = {
    args foreach { s => toCollection(s).foreach { x => println(x) } }
  }
}
//使用例
$ scala -cp ... StringUtilClient "Programming Scala"
Programming
Scala
//コードが成長して、StringUtilとそのクライアントコードが、別々のjarに分割されたと仮定。
//メンテナーが、StringUtilはデフォルトではリストを返すと決定、と仮定(?)
//ちょっと怪しいな・・・
object StringUtil {
  def joiner(strings: List[String], separator: String): String =
    strings.mkString(separator)

  def joiner(strings: List[String]): String = strings.mkString(" ")

  def toCollection(string: String) = string.split(' ').toList //toListを付け足して変更した
}
//StringUtilをリコンパイルして、jarに再配置。
//それから同じクライアントを再コンパイルせずに、走らせると・・・
$ scala -cp ... StringUtilClient "Programming Scala"
java.lang.NoSuchMethodError: StringUtil$.toCollection(...
  at StringUtilClient$$anonfun$main$1.apply(string-util-client.scala:6)
  at StringUtilClient$$anonfun$main$1.apply(string-util-client.scala:6)
...
//何が起こったのか? クライアントがコンパイルされたとき、StringUtil.toCollectionはArrayを返していました。
//そして、Listを返すようにtoCollectionは変更されました。
//両方のバージョンでは、メソッドの返り値は型推論されました。
//したがって、クライアントは再コンパイルされるべきでした。
//ArrayとListの親であるSeqを返り値として指定しておけばクライアントのリコンパイルを強制出来た?(駄目だここの英語わかんね・・・)

API開発時にクライアントを分割するときには、メソッドの戻り値を明示的に宣言してください。それから、できる中で最もgeneralな戻り型を使用しましょう。 APIがabstract methodを宣言するとき、特に重要です。

・メソッドで、=を書き忘れるとUnitって解釈されちゃうよ。

scala> def double(i: Int) { 2 * i } //=を書き忘れ
double: (Int)Unit

scala> println(double(2)) //IntではなくUnitが返ってきてしまう。
() 

・シンボル。Rubyと同じっぽい。使いすぎ注意?

・tuple._1 ... _Nで取り出せる。タプルはなぜ1から始まるのか。関数型の慣習にしたがった。

def tupleator(x1: Any, x2: Any, x3: Any) = (x1, x2, x3)

val t = tupleator("Hello", 1, 2.3)
println( "Print the whole tuple: " + t )
println( "Print the first item:  " + t._1 )
println( "Print the second item: " + t._2 )
println( "Print the third item:  " + t._3 )

val (t1, t2, t3) = tupleator("World", '!', 0x22) //val (t1,t2,t3)の所はパターンマッチ使ってる。
println( t1 + " " + t2 + " " + t3 )
//出力結果
Print the whole tuple: (Hello,1,2.3)
Print the first item:  Hello
Print the second item: 1
Print the third item:  2.3
World ! 34

・Option(Some,None)を使えば、nullをチェックしなくてすむ。


次回は Abstract Types And Parameterized Types から。