2012-12-16 18 views
85

我尝试了解一些滑动工作以及它需要什么。斯卡拉滑动方法我目前无法理解

这一个例子:

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的实例?

回答

189

[更新] - 添加(另一个)上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 
    

    在这种情况下,你已经确定你的for理解的yield条款 在希望的投影。

  2. 现在关于<>Bar.unapply

    这提供了所谓的映射预测

    到目前为止,我们已经看到了如何华而不实,您可以表达斯卡拉 返回一个投影列(或计算值)的查询;因此,在执行 这些查询时你得想查询作为斯卡拉元组结果行的。 元组的类型将与定义的投影相匹配(通过您在前例中的 for理解,由默认*投影)。 这就是为什么field1 ~ field2返回Projection2[A, B]一个投影,其中 Afield1B类型是的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)去一个Bar所需的 信息。如果您已经猜到了,那么 是相反的。

    来自哪里unapply?这是一个标准的Scala方法可供 任何普通案例类 - 只是定义Bar给你一个Bar.unapply其 是提取可以用来找回了 Bar与建造的idname

    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。 调用时,它返回BazList对象:

    val result: List[Baz] = q1.list 
    
  3. 最后,顺便说一句,在.?提供选项起重 - 的 Scala的方式处理值可能不是。

    (id ~ name) // Projection2[Int, String] // this is just for illustration 
    (id.? ~ name) // Projection2[Option[Int], String] 
    

    其中,包裹起来,将很好地与原有的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] 
    
  4. 为响应评论用途如何油滑for内涵:

    不知何故,单子总是设法出现和要求到 作为解释的一部分...

    理解不仅限于集合。 它们可以用于任何种类的Monad,集合 只是Scala中可用的各种monad类型之一。

    但作为收藏品都很熟悉,他们提出了一个很好的起点 点解释:

    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, ...) 
    

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

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

    基本上,filter什么,mapflatMap 方法(换句话说,Monad)可以用在 for的理解中,代替ns。一个很好的例子是 是Option monad。下面是前面的例子 在同一for声明工程对 List双方以及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)) 
    

    在Slick中,查询是monadic - 它们只是 ,map,flatMapfilter方法的对象。所以for理解 (在*方法的说明中所示),只是转换为:

    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 
    

    正如你所看到的,flatMapmapfilter用于 由Query(Bars) 反复变换生成Query每次调用filtermap。对于 集合,这些方法实际上会迭代并过滤集合 ,但在Slick中它们用于生成SQL。更多细节在这里: How does Scala Slick translate Scala code into JDBC?

+11

美丽的答案。 –

+0

在'1'解释块中:'val q ='是WrappingQuery,并不明显,它在阅读代码时看起来像列表。它如何转换为Query ..? (我还在玩你的解释,以了解它是如何工作的,谢谢你!) – ses

+0

@ses - 添加了一个(稍微长一点的)关于这个的解释...另外,看看http://stackoverflow.com/questions/13454347/monads-with-java-8/13455602#13455602 - 我意识到这是几乎相同的内容。 – Faiz

6

由于没有人回答过,所以这可能有助于开始。我不太了解Slick。

Slick documentation

解禁嵌入:

每个表需要一个*方法指含有一个默认的投影。这个 描述了当您从查询返回行(以表格对象的形式为 )时您返回的结果。 Slick的*投影不必 与数据库中的匹配。您可以添加新列(例如,使用 计算得出的值),或者根据需要省略一些列。对应于*投影的未提升类型 作为 表的类型参数给出。对于简单的非映射表,这将是单列 类型或列类型的元组。

换句话说,浮油需要知道如何处理从数据库返回的行。你定义的方法使用它们的解析器组合函数来将你的列定义组合成可以在一行上使用的东西。

+0

ook。和投影是列只是表示..像: 最终类Projection2 [T1,T2]( 倍率VAL _1:柱[T1], 倍率VAL _2:柱[T2] ) 延伸Tuple2(_1, _2)与投影[(T1,T2)] {.. – ses

+0

现在..怎么会这样:Bar有'unapply'方法? – ses

+2

Aha .. - 所有案例类实现产品特质,而不应用是Product的方法。魔法。 – ses