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))
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)
これは面白いな、と言いつつも詳細はヒミツのまま終わる模様です。
そいえば
リスト遊び、なソレを云々、なゲンジツトウヒはあるかも (ぇ