Scala School (5)

朝練の続き。今は Basics continued のページです。

Packages

package 宣言できる、とのことですがディレクトリ構成とかって java と同じってことと思って良いのかどうか。とは言え java のソレもあまりきちんと理解できていないかもしれないですが。
あと、package 宣言しちゃうと (しなくても、なのかどうかは不明) 変数とは手続きはトップレベルに記述できなくなってしまうみたいです。class または object の中に書いてね、ってことでした。
静的なソレを記述するのに object は便利ですよ、とあります。
あと参照の方法として以下な記述をしているとして

package com.twitter.example

object colorHolder {
  val BLUE = "Blue"
  val RED = "Red"
}

直接メンバにアクセスするには以下のように書いてね、との由。

println("the color is: " + com.twitter.example.colorHolder.BLUE)

このあたりの仕組みとして REPL の出力例が示されています。

scala> object colorHolder {
     | val Blue = "Blue"
     | val Red = "Red"
     | }
defined module colorHolder

module って何だ、というソレはスルーで _designed to be part of Scala's module system_ というあたりを何となく覚えておく、という事にて。

Pattern Matching

ここ guards ってのがよく分かりません。テキストにも _Notice how we captured the value in the variable ‘i’._ としかフォローが無く色々ツラい。

Matching on type

型問わず、らしい。これもすばら。以下が例示されてます。正に何でもアリ。

def bigger(o: Any): Any = {
  o match {
    case i: Int if i < 0 => i - 1
    case i: Int => i + 1
    case d: Double if d < 0.0 => d - 0.1
    case d: Double => d + 0.1
    case text: String => text + "s"
  }
}
Matching on class members

Calculator というクラスは二つの属性を持っていましたが、こんなパターンマッチの方法しか無いのではイタすぎるので、という事らしいです。

def calcType(calc: Calculator) = calc match {
  case calc.brand == "hp" && calc.model == "20B" => "financial"
  case calc.brand == "hp" && calc.model == "48G" => "scientific"
  case calc.brand == "hp" && calc.model == "30B" => "business"
  case _ => "unknown"
}

Case Classes

なんとなくパタンマッチの case のためのクラスに見えます。例示されているソレを REPL に食わせてみます。

scala> case class Calculator(brand: String, model: String)
defined class Calculator

new を使わなくて良い、というのもポイントなのかな。case 付けると factory な object が同時に作成されると類推。中身は同じように見える二つのオブジェクトを生成していますね。

scala> val hp20b = Calculator("hp", "20b")
hp20b: Calculator = Calculator(hp,20b)

scala> val hp20B = Calculator("hp", "20b")
hp20B: Calculator = Calculator(hp,20b)

で、それぞれを比較しています。

scala> hp20b == hp20B
res4: Boolean = true

同じモノ、と認識なさっているご様子。これをパタンマッチで使うのかどうか。ちなみに補足として普通のクラスみたくメソド持てますよ、とあります。

CASE CLASSES WITH PATTERN MATCHING

case class を使えば以下のように書けるとのこと。これは簡単ですね。

val hp20b = Calculator("hp", "20B")
val hp30b = Calculator("hp", "30B")

def calcType(calc: Calculator) = calc match {
  case Calculator("hp", "20B") => "financial"
  case Calculator("hp", "48G") => "scientific"
  case Calculator("hp", "30B") => "business"
  case Calculator(ourBrand, ourModel) => "Calculator: %s %s is of unknown type".format(ourBrand, ourModel)
}

以降で otherwise な条件の書き方がいくつかフォローされてます。

Exceptions

java と同様 try-catch-finally が使えます、とのこと。あと例外は case で捕まえるようです。match 無しでいきなり case なんですね。
それから try-catch は一つの式で値を戻すようです。finally は別な式にせざるを得ないあたりに若干の無理やりなカンジが漂っているように感じます。とは言え、これは一つの式にはできないのかどうか。

Basic Data Structures

そのまま Collections に突入。ページの URL は以下です。

ええと先頭から順に列挙してみます。

Lists

所謂リスト、のようです

Sets

Sets は重複したデータを持たない模様。

Tuple

クラスを使わないでまとめるのか。アクセサとして 1-based な位置を指定するらしい。

scala> val hostPort = ("localhost", 80)
hostPort: (java.lang.String, Int) = (localhost,80)

scala> hostPort._1
res5: java.lang.String = localhost

scala> hostPort._2
res6: Int = 80

scala> hostPort._3
<console>:9: error: value _3 is not a member of (java.lang.String, Int)
              hostPort._3
                       ^

で、Tuple はパタンマッチに適しているらしく以下なカンジで簡易に記述できるみたい。

scala> 1 -> 2
res8: (Int, Int) = (1,2)

scala> "localhost" -> 80
res9: (java.lang.String, Int) = (localhost,80)
Maps

これ、辞書って思ってて良いのかな。

scala> Map(1 -> "one", 2 -> "two")
res10: scala.collection.immutable.Map[Int,java.lang.String] = Map(1 -> one, 2 -> two)

このあたりまではよくあるソレとして次のが面白かったです。

Option

Option は何かを保持しているかも、していないかも、なコンテナらしい。基本的なインターフェースとしては以下、とのこと。

trait Option[T] {
  def isDefined: Boolean
  def get: T
  def getOrElse(t: T): T
}

Option は具体的な状態 (?) として Some[T] と None という二つのサブクラスを持つ模様。例示されているソレを REPL に食わせてみます。

scala> val numbers = Map(1 -> "one", 2 -> "two")
numbers: scala.collection.immutable.Map[Int,java.lang.String] = Map(1 -> one, 2 -> two)

scala> numbers.get(2)
res11: Option[java.lang.String] = Some(two)

scala> numbers.get(3)
res12: Option[java.lang.String] = None

ここはとりあえずそのまんまですね。これらを変数に格納してみます。

scala> var tmp = numbers.get(2)
tmp: Option[java.lang.String] = Some(two)

で、メソド呼び出してみます。

scala> tmp.isDefined
res15: Boolean = true

scala> tmp.get
res16: java.lang.String = two

scala> tmp.getOrElse("three")
res17: java.lang.String = two

あるいは None の方。

scala> tmp = numbers.get(3)
tmp: Option[java.lang.String] = None

scala> tmp
res18: Option[java.lang.String] = None

scala> tmp.isDefined
res19: Boolean = false

おそらく get は例外吐くはず。吐きました。getOrElse はどうなるのかな。

scala> tmp.getOrElse("three")
res21: java.lang.String = three

成程。でも None に対して get を呼ぶと例外、ってのはなんとなく不親切な気がします。とは言えとりあえず isDefined 呼んどけ、とか書いてありますね。
あ、でも getOrElse かパタンマッチで使ってね、とかありますね。そしてパタンマッチの場合は以下な書き方ができる模様。

val result = res1 match {
  case Some(n) => n * 2
  case None => 0
}

これはすばらですね。

Functional Combinators

ええと、List(1, 2, 3) map squeared というソレはリストの要素一つ一つに squared という手続きを適用して新しいリスト ((1, 4, 9) みたいな) を戻します。こうした操作を combinator と呼ぶ模様。

map

ええと、numbers を作っておきます。

scala> val numbers = List(1, 2, 3, 4)
numbers: List[Int] = List(1, 2, 3, 4)

で、以下。

scala> numbers.map((i: Int) => i * 2)
res22: List[Int] = List(2, 4, 6, 8)

map に渡しているのは手続きオブジェクトなのかな。まぁ当り前の map ですね。次の例が良く分からんかったのですが、partially evaluated function ということはこれも手続きオブジェクトとして渡すことができますよ、という事なのか。一応確認を。

scala> def timesTwo(i: Int): Int = i * 2
timesTwo: (i: Int)Int

scala> numbers.map(timesTwo _)
res23: List[Int] = List(2, 4, 6, 8)
foreach

foreach は値を戻さないようです。

scala> numbers.foreach((i: Int) => i * 2)

副作用のみ、ということなのですが、上の式だと i については何もしていないので numbers の中身は変わらないですね。

scala> numbers
res28: List[Int] = List(1, 2, 3, 4)

戻りを確保して云々してますが Unit という型が出てきますね。void?

filter

filter も手続きオブジェクトを渡してその戻りが真か偽かによって戻すリストに追加するかどうか、というソレ。部分適用な手続きオブジェクトを渡す例も示されています。

zip

これは二つのリストの各要素をペアにしたリストを作る、ということなのかな。

partition

これは渡した条件式がリストの各要素に適用されて戻りが真か偽か、で二つのリストに分かつ、という事なのか。

find

これも Option 戻してますね。渡した手続きオブジェクトにリストの各要素を渡して戻りが真だった最初の要素を Option として戻す、なのかどうか。

drop and dropWhile

drop は渡した値分、リスト先頭から削除、なのかな。これに対して dropWhile は条件式が渡せるようです。条件が真だった最初の要素が drop される模様。

foldRight and foldLeft

fold は Gauche 方面でも出てくるアレですね。数えあげて map して accumulate なナニ。以下なソレだと

scala> numbers.foldLeft(0)((m: Int, n: Int) => m + n)
res0: Int = 55

て、numbers は以下なリストってことで。

scala> var numbers = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
numbers: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

foldLeft はリストの左から値を取り出しつつ左側の引数に accumulate するのかどうなのか。foldRight 見てみたらリストの右から値を取り出して右側の引数に accumulate しているようなのでこの理解で良いのかどうか。
ちなみに accumulate 対象になるのは戻り値、というか評価される式の値、と言えば良いのかどうか。

flatten

入れ子になったリストを平たくします。

flatMap

こちらも入れ子なリストを受けとって map するのかな。ちなみに map は入れ子になったリストでも有効なようで、その結果を flatten する例も示されていますね。
とは言え未だ以下な記述が直感的にオチません。

nestedNumbers.flatMap(x => x.map(_ * 2))

このあたりを見ても Scheme/Lisp 族は凄いな、って思います。

Generalized functional combinators

まあ大体 fold 使えば何とでもなるよ的話。オレ map の実装例が出てますね。

def ourMap(numbers: List[Int], fn: Int => Int): List[Int] = {
  numbers.foldRight(List[Int]()) { (x: Int, xs: List[Int]) =>
    fn(x) :: xs
  }
}
がよく分からんかった上にググるのが難しかったんですが右側のリストの先頭に左側の値を要素として追加するメソッド? みたいですね。成程。オレ map の実装もなんとなくイメージできます。よかったよかった。
Map?

ここもびっくり。パタンマッチな記述で filter できるらしい。以下なカンジの Map を作って

scala> val extensions = Map("steve" -> 100, "bob" -> 101, "joe" -> 201)
extensions: scala.collection.immutable.Map[java.lang.String,Int] = Map(steve -> 100, bob -> 101, joe -> 201)

以下が普通に (?) 考えそうなフィルタ適用の例。

scala> extensions.filter((namePhone: (String, Int)) => namePhone._2 < 200)
res24: scala.collection.immutable.Map[java.lang.String,Int] = Map(steve -> 100, bob -> 101)

この条件式 (?) をパタンマッチな記述で云々、なのが以下の例。こりゃすばら。

scala> extensions.filter({case (name, extension) => extension < 200})
res25: scala.collection.immutable.Map[java.lang.String,Int] = Map(steve -> 100, bob -> 101)

これは面白いな、と言いつつも詳細はヒミツのまま終わる模様です。

そいえば

リスト遊び、なソレを云々、なゲンジツトウヒはあるかも (ぇ