Skip to content

Instantly share code, notes, and snippets.

@Shinpeim
Last active October 31, 2024 05:59
Show Gist options
  • Save Shinpeim/6740436 to your computer and use it in GitHub Desktop.
Save Shinpeim/6740436 to your computer and use it in GitHub Desktop.
Scala 入学式の資料

Better Java としての Scala

Hello World

  • src/main/scala/Main.scala
object Main {
  def main(args: Array[String]): Unit = {
    println("hello scala!")
  }
}
  • build.sbt
name := "NiigataScala"

version := "0.1"

scalaVersion := "2.10.2"

ディレクトリ構成

  • ソースは普通は src/main/scala 以下に置く
  • Javaと一緒に使う場合、java ファイルは src/main/java に
  • jarに含めたいその他のリソース(設定ファイルとかそういうの)は src/main/resources に置く

println?

  • java のSystem.out.println 相当

object Main, def main?

このプログラムのエントリーポイントとなる部分のおまじない(と今は思っておいて)

hello world in REPL

ターミナルで scala と打つとREPL(Read Eval Print Loop)が立ち上がるので対話的にScalaが実行できる

hello world in scala script

scala コマンドに *.scala を食わせるとスクリプトとして実行できる

基本的な型とリテラル

"" で囲むと文字列(String)となる。数字はそのまま書くとIntになる。数字のあとにlを付けるとLong。少数を普通に書くとDouble''で文字を囲むとCharとなる。true, falseBoolean。プリミティブ型ってのはなくてすべてがオブジェクト。

変数と型推論

object Main {
  def main(args: Array[String]) = {
    val hello = "hello scala" //型宣言がない!
    println(hello)

    val hello2: String = "hello!" //型宣言しようと思えばできる
    println(hello2)

    // hello2 = "wrong!!" //val で宣言した変数には再代入ができない

    var hello3 = "hello scala!!"
    println(hello3)

    hello3 = "hello scala!!!" // varで宣言した変数には再代入ができる
    println(hello3)
    hello3 = 123 // 型が違うので代入できない
    
    var hello4:Any = "hoge"
    hello4 = 123 // String is a Any だし Int is a Any なので代入可能
  }
}

クラスの定義

フィールドの定義

class Player {
  val name = "しんぺい"
  var hp = 10
}

object Main {
  def main(args: Array[String]) = {
    val shinpei = new Player
    println(shinpei.name)
    println(shinpei.hp)

    shinpei.hp = 4 // var なので代入できる
    println(shinpei.hp)
  }
}

public なフィールドを定義しているように見えるけど、内部的にはアクセサを自動で生成してる。今後はこれを便宜的に「publicなプロパティ」と呼ぶ

privateにしてみる

class Player {
  val name = "しんぺい"
  private var hp = 10
}

object Main {
  def main(args: Array[String]) = {
    val shinpei = new Player
    println(shinpei.name)
    println(shinpei.hp) //コンパイルエラー

    shinpei.hp = 4 //コンパイルエラー
    println(shinpei.hp) //コンパイルエラー
  }
}

コンストラクタに引数を取りたい

// 引数の型は必ず指定する
class Player(_name: String) {
  // _name は private field扱いになるので外部に公開するために代入する
  val name = _name
  private var hp = 10
}

object Main {
  def main(args: Array[String]) = {
    val shinpei = new Player("しんぺい")
    val takashi = new Player("たかし")

    println(shinpei.name)
    println(takashi.name)
  }
}

あるいは

//コンストラクタのパラメータにval とかvar を付けるとパブリックなプロパティ扱いになる
class Player(val name: String) {
  private var hp = 10
}

object Main {
  def main(args: Array[String]) = {
    val shinpei = new Player("しんぺい")
    val takashi = new Player("たかし")

    println(shinpei.name)
    println(takashi.name)
  }
}
// private とかつければそうなる
class Player(val name: String, private var hp: Int = 10)

object Main {
  def main(args: Array[String]) = {
    val shinpei = new Player("しんぺい")
    val takashi = new Player("たかし")

    println(shinpei.name)
    println(takashi.name)
  }
}

メソッドの定義

class Player(val name: String, private var hp: Int = 10) {
  // 引数の型は必ず指定しなければならない
  // メソッドはデフォルトで public となる
  // 返り値の型は省略可能
  def attack(otherPlayer: Player) = println(name + "は" + otherPlayer.name + "を攻撃した!")
}

object Main {
  def main(args: Array[String]) = {
    val shinpei = new Player("しんぺい")
    val takashi = new Player("たかし")

    takashi.attack(shinpei)
  }
}

返り値の型を明示することもできる

class Player(val name: String, private var hp: Int = 10) {
  // UnitはJavaでいうところのvoid
  def attack(otherPlayer: Player): Unit = println(name + "は" + otherPlayer.name + "を攻撃した!")
}

object Main {
  def main(args: Array[String]) = {
    val shinpei = new Player("しんぺい")
    val takashi = new Player("たかし")

    takashi.attack(shinpei)
  }
}

複数行にわたるメソッドの書き方,それと if

class Player(val name: String, private var hp: Int = 10) {

  // 複数行に渡るメソッドは {} で囲む
  def attack(otherPlayer: Player): Unit = {
    println(name + "は" + otherPlayer.name + "を攻撃した!")
    otherPlayer.damaged(3)
  }

  def damaged(damage: Int): Unit = {
    println(name + "は" + damage.toString + "のダメージを受けた")

    // if が値を返している。
    hp = if (hp - damage < 0) { 0 } else { hp - damage }
    if (isDead) { println(name + "は死んでしまった" }
  }

  // return はいらない
  private def isDead: Boolean = hp <= 0
}

object Main {
  def main(args: Array[String]) = {
    val shinpei = new Player("しんぺい", 3)
    val takashi = new Player("たかし")

    takashi.attack(shinpei)
  }
}

クラスメソッド(staticメソッド)はどうすんの?

クラスメソッドってのは存在しない。それっぽい見た目のものは定義できる。

// Playerという名前のシングルトンオブジェクトを定義している。
// 初めて参照されたときにインスタンス化され、
// 二度目以降はそのインスタンスを参照する
object Player {
  val defaultPlayreName = "名無しマン"
  def createDefaultPlayer = new Player(defaultPlayerName)
}

class Player(val name: String, private var hp: Int = 10) {
  def attack(otherPlayer: Player): Unit = {
    println(name + "は" + otherPlayer.name + "を攻撃した!")
    otherPlayer.damaged(3)
  }

  def damaged(damage: Int): Unit = {
    println(name + "は" + damage.toString + "のダメージを受けた")
    hp = if (hp - damage < 0) { 0 } else { hp - damage }
    if (isDead) { println(name + "は死んでしまった" }
  }

  private def isDead: Boolean = hp <= 0
}

object Main {
  def main(args: Array[String]) = {
    val shinpei = Player.defaultPlayer
    val takashi = new Player("たかし")
  }
}

継承

extends でできます

class Player(val name: String, private var hp: Int = 10) {
  val strength = 3

  def attack(otherPlayer: Player): Unit = {
    println(name + "は" + otherPlayer.name + "を攻撃した!")
    otherPlayer.damaged(strength)
  }

  def damaged(damage: Int) = {
    println(name + "は" + damage.toString + "のダメージを受けた")
    hp = if (hp - damage < 0) { 0 } else { hp - damage }
    if (isDead) { println(name + "は死んでしまった") }
  }

  private def isDead = hp <= 0
}

class Knight(name: String, hp: Int) extends Player(name: String, hp: Int) {
  override val strength = 5
}

class Magician(name: String, hp: Int, private var mp: Int = 5) extends Player(name: String, hp:Int) {
  def mera(otherPlayer: Player) = {
    println(name + "はメラを唱えた")

    if (mp < 3) {
      "しかしmpが足りない"
    } else {
      mp -= 3
      otherPlayer.damaged(10)
    }
  }
}

object Main {
  def main(args: Array[String]): Unit = {
    val shinpei = new Magician("しんぺい", 10, 5)
    val takashi = new Knight("たかし",15)

    shinpei.mera(takashi)
    takashi.attack(shinpei)
    takashi.attack(shinpei)
  }
}

インターフェイス的なやつ

traitというのがそれです。実装も書けてマジクール。Java の インターフェイス と Ruby の Moduleの中間みたいな

trait Weapon {
  val strength: Int
}
trait KnightWeapon extends Weapon
trait MagicianWeapon extends Weapon

object NullWeapon extends Weapon {
  val strength = 0
}
object LongSword extends KnightWeapon {
  val strength = 10
}
object Rod extends MagicianWeapon {
  val strength = 2
}

trait HasWeapon {
  protected var weapon: Weapon = NullWeapon
}
trait CanEquipKnightWeapon extends HasWeapon {
  def equip(w: KnightWeapon) = weapon = w
}
trait CanEquipMagicianWeapon extends HasWeapon {
  def equip(w: MagicianWeapon) = weapon = w
}

class Player(val name: String, private var hp: Int = 10) extends HasWeapon {
  val strength = 3

  def attack(otherPlayer: Player): Unit = {
    println(name + "は" + otherPlayer.name + "を攻撃した!")

    val damage = strength + weapon.strength
    otherPlayer.damaged(damage)
  }

  def damaged(damage: Int) = {
    println(name + "は" + damage.toString + "のダメージを受けた")

    hp = if (hp - damage < 0) { 0 } else { hp - damage }

    if (isDead) {
      println(name + "は死んでしまった")
    }
  }

  private def isDead = hp <= 0
}

class Knight(name: String, hp: Int)
  extends Player(name: String, hp: Int) with CanEquipKnightWeapon {

  override val strength = 5
}

class Magician(name: String, hp: Int, private var mp: Int = 5)
  extends Player(name: String, hp:Int) with CanEquipMagicianWeapon {

  def mera(otherPlayer: Player) = {
    println(name + "はメラを唱えた")

    if (mp < 3) {
      "しかしmpが足りない"
    } else {
      otherPlayer.damaged(10)
    }
  }
}

object Main {
  def main(args: Array[String]): Unit = {
    val shinpei = new Magician("しんぺい", 10, 5)
    val takashi = new Knight("たかし",15)

    shinpei.equip(Rod)
    shinpei.attack(takashi)

    takashi.equip(LongSword)
    takashi.attack(shinpei)
  }
}

型パラメータ

ところで、CanEquiq**Weapon、DRYじゃなくて気になる。型パラメータ使おう。

trait Weapon {
  val strength: Int
}
trait KnightWeapon extends Weapon
trait MagicianWeapon extends Weapon

object NullWeapon extends Weapon {
  val strength = 0
}
object LongSword extends KnightWeapon {
  val strength = 10
}
object Rod extends MagicianWeapon {
  val strength = 2
}

trait HasWeapon {
  protected var weapon: Weapon = NullWeapon
}
trait CanEquip[T <: Weapon] extends HasWeapon {
  def equip(w: T) = weapon = w
}

class Player(val name: String, private var hp: Int = 10) extends HasWeapon {
  val strength = 3

  def attack(otherPlayer: Player): Unit = {
    println(name + "は" + otherPlayer.name + "を攻撃した!")

    val damage = strength + weapon.strength
    otherPlayer.damaged(damage)
  }

  def damaged(damage: Int) = {
    println(name + "は" + damage.toString + "のダメージを受けた")

    hp = if (hp - damage < 0) { 0 } else { hp - damage }

    if (isDead) {
      println(name + "は死んでしまった")
    }
  }

  private def isDead = hp <= 0
}

class Knight(name: String, hp: Int)
  extends Player(name: String, hp: Int) with CanEquip[KnightWeapon] {

  override val strength = 5
}

class Magician(name: String, hp: Int, private var mp: Int = 5)
  extends Player(name: String, hp:Int) with CanEquip[MagicianWeapon] {

  def mera(otherPlayer: Player) = {
    println(name + "はメラを唱えた")

    if (mp < 3) {
      "しかしmpが足りない"
    } else {
      otherPlayer.damaged(10)
    }
  }
}

object Main {
  def main(args: Array[String]): Unit = {
    val shinpei = new Magician("しんぺい", 10, 5)
    val takashi = new Knight("たかし",15)

    shinpei.equip(Rod)
    shinpei.attack(takashi)

    takashi.equip(LongSword)
    takashi.attack(shinpei)
  }
}

ちなみに、クラス定義のときじゃなくて new するときにも trait を mixin できる

trait Weapon {
  val strength: Int
}
trait KnightWeapon extends Weapon
trait MagicianWeapon extends Weapon

object NullWeapon extends Weapon {
  val strength = 0
}
object LongSword extends KnightWeapon {
  val strength = 10
}
object Rod extends MagicianWeapon {
  val strength = 2
}

trait HasWeapon {
  protected var weapon: Weapon = NullWeapon
}
trait CanEquip[T <: Weapon] extends HasWeapon {
  def equip(w: T) = weapon = w
}

class Player(val name: String, private var hp: Int = 10) extends HasWeapon {
  val strength = 3

  def attack(otherPlayer: Player): Unit = {
    println(name + "は" + otherPlayer.name + "を攻撃した!")

    val damage = strength + weapon.strength
    otherPlayer.damaged(damage)
  }

  def damaged(damage: Int) = {
    println(name + "は" + damage.toString + "のダメージを受けた")

    hp = if (hp - damage < 0) { 0 } else { hp - damage }

    if (isDead) {
      println(name + "は死んでしまった")
    }
  }

  private def isDead = hp <= 0
}

class Knight(name: String, hp: Int)
  extends Player(name: String, hp: Int) with CanEquip[KnightWeapon] {

  override val strength = 5
}

class Magician(name: String, hp: Int, private var mp: Int = 5)
  extends Player(name: String, hp:Int) with CanEquip[MagicianWeapon] {

  def mera(otherPlayer: Player) = {
    println(name + "はメラを唱えた")

    if (mp < 3) {
      "しかしmpが足りない"
    } else {
      otherPlayer.damaged(10)
    }
  }
}

object Main {
  def main(args: Array[String]): Unit = {
    val shinpei = new Magician("しんぺい", 10, 5)
    val takashi = new Player("たかし",15) with CanEquip[Weapon]

    shinpei.equip(Rod)
    shinpei.attack(takashi)

    takashi.equip(LongSword)
    takashi.attack(shinpei)
  }
}

もっと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
  )
)

便利!

パターンマッチふたたび

パターンマッチの実態はPartialFunctionというもの。

object Pat extends App {
  val pattern:PartialFunction[Any, Any] = {
    case "nyan" => "got nyan"
    case "wan"  => "got wan"
  }
  println(pattern("nyan"))
}

部分的な関数ってなんやねんって感じだけど、要するに「部分的に定義された関数」のこと。もっと言うと、「ある引数に対する振る舞いは定義されてるけど、ある引数に対する振る舞いは未定義であるような関数」のこと。

object Pat extends App {
  val pattern:PartialFunction[Any, Any] = {
    case "nyan" => "got nyan"
    case "wan"  => "got wan"
  }
  println(pattern.isDefinedAt("nyan")) // => true
  println(pattern.isDefinedAt("hoge")) // => false
  pattern("hoge") // => raises Exception
}

部分的な定義を組み合わせることもできる

object Pat extends App {
  val stringPattern: PartialFunction[Any, Any] = {
    case "nyan" => "got nyan"
    case "wan"  => "got wan"
  }
  val intPattern: PartialFunction[Any, Any] = {
    case 1 => "got 1"
    case 2 => "got 2"
  }
  val failOver: PartialFunction[Any, Any] = {
    case _ => "got something"
  }

  val pattern = stringPattern.orElse(intPattern).orElse(failOver)

  println(pattern("nyan")) // => got nyan
  println(pattern(1)) // => got 1
  println(pattern(1.1)) // => got something
}

これが噂に聞く関数合成……? => ちがいます。怖いひとに怒られないようにしましょう。

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

object Nyan {
  def main(args:Array[String]): Unit = {
    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, 10 second))
  }
}

こういう 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ライフを!

準備

プロジェクトの作成

File -> new project

  • project name: NullPoGaBot
  • scala homeも設定

build.sbt

name := "NullPoGaBot"

version := "0.1"

scalaVersion := "2.10.2"

libraryDependencies ++= Seq(
  "org.twitter4j"            %  "twitter4j-core"   % "[3.0,)",
  "org.twitter4j"            %  "twitter4j-stream" % "[3.0,)"
)

resolvers ++= Seq("twitter4j" at "http://twitter4j.org/maven2")

依存するライブラリをこんな風に書くと勝手にインストールしてくれる。

SBTコンソールから gen-idea することで IntelliJ が依存ライブラリを認識してくれるようになる

NullPoGaBot という package を作る

src/main/scala ディレクトリを作成して

src -> main -> scala を右クリックからの new package からの NullPoGaBot

ぬるぽ対する反応を取得する部分をまずは書く

Replyer

package NullPoGaBot

object Replyer {
  def getReplyTo(tweet: String):Option[String] = tweet match {
    case t: String if t.contains("ぬるぽ") => Some("ガッ!")
    case t: String if t.contains("ヌルポ") => Some("ガッ")
    case t: String if t.contains("NullPointerException") => Some("ガッ")
    case _ => None
  }
}

NullPoGaBot

package NullPoGaBot

object NullPoGaBot {
  def main(args:Array[String]):Unit = {
    println(Replyer.getReplyTo("ぬるぽ"))
    println(Replyer.getReplyTo("ヌルポ"))
    println(Replyer.getReplyTo("NullPointerException"))
    println(Replyer.getReplyTo("にゃーん"))
  }
}

本来は specs2 みたいなテスト書くのが良いんだけど、今回は時間の都合でprintデバッグで行きます

UserStreamを受信する部分を書く

Streamer.scala

UserStreams のハンドラー

package NullPoGaBot

class Streamer extends twitter4j.UserStreamAdapter {
  override def onStatus(status: twitter4j.Status):Unit = {
    println(status)
  }
}

NullPoGaBot.scala

package NullPoGaBot

object NullPoGaBot {
  private val twitterConfig = {
    val builder = new twitter4j.conf.ConfigurationBuilder
    builder.setOAuthAccessToken("...")
    builder.setOAuthAccessTokenSecret("...")
    builder.setOAuthConsumerKey("...")
    builder.setOAuthConsumerSecret("...")
    builder.build
  }
  
  def main(args:Array[String]):Unit = {
    val stream = (new twitter4j.TwitterStreamFactory(twitterConfig)).getInstance
    stream.addListener(new Streamer)

    stream.user // user streams受信開始
  }
}

ReplyerでReplyできるようにする

Replyer

configを受け取るようにして、実際に twitter に post する replyTo メソッドを書く

package NullPoGaBot

class Replyer(config: twitter4j.conf.Configuration){
  val client = new twitter4j.TwitterFactory(config).getInstance

  def replyTo(status:twitter4j.Status) = {
    getReplyTo(status.getText) match {
      case Some(reply:String) =>
        val tweetFrom = status.getUser.getScreenName
        val update = new twitter4j.StatusUpdate("@" + tweetFrom + " " + reply)
        update.setInReplyToStatusId(status.getId)
        client.updateStatus(update)

      case None =>
        Unit
    }
  }

  def getReplyTo(tweet: String):Option[String] = {
    tweet match {
      case t: String if t.contains("ぬるぽ") => Some("ガッ!")
      case t: String if t.contains("ヌルポ") => Some("ガッ")
      case t: String if t.contains("NullPointerException") => Some("ガッ")
      case _ => None
    }
  }
}

Streamer

Streamer が replyerを持つようにして、statusを受信したらreplyするようにする

package NullPoGaBot

class Streamer(replyer: Replyer) extends twitter4j.UserStreamAdapter {
  override def onStatus(status: twitter4j.Status):Unit = {
    println(status)
    replyer.replyTo(status)
  }
}

NullPoGaBot

package NullPoGaBot

object NullPoGaBot {
  private val twitterConfig = {
    val builder = new twitter4j.conf.ConfigurationBuilder
    builder.setOAuthAccessToken("...")
    builder.setOAuthAccessTokenSecret("...")
    builder.setOAuthConsumerKey("...")
    builder.setOAuthConsumerSecret("...")
    builder.build
  }
  
  def main(args:Array[String]):Unit = {
    val replyer = new Replyer(twitterConfig)
    val streamer = new Streamer(replyer)

    val stream = (new twitter4j.TwitterStreamFactory(twitterConfig)).getInstance
    stream.addListener(streamer)

    stream.user // user streams受信開始
  }
}
@civic
Copy link

civic commented Sep 29, 2013

// private とかつければそうなる
class Player(val name: String, private var hp = 10)

のところは、var hp: Int みたいに型指定が必要ですよね。

@Shinpeim
Copy link
Author

ありがとうございます!修正しました!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment