89

私はいくつかのスリック作品とそれが必要とするものを理解しようとしています。

ここに例があります:

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

4

2 に答える 2

198

[更新] -内包表記に関する (さらに別の) 説明を追加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です。

    これにより、 Mapped Projectionsと呼ばれるものが提供されます。

    これまでのところ、列(または計算された値)の射影を返すクエリを Scala で表現できるようにする方法について説明してきました。したがって、これらのクエリを実行するときは、クエリの結果行をScala tupleと考える必要があります。タプルのタイプは、定義されている投影と一致します ( 前の例のように理解することにより、デフォルトの投影により)。これが、が の型であり、 の型で ある の射影を返す理由です。for*field1 ~ field2Projection2[A, B]Afield1Bfield2

    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。2 つの引数は、このプロジェクションをケース クラスにマップするBar, Bar.unapply _ 方法を巧妙に伝えます。(Int, String)

    これは双方向のマッピングです。Barはケース クラスのコンストラクタであるため、 から に移動するために必要な情報(id: Int, name: String)ですBar。そしてunapply 、あなたがそれを推測したなら、それは逆です。

    どこunapplyから来たの?これBarは、通常の ケースクラスで使用できる標準の Scala メソッドです。Bar.unapplyidnameBar

    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 射影QueryBazです。List呼び出されると、次のBazオブジェクトを返します。

     val result: List[Baz] = q1.list
    
  3. 最後に、余談ですが、Option Liftingは、そうではないかもしれない値を処理する 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. forSlick が内包表記をどのように使用しているかについてのコメントに応えて:

    どういうわけか、モナドは常に現れ、説明の一部であることを要求します...

    内包表記はコレクションだけに固有のものではありません。コレクションは、あらゆる種類のMonadで使用できます。コレクションは、Scala で利用できる多くの種類のモナド型の 1 つにすぎません。

    ただし、コレクションはよく知られているため、説明の出発点として適しています。

    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 では、for 内包表記はメソッド (ネストされている可能性がある) メソッド呼び出しの構文糖衣です。上記のコードは (多かれ少なかれ) 以下と同等です。

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

    基本的に、filtermapflatMap メソッド (つまりMonadfor ) を持つものはすべて、 の代わりに内包表記で使用できます ns。良い例はOption モナドです。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 では、クエリはモナドであり、map,flatMapおよびfilterメソッドを持つ単なるオブジェクトです。したがって、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
    

    ご覧のとおりflatMapmapとは、と を呼び出すたびに を繰り返し変換することによってfilterを生成するために使用されます。コレクションの場合、これらのメソッドは実際にコレクションを反復およびフィルタリングしますが、Slick では SQL の生成に使用されます。詳細はこちら: Scala Slick はどのように Scala コードを JDBC に変換しますか?QueryQuery(Bars)filtermap

于 2012-12-17T06:28:02.990 に答える
6

他の誰も回答していないので、これはあなたが始めるのに役立つかもしれません. スリックはよくわからない。

スリックのドキュメントから:

持ち上げられた埋め込み:

すべてのテーブルには、デフォルトのプロジェクションを含む * メソッドが必要です。これは、クエリから (テーブル オブジェクトの形式で) 行を返すときに何が返されるかを示しています。Slick の * 射影は、データベースのものと一致する必要はありません。必要に応じて、新しい列 (計算値など) を追加したり、一部の列を省略したりできます。* 射影に対応する非持ち上げ型は、型パラメーターとして Table に与えられます。マップされていない単純なテーブルの場合、これは単一の列タイプまたは列タイプのタプルになります。

つまり、slick は、データベースから返された行を処理する方法を知っている必要があります。定義したメソッドは、パーサー コンビネーター関数を使用して、列定義を行で使用できるものに結合します。

于 2012-12-17T01:13:11.900 に答える