immutable なやつと mutable なやつがある。基本的に immutable なやつを使うべき。
最初からimportされてるので List
で呼び出せる
List は head(一番最初の要素) と tail(残りのリスト)からなる。
list: List[Int]
のとき、list.head
は Int
で、list.tail
は List[Int]
。空のリストを Nil
で表す。
List(element, element, element)
あるいは、 haed :: tail
で作れる
List(1, 2, 3)
// or
(1 :: (2 :: (3 :: Nil)))
// 右結合なので
1 :: 2 :: 3 :: Nil
めっちゃ強力な switch case みたいな感じ
xxx match {
case yyy => zzz
case aaa => bbb
}
とすると、xxx が yyy なら zzz が評価され、xxx が aaa ならば bbb が評価される。例をいくつか。
object Main {
def main(args: Array[String]): Unit = {
var nyan: Any = "nyan"
nyan match {
case "nyan" => println("this is nyan!")
case "wan" => println("this is wan!")
case _ => println("given unknown thing!")
}
nyan = "wan"
nyan match {
case "nyan" => println("this is nyan!")
case "wan" => println("this is wan!")
case _ => println("given unknown thing!")
}
nyan = 123
nyan match {
case "nyan" => println("this is nyan!")
case "wan" => println("this is wan!")
case _ => println("given unknown thing!")
}
}
}
object Main {
def main(args: Array[String]): Unit = {
var nyan: Any = "nyan"
nyan match {
case s: String => println("string:" + s + " is given")
case i: Int => println("int:" + i.toString + " is given")
case _ => println("given unknown thing!")
}
nyan = 123
nyan match {
case s: String => println("string:" + s + " is given")
case i: Int => println("int:" + i.toString + " is given")
case _ => println("given unknown thing!")
}
nyan = 123
nyan match {
case s: String => println("string:" + s + " is given")
case i: Int => println("int:" + i.toString + " is given")
case _ => println("given unknown thing!")
}
}
}
List(1,2,3) match {
case head :: tail =>
println(head)
println(tail)
case _ =>
println("not matched")
}
// output:
// 1
// List(2, 3)
List(1,2,3) match {
case head :: second :: third :: fourth =>
println(head)
println(second)
println(third)
println(fourth)
case _ =>
println("not matched")
}
// output:
// 1
// 2
// 3
// List()
たとえばリストの中に目的のものが入ってるかどうか探す場合、手続き的な書き方で書くとループで探す
def includes_shinpei?(list)
list.each do |name|
return true if name == "しんぺい"
end
return false
end
p includes_shinpei?(["たかし", "しんぺい", "ゆうすけ"])
p includes_shinpei?(["けんた", "たかし", "ゆうすけ"])
これを再帰で探すように書く
import scala.annotation.tailrec
object Main {
def main(args: Array[String]) = {
println(doesIncludeShinpei(List("たかし", "しんぺい", "ゆうすけ")))
println(doesIncludeShinpei(List("カーチャン", "たかし", "ゆうすけ")))
}
@tailrec
def doesIncludeShinpei(list: List[Person]): Boolean = list match {
case "しんぺい" :: _ => true
case Nil => false
case _ :: tail => doesIncludeShinpei(tail)
}
}
慣れるまで不思議かもしれないけど、慣れると宣言的に書けて良いねって感じになる。あとすべてのループはこういう再帰に書き換えることが可能。
えー!たくさん再帰したらスタック溢れちゃうよ!という心配は要らない。関数の末尾で再帰するような場合(末尾再帰と言う)コンパイラ氏がループに最適化してくれる。それを強制するアノテーションが@tailrec
。
ループを再帰に書き直すことで何が得られるのか?変数への再代入などが必要なくなるというのが大きいとわたしは思っている。
Klass(args)
という書き方でインスタンス化でき、パターンマッチでマッチさせたり中の値を取り出すことのできる class だと思うと良い。正確に説明する時間ないので正確な説明は省く。かなり強力。
import scala.annotation.tailrec
trait Word
case class Noun(surface: String) extends Word
case class Verb(surface: String, form:WordForm) extends Word
case class OtherWord(surface: String) extends Word
trait WordForm
case object Mizen extends WordForm
case object Renyou extends WordForm
case object Syuushi extends WordForm
case object Rentai extends WordForm
case object Katei extends WordForm
case object Meirei extends WordForm
object Main {
def main(args: Array[String]): Unit = {
val sentence = List(Noun("今日中"), OtherWord("に"), Noun("仕事"),
OtherWord("が"), Verb("終わら", Mizen), OtherWord("ない"), OtherWord("と"), OtherWord("ヤバい"))
getReply(sentence) match {
case Some(reply) => println(reply)
case None => Unit
}
}
@tailrec
def getReply(sentence: List[Word]): Option[String] = sentence match {
case Noun(n) :: OtherWord("が") :: Verb(v, Mizen) :: rest => Some(n + "は" + v + "ないわ。私が守るもの")
case _ :: rest => getReply(rest)
case Nil => None
}
}
いろんなものを「型」に落とし込んで型で設計して型でコードを書く。コンパイラがコンパイル時に型をチェックしてくれるので Runtime 時のエラーが減り堅牢になる。
- null にみんな嫌気さしてるはず
- nullチェック忘れてぬるぽ
- いつどこでnullが入ってくるかわかんない
- 辛すぎる
そこで Option 型。Option[T]
は Some(T)
or None
の直和。
今までだったら null を返してたようなときには None を返し、非 null なものはSomeに包んで返すようにすると幸せになれる。
import scala.annotation.tailrec
object Main {
def main(args: Array[String]): Unit = {
println(extractKeyword("ねこがにゃー", List("わん", "にゃー", "ぴよ")))
println(extractKeyword("ひよこがぴよ", List("わん", "にゃー", "ぴよ")))
println(extractKeyword("ぶたがぶー", List("わん", "にゃー", "ぴよ")))
}
@tailrec
def extractKeyword(s: String, keywords: List[String]): Option[String] = {
keywords match {
case head :: tail if s.contains(head) => Some(head)
case _ :: rest => extractKeyword(s, rest)
case Nil => None
}
}
}
値を取り出すときには、例えばパターンマッチを使う
import scala.annotation.tailrec
object Main {
def main(args: Array[String]): Unit = {
val maybeKeyword = extractKeyword("ねこがにゃー", List("わん", "にゃー", "ぴよ"))
maybeKeyword match {
case Some(keyword) => println("found keyword:" + keyword)
case None => println("found no keywords")
}
extractKeyword("ぶたがぶー", List("わん", "にゃー", "ぴよ")) match {
case Some(keyword) => println("found keyword:" + keyword)
case None => println("found no keywords")
}
}
@tailrec
def extractKeyword(s: String, keywords: List[String]): Option[String] = {
keywords match {
case head :: tail if s.contains(head) => Some(head)
case _ :: rest => extractKeyword(s, rest)
case Nil => None
}
}
}
ここで重要なのは、「値が無い」という状態をNoneという型に閉じ込めたこと。型に閉じ込めたことによって、コンパイル時にコンパイラのチェックを通る。nullableな値を返すメソッドなどはOptionを返すようにしておくとヌルポの恐怖から解放される。
さらに便利な使い方は後ほど「モナド」の世界に脚を踏み入れてから。
関数はオブジェクトです。このへんからREPLで
val addExclamation: Function[String, String] = s => s + "!"
addExclamation.apply("nyan")
addExclamation("nyan")
val concat: Function2[String, String, String] = (a, b) => a + b
concat("nyan", "wan")
別の書き方
val addExclamation: Function[String, String] = _ + "!"
val concat: Function[String, String, String] = _ + _
重要な点として、「関数とメソッドは別もの」です。関数はオブジェクトなので変数に代入したりできるけど、メソッドはそうはいかない。ただし、メソッドが関数に自動変換されることもあるし自分で変換することもできます。ただこのへんはややこしいので時間がないので割愛!
List#foreach
は 関数を引数に取り、Listの各要素に対してその関数を呼び出す
val list = List("nyan", "wan", "piyo")
val printIt = (s:String) => println(s)
list.foreach(printIt)
`List#filter` は boolean を返す関数を引数に取り、Listの各要素に対してその関数を呼び、結果が true だったものを集めた新しい List を返す
val list = List("nyan", "wan", "piyo") list.filter(_ != "nyan")
`List[A]#map` は、引数に関数(A => B) を取り、Listの各要素に対してこの関数を適用してそれらのListを返す。
val list = List(1, 2, 3) println(list.map(_.toString + "!")) // List("1!", "2!", "3!")
`List[A]#flatMap` は、引数に関数(A => List[B])をとり、List の各要素に対してこの関数を適用する。そして、その結果を連結した List[B] を返す。ポイントは、 List[B] を返す関数を引数に指定する、というところ。
val list = List(1, 2, 3) list.flatMap(x => List(x.toFloat)) //OK list.flatMap(x => x.toFloat) //NG list.flatMap(x => List(x.toFloat, (x + 1).toFloat)) //OK
応用編。
val listA = List(a, b) val listB = List(c, d)
この二つのListから、`List(a + c, a + d, b + c, b + d)` を得たい。パッと思いつくのは、map を nest すればいいんじゃないのってやつ。やってみましょう。
val listA = List("a", "b") val listB = List("c", "d") listA.map(aORb => listB.map(cORd => aORb + cORd ) )
結果は`List(List(ac, ad), List(bc, bd))`。残念な感じ。そこでflatMapを使ってみる。
listA.flatMap(aORb => listB.map(cORd => aORb + cORd ) )
内側の `aORb => listB.map(...)` が List を返す関数。外側の`listA.flatMap`はそのListを返す関数を引数にとって、結果を連結して返す!いい感じ!
ちなみに、上記のforeach, filter, map, flatMap ってのは重要なメソッド群。これらはFilterMonadic traitで定義されている。モナド!!!!!
## Optionだって「モナド」だよ
モナド、怖くないです。誤解を恐れずに言うと、モナドというのは、「すごいコンテナ」。じつは Option もモナドの一種。Option は「nullかも知れないオブジェクトを包むためのコンテナ」。
foreach はたいてい「"中身"に対して引数の関数を適用する」という手続きとして定義されている。
val someNyan: Option[String] = Some("nyan") someNyan.foreach(println)
val none: Option[String] = None none.foreach(println)
map は基本的に「"中身"に対して引数の関数を適用して、"中身"を変換する」という手続きとして定義されている。
val someNyan: Option[String] = Some("nyan") someNyan.map(_.toUpperCase)
val none: Option[String] = None none.map(_.toUpperCase)
flatMap も見てみる
val someNyan: Option[String] = Some("nyan") someNyan.flatMap(x => Option(x.toUpperCase))
val none: Option[String] = None none.flatMap(x => Option(x.toUpperCase))
filter はたいてい「"中身"に対して引数の関数を適用して、trueだったら残してfalseだったら残さない」という手続きとして定義されている。
val someNyan: Option[String] = Some("nyan") someNyan.filter(_ == "nyan") someNyan.filter(_ == "wan")
val none: Option[String] = None none.filter(_ == "hoge")
さて、これらを応用してみる。
var optionNyan: Option[String] = Some("nyan") var optionWan: Option[String] = Some("wan")
optionNyan.flatMap(nyan => optionWan.map(wan => nyan + wan ) )
optionNyan = None optionWan = Some("wan") optionNyan.flatMap(nyan => optionWan.map(wan => nyan + wan ) )
optionNyan = Some("nyan") optionWan = None optionNyan.flatMap(nyan => optionWan.map(wan => nyan + wan ) )
optionNyan = None optionWan = None optionNyan.flatMap(nyan => optionWan.map(wan => nyan + wan ) )
便利!
## Scala の for はループ構文ではない(と思ったほうがいい)
単純な操作ならいいんだけど上記のやつが複雑に組合わさってくると、ちょっと読みにくい。for 構文で書き換えることができる。
### foreach を書き換える for
val list = List(1, 2, 3) for {el <- list} { val doubled = el * 2 println(doubled) }
これは、
list.foreach(el => { val doubled = el * 2 println(doubled) })
と等価。Option も foreach を持ってるので、 Option に対しても同じことができる。
var option:Option[Integer] = Some(1)
for {value <- option} { val doubled = value * 2 println(doubled) }
option = None for {value <- option} { val doubled = value * 2 println(doubled) }
### filter を書き換える for
def isOdd(n: Integer) = n % 2 == 1 val list = List(1, 2, 3, 4)
for { el <- list if isOdd(el)} { println(el) }
これは
list.filter(el => isOdd(el)).foreach(el => println(el))
と等価。
## map を書き換える for
yield を付けると foreach ではなくて map になる
val list = List(1, 2, 3) for { el <- list } yield { el * 2 }
list.map(el => el * 2)
と等価。
## for はネストできる
val listOfList = List(List("a", "b"), List("c", "d")) for {list <- listOfList el <- list} { println(el) }
これは以下のコードと一緒
val listOfList = List(List("a", "b"), List("c", "d")) listOfList.foreach(list => list.foreach(el => println(el) ) )
## yield で nest した場合は flatMap とmap を組み合わせてくれる
val listA = List("a", "b") val listB = List("c", "d") for {aORb <- listA cORd <- listB} yield { aORb + cORd }
これは以下のコードと一緒。
val listA = List("a", "b") val listB = List("c", "d") listA.flatMap(aORb => listB.map(cORd => aORb + cORd ) )
### Option でもやってみる
var someNyan:Option[String] = Some("nyan") var someWan:Option[String] = Some("wan") for {nyan <- someNyan wan <- someWan} yield { nyan + wan }
someNyan = None someWan = Some("wan") for {nyan <- someNyan wan <- someWan} yield { nyan + wan }
someNyan = Some("nyan") someWan = None for {nyan <- someNyan wan <- someWan} yield { nyan + wan }
someNyan = Some("nyan") someWan = None for {nyan <- someNyan wan <- someWan} yield { nyan + wan }
var someNyan:Option[String] = Some("nyan") var someWan:Option[String] = Some("wan") for {nyan <- someNyan if nyan == "wan" wan <- someWan} yield { nyan + wan }
こんな感じで、monadicな操作はforを使って書くと読みやすく書きやすい!
## もっとモナド Future
`Future`モナドは、「値が未来において決まる計算」を包むためのモナド。
`Future[Int]`ならば、中には「いつかIntの結果になる計算」が包まれている。
import scala.concurrent.{Await, Future, future} import scala.concurrent.duration._ import scala.concurrent.ExecutionContext.Implicits.global
val f: Future[Int] = future { Thread.sleep(1000) 1 }
// 「1秒後に "1" になる計算の結果を2倍するという計算」をFutureで包んで返す val g: Future[Int] = f.map(i => i * 2)
println("ここはすぐ表示される")
//Await.resultでもって「中身」が決定するまで待って結果を取り出し、結果を表示する println(Await.result(g))
こういう Monad と for を組み合わせて「モナディック」に計算を行う。
import scala.concurrent.{Await, Future, future} import scala.concurrent.duration._ import scala.concurrent.ExecutionContext.Implicits.global
object Hoge { def main(args: Array[String]): Unit = { val f0: Future[Int] = future { Thread.sleep(1000) 1 } val f1: Future[Int] = future { Thread.sleep(2000) 2 } val f2: Future[Int] = future { Thread.sleep(3000) 3 } val f3: Future[Int] = future { Thread.sleep(4000) 4 }
val f4: Future[Int] = for {
a <- f0
b <- f1
} yield {
a + b
}
val f5: Future[Int] = for {
a <- f2
b <- f3
} yield {
a + b
}
val f6: Future[Int] = for {
a <- f4
b <- f5
} yield {
a + b
}
println(Await.result(f6, 20 seconds))
} }
Futureはこれだけじゃなくて例外吐いたときのこととか `andThen` とか `Promiss` いろいろ触れるべきポイントあるけどそれはまた今度。
# 今回まったく触れてない Scala 重要ポイントたち
* コンパニオンオブジェクト
* 名前つきパラメータ
* call-by-name
* implicit parameter
* implicit conversion
* Actor(akka)
* Structural Subtyping(構造的部分型)
などなど。でももう「入学済み」のみんななら個別に調べれば大丈夫!!良いScalaライフを!