harryのブログ

ロードバイクとか模型とかゲームについて何か書いてあるかもしれません

`DB.withConnection{ implicit c => }`のimplicitってなんなの?

  • この記事は 2016年04月14日 にQiitaへ投稿した内容を転記したものです。
  • 本記事は執筆から1年以上が経過しています。

前書き

職場の新人氏が質問してて「調べてもおまじないとしか書かれてなくて…」とのことだったので書きました。

で、なんなん?

DB.withConnection()は引数にConnection => A型の関数を取るメソッドなので、(c: Connection) => Aの無名関数を渡しています。 ただし、引数はimplicit引数にしています。

// 省略していない式
DB.withConnection( (implicit c: Connection) => Some("value") )

// メソッド定義でcの引数の型(Connection)はわかっているので省略可能
DB.withConnection( (implicit c) => Some("value") )

// 無名関数の引数が1つなので、丸括弧は省略可能
DB.withConnection( implicit c => Some("value") )

// 引数の関数を囲ってる () は {} にできる
DB.withConnection { implicit c =>
  Some("value")
}

なんでimplicit引数にしてるの?

ここでSQL操作を行うと思うのですが、例えばAnormのSqlResult.as()等のimplicit引数へConnectionを暗黙的に渡すためです。

DB.withConnection { implicit c =>
  SQL("""
    |SELECT *
    |FROM User
    |WHERE logged_in BETWEEN {from} AND {to}""".stripMargin
  ).on(
    'from -> from,
    'to -> to
  ).as(userParser *)
}
  /**
   * Converts this query result as `T`, using parser.
   */
  def as[T](parser: ResultSetParser[T])(implicit connection: Connection): T =
    Sql.asTry(parser, resultSet(connection), resultSetOnFirstRow).get

なので、一応、下記のように書くこともできます。

DB.withConnection { c =>
  implicit val conn = c

  SQL("""
    |SELECT *
    |FROM User
    |WHERE logged_in BETWEEN {from} AND {to}""".stripMargin
  ).on(
    'from -> from,
    'to -> to
  ).as(userParser *)
}

または

DB.withConnection { c =>
  SQL("""
    |SELECT *
    |FROM User
    |WHERE logged_in BETWEEN {from} AND {to}""".stripMargin
  ).on(
    'from -> from,
    'to -> to
  ).as(userParser *)(c)
}

このようなimplicit引数の動作は、REPL等でも確認することができます。

  def foo(implicit f: Int): Int = f * 10

  def bar(implicit b: Int): Int = {
    // implicit引数の b が暗黙的に使用される
    foo
  }

  def baz(b: Int): Int = {
    implicit val i = b
    // i が暗黙的に使用される
    foo
  }

  println(foo(5)) // 50
  println(bar(6)) // 60
  println(baz(7)) // 70

実際に関数へConnectionを渡している所は?

例えば、DefaultDatabase.withConnection()とか。

  def withConnection[A](block: Connection => A): A = {
    withConnection(autocommit = true)(block)
  }

  def withConnection[A](autocommit: Boolean)(block: Connection => A): A = {
    val connection = getConnection(autocommit)
    try {
      block(connection)
    } finally {
      connection.close()
    }
  }