斯卡拉光滑的方法到目前为止我无法理解

时间:2012-12-16 23:51:14

标签: scala playframework-2.0 magic-methods scalaquery slick

我尝试了解一些Slick作品及其所需要的内容。

这是一个例子:

package models

case class Bar(id: Option[Int] = None, name: String)

object Bars extends Table[Bar]("bar") {
  def id = column[Int]("id", O.PrimaryKey, O.AutoInc)

  // This is the primary key column
  def name = column[String]("name")

  // Every table needs a * projection with the same type as the table's type parameter
  def * = id.? ~ name <>(Bar, Bar.unapply _)
}

有人可以在这里解释*方法的目的是什么,<>是什么,为什么unapply?什么是投影 - 方法~'返回Projection2的实例?

2 个答案:

答案 0 :(得分:194)

[更新] - for理解中添加了(又一个)解释

  1. *方法:

    这将返回默认投影 - 您的描述如下:

      

    '我通常感兴趣的所有列(或计算值)。

    你的桌子可能有几个字段;你只需要一个子集 你的默认投影。默认投影必须与类型匹配 表的参数。

    我们一次拿一个。没有<>内容,只需*

    // First take: Only the Table Defintion, no case class:
    
    object Bars extends Table[(Int, String)]("bar") {
      def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
      def name = column[String]("name")
    
      def * = id ~ name // Note: Just a simple projection, not using .? etc
    }
    
    // Note that the case class 'Bar' is not to be found. This is 
    // an example without it (with only the table definition)
    

    只需这样的表定义就可以进行如下查询:

    implicit val session: Session = // ... a db session obtained from somewhere
    
    // A simple select-all:
    val result = Query(Bars).list   // result is a List[(Int, String)]
    

    (Int, String)的默认预测会导致List[(Int, String)] 对于诸如此类的简单查询。

    // SELECT b.name, 1 FROM bars b WHERE b.id = 42;
    val q = 
       for (b <- Bars if b.id === 42) 
         yield (b.name ~ 1)
         // yield (b.name, 1) // this is also allowed: 
                              // tuples are lifted to the equivalent projection.
    

    q的类型是什么?它是Query,投影(String, Int)。  调用时,它会根据投影返回List(String, Int)元组。

     val result: List[(String, Int)] = q.list
    

    在这种情况下,您已在yield子句中定义了所需的投影  for理解的。{

  2. 现在关于<>Bar.unapply

    这提供了所谓的映射投影

    到目前为止,我们已经看到了如何使用sclick来表达Scala中的查询 返回(或计算值)的投影;所以在执行时 这些查询您必须将查询的结果行视为Scala元组。 元组的类型将与定义的投影相匹配(由您的 通过默认的for投影,与上一个示例中的*理解相同。 这就是field1 ~ field2返回Projection2[A, B]投影的原因 A类型为field1B类型为field2

    q.list.map {
      case (name, n) =>  // do something with name:String and n:Int
    }
    
    Queury(Bars).list.map {
      case (id, name) =>  // do something with id:Int and name:String 
    }
    

    我们正在处理元组,如果我们有太多,这可能很麻烦 列。我们想把结果看作TupleN而不是(id ~ name) // A projection // Assuming you have a Bar case class: case class Bar(id: Int, name: String) // For now, using a plain Int instead // of Option[Int] - for simplicity (id ~ name <> (Bar, Bar.unapply _)) // A MAPPED projection // Which lets you do: Query(Bars).list.map ( b.name ) // instead of // Query(Bars).list.map { case (_, name) => name } // Note that I use list.map instead of mapResult just for explanation's sake. 具有命名字段的对象。

    <>

    这是如何工作的? Projection2[Int, String]需要投标Bar和 返回类型Bar, Bar.unapply _上的映射投影。两个论点(Int, String) 告诉我们这个Bar投影必须如何映射到案例类。

    这是一种双向映射; (id: Int, name: String)是案例类构造函数,所以就是这样 从Barunapply所需的信息。 unapply 如果你猜对了,那就是反过来了。

    Bar来自哪里?这是可用的标准Scala方法 任何普通的案例类 - 只需定义Bar.unapply即可获得id 是一个提取器,可用于取回nameBar val bar1 = Bar(1, "one") // later val Bar(id, name) = bar1 // id will be an Int bound to 1, // name a String bound to "one" // Or in pattern matching val bars: List[Bar] = // gotten from somewhere val barNames = bars.map { case Bar(_, name) => name } val x = Bar.unapply(bar1) // x is an Option[(String, Int)] 是使用以下内容构建的:

    object Bars extends Table[Bar]("bar") {
      def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
      def name = column[String]("name")
      def * = id ~ name <>(Bar, Bar.unapply _)
    }
    

    因此,您的默认投影可以映射到您最期望使用的案例类:

    case class Baz(name: String, num: Int)
    
    // SELECT b.name, 1 FROM bars b WHERE b.id = 42;
    val q1 = 
       for (b <- Bars if b.id === 42) 
         yield (b.name ~ 1 <> (Baz, Baz.unapply _))
    

    或者您甚至可以按查询提供:

    q1

    此处Query的类型为Baz映射投影到List。  调用时,它会返回Baz val result: List[Baz] = q1.list 个对象:

    .?
  3. 最后,除了 (id ~ name) // Projection2[Int, String] // this is just for illustration (id.? ~ name) // Projection2[Option[Int], String] 提供选项提升 - Scala方式 处理可能不是的价值。

    Bar

    结束,将与case class Bar(id: Option[Int] = None, name: String) // SELECT b.id, b.name FROM bars b WHERE b.id = 42; val q0 = for (b <- Bars if b.id === 42) yield (b.id.? ~ b.name <> (Bar, Bar.unapply _)) q0.list // returns a List[Bar]

    的原始定义很好地配合使用
    for
  4. 回应关于Slick如何使用val ns = 1 to 100 toList; // Lists for familiarity val result = for { i <- ns if i*i % 2 == 0 } yield (i*i) // result is a List[Int], List(4, 16, 36, ...) 理解的评论:

    不知何故,monads总是设法出现并要求 成为解释的一部分......

    因为理解并非仅针对馆藏。 它们可用于任何类型的 Monad ,而收藏品则可用于 只是Scala中可用的多种monad类型之一。

    但是由于收藏品很熟悉,它们是一个很好的起点 要点解释:

    ns.filter(i => i*i % 2 == 0).map(i => i*i)
    

    在Scala中,理解是语法糖 方法(可能是嵌套的)方法调用:上面的代码 是(或多或少)相当于:

    filter

    基本上,mapflatMapfor 方法(换句话说, Monad )可用于a ns代替for理解List。一个很好的例子 是Option monad。这是前面的例子 其中Option语句对两者都有效 // (1) val result = for { i <- ns // ns is a List monad i2 <- Some(i*i) // Some(i*i) is Option if i2 % 2 == 0 // filter } yield i2 // Slightly more contrived example: def evenSqr(n: Int) = { // return the square of a number val sqr = n*n // only when the square is even if (sqr % 2 == 0) Some (sqr) else None } // (2) result = for { i <- ns i2 <- evenSqr(i) // i2 may/maynot be defined for i! } yield i2 以及// 1st example val result = ns.flatMap(i => Some(i*i)).filter(i2 => i2 %2 ==0) // Or for the 2nd example result = ns.flatMap(i => evenSqr(i)) monads:

    map

    在最后一个例子中,转换可能会看起来 像这样:

    flatMap

    在Slick中,查询是monadic - 它们只是对象 filterfor*方法。所以val q = Query(Bars).filter(b => b.id === 42).map(b => b.name ~ 1) // Type of q is Query[(String, Int)] val r: List[(String, Int)] = q.list // Actually run the query 理解 (显示在flatMap方法的解释中)只是转换为:

    map

    如您所见,filterQueryQuery(Bars)已用完 通过重复转换filter生成map 每次调用{{1}}和{{1}}。如果是 集合这些方法实际上迭代并过滤集合 但在Slick中,它们用于生成SQL。更多细节在这里: How does Scala Slick translate Scala code into JDBC?

答案 1 :(得分:6)

由于没有其他人回答,这可能有助于您入门。我不太了解Slick。

来自Slick documentation

  

解除嵌入:

     

每个表都需要一个包含默认投影的*方法。这个   描述返回行时返回的内容(以行的形式)   来自查询的表对象)。光滑的*投影不必   匹配数据库中的那个。您可以添加新列(例如,使用   计算值)或省略一些列。非提升型   对应于*投影作为类型参数给出   表。对于简单的非映射表,这将是一个列   类型或列类型的元组。

换句话说,光滑需要知道如何处理从数据库返回的行。您定义的方法使用其解析器组合函数将列定义组合成可以在行上使用的内容。