How to use

implicit

in Scala

about this presentation: implicit???

  • implicit

    • それとなく示された, 暗黙の
  • Scalaの修飾子: implicit val i = 2

    • そもそもScala??
  • 他の言語には無い機能

    • Scalaを学ぶ上での障壁を取り除く
    • 新たな言語で実装された際に狼狽えないために

about this presentation: implicit???

  • implicit: それとなく示された, 暗黙の
  • Scalaの修飾子: implicit val i = 2

    • そもそもScala??
  • 他の言語にはあまり無い機能

    • Scalaを学ぶ上での障壁を取り除く
    • 新たな言語で実装された際に狼狽えないために
  • implicitによるScalaの拡張

    • Scalaは何が出来るようになったのか

about Scala

  • オブジェクト指向: better Java
  • 関数型: Haskellを非常に参考にしてる
  • JVM
  • 静的型付け
  • 豊富なコレクション+操作関数
  • REPL: 対話型環境
scala> val foo = "foo" // String型の変数
foo: String = foo

scala> def hoge(i: Int)(j: Int) = i * j // (カリー化された風な)関数
hoge: (i: Int)(j: Int)Int

scala> val fuga = (x: Int) => hoge(2)(x) // (Int => Int)となる関数
fuga: Int => Int = <function1>

scala> fuga(3) // 関数呼び出し
res1: Int = 6

about Scala: OOP

// trait: Rubyのモジュール、Javaの(実装が持てる)Interfaceみたいなの
scala> trait A {
     |   def f(i: Int): Int
     |   def twice(j: Int): Int = j*2
     | }
defined trait A

// Intを取るコンストラクタを持つクラスに、traitをmixin
scala> class B(n: Int) extends A {
     |   def f(i: Int) = i * n
     | }
defined class B

scala> val b = new B(3) // インスタンス生成
b: B = B@380910bc
scala> b.f(3) // クラスに定義された関数が呼び出せる
res5: Int = 9
scala> b.twice(5) // mixinしたtraitに定義された関数が呼び出せる
res6: Int = 10

implicit

  • 前準備

    • implicit parameter

      • 暗黙のパラメータ
    • implicit conversion

      • 暗黙の型変換

implicit (前準備1: implicit parameter)

暗黙のパラメータ

// implicitなInt型の変数を用意
scala> implicit val i = 2
i: Int = 2

// implicitパラメータを取る関数
scala> def foo(x: Int)(implicit y: Int) = x * y
foo: (x: Int)(implicit y: Int)Int

// なぜかimplicitな値が渡されてる
scala> foo(5)
res0: Int = 10
  • NOTICE

    • 同じ型のimplicit変数は同じスコープ内には定義出来ない
    • 取るべきimplicitなパラメータが見つからない時もコンパイル時にエラー

implicit (前準備2: implicit conversion)

暗黙の型変換

// 暗黙の型変換を行う関数
scala> implicit def getString(i: Int): String = i.toString
warning: there was one feature warning; re-run with -feature for details
getString: (i: Int)String

scala> val i = 3
i: Int = 3

scala> val str: String = i
str: String = 3

scala> def bang(str: String) = str + "!!"
bang: (str: String)String

// String型の引数が要求されているので、暗黙の型変換が行われる
scala> bang(i)
res0: String = 3!!

暗黙の型変換まとめておいて、必要に応じてimportするとか

Use Case: implicitの使い方3パターン

  1. pimp my libraryパターン

    • enrigh my library
    • static monkeypatching
    • method injection
  2. Context/Session/Connection等の引き回し

    • DBのコネクションプールからの利用
  3. 型クラスの実現

    • アドホック多相を実現

1. pimp my libraryパターン

外部ライブラリにより提供されたクラスを拡張したい…

  • Javaの場合

    • それっぽいクラスで継承

1. pimp my libraryパターン

  • Rubyの場合

    • 既存のクラスをそのまま拡張出来る
    • Railsの1.hour.agoとか
✘╹◡╹✘ ☆  pry
[1] pry(main)> "waiwai".bang
NoMethodError: undefined method `bang' for "waiwai":String
from (pry):1:in `__pry__'
[1] pry(main)> class String
[1] pry(main)*   def bang
[1] pry(main)*     self + "!!"
[1] pry(main)*   end
[1] pry(main)* end
=> :bang
[2] pry(main)> "waiwai".bang
=> "waiwai!!"

1. pimp my libraryパターン

  • Scalaの場合

    • 異なるクラスへ暗黙的に変換する(implicit conversion)
scala> "waiwai".bang
<console>:8: error: value bang is not a member of String
              "waiwai".bang
                       ^
scala> implicit class RichString(str: String) {
     |   def bang = s"$str!!"
     | }
defined class RichString

scala> "waiwai".bang
res1: String = waiwai!!

scala> "waiwai": { def bang: String }
res2: AnyRef{def bang: String} = RichString@75cd480d

2. Context/Session/Connection等の引き回し

// コネクションプールからコネクションを取得
def update(id: UserId, name: String) = DBAction { implicit session: DBSession =>
  // 暗黙的パラメータの利用
  val user = UserRepository.updateName(id, name)
  Ok("")
}
object UserRepository {
  val users: UserDAO = ???

  // コネクションが渡される事前提に処理を記述
  def update(userId: UserId, name: String)(implicit session: DBSession): Option[User] =
    session.withTransaction {
      users.findById(userId).map { user =>
        users.insert(user.copy(name = name))
      }
    }

  def searchByTeamId(teamId: TeamId)(implicit session: DBSession) = ???
  ...
}

3. アドホック多相の実装

  • Haskellの型クラスの仕組みをimplicitを用いてScalaで実現

    • クラス毎の実装(アドホックな実装)を提供
    • さらにScalaの方が微妙に便利
trait Adder[A] { // plusという関数を持つ
  def plus(a1: A, a2: A): A
}
// plusという関数を持つAdderを用いるだけで、型引数の中身を知らなくて良い
def adhocplus[A](a1: A, a2: A)(implicit adder: Adder[A]): A = adder.plus(a1, a2)

object SimpleAdder {
  // Int用の実装
  implicit val intAdder = new Adder[Int] {
    def plus(a1: Int, a2: Int) = a1 + a2
  }
  // String用の実装
  implicit val stringAdder = new Adder[Int] {
    def plus(a1: String, a2: String) = a1 + a2 + "!!!"
  }
}

import SimpleAdder._ // このスコープで有効な(Haskellでいう)インスタンスを明示できる
adhocplus(1, 3) // -> 4
adhocplus("aa", "bb") // -> "aabb!!!"
  • かっこいい型クラス

    • scalaz
    • shapeless
  • 実例implicit: Play Framework

    • 引数の型からContent-Typeを決定
    • 既存の型を直接拡張する分には億劫
object Writeable extends DefaultWriteables {
  // controllerの最後が凡そ OK("")、OK(json)
  // BAD_REQUEST("")、BAD_REQUEST(Html("<html></html>")) などを呼びだす
  // `transform`のA型の引数が、上記引数の型になる(String型だったり、Html型だったり)
  // (これらを呼び出す時、ContentTypeOf は明示しなくて良い)
  // mimeTypeが ContentTYpeOf[A] から呼び出されている
  def apply[A](transform: A => Array[Byte])(implicit ct: ContentTypeOf[A]): Writeable[A] =
    Writeable(transform, ct.mimeType)
}

object ContentTypeOf extends DefaultContentTypeOfs
trait DefaultContentTypeOfs {
  implicit def contentTypeOf_Html(implicit codec: Codec): ContentTypeOf[Html] = {
    ContentTypeOf[Html](Some(ContentTypes.HTML)) // -> "text/html"
  }
  implicit def contentTypeOf_JsValue(implicit codec: Codec): ContentTypeOf[JsValue] = {
    ContentTypeOf[JsValue](Some(ContentTypes.JSON)) // -> "application/json"
  }
  implicit def contentTypeOf_String(implicit codec: Codec): ContentTypeOf[String] = {
    ContentTypeOf[String](Some(ContentTypes.TEXT)) // -> "text/plain"
  }
  ...
}

Let’s try Scala

!!!!!!ここにScalaちゃんの画像!!!!!!