Skip to content

Instantly share code, notes, and snippets.

@Shinpeim
Created September 28, 2013 05:38
Show Gist options
  • Save Shinpeim/6738787 to your computer and use it in GitHub Desktop.
Save Shinpeim/6738787 to your computer and use it in GitHub Desktop.

もっとScala!

基本的なデータ構造 List

immutable なやつと mutable なやつがある。基本的に immutable なやつを使うべき。

scala.collection.immutable.List

最初からimportされてるので List で呼び出せる

List は head(一番最初の要素) と tail(残りのリスト)からなる。

list: List[Int] のとき、list.headInt で、list.tailList[Int]。空のリストを Nil で表す。

Listの作り方

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とパターンマッチ

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

ループを再帰に書き直すことで何が得られるのか?変数への再代入などが必要なくなるというのが大きいとわたしは思っている。

case class

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 時のエラーが減り堅牢になる。

Option型

  • 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を返すようにしておくとヌルポの恐怖から解放される。

さらに便利な使い方は後ほど「モナド」の世界に脚を踏み入れてから。

関数型としての Scala

関数はオブジェクトです。このへんから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ライフを!
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment