スカラスリック方式今までわからない


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ですか、なぜですか?そしてProjection-methodとは~'のインスタンスを返しますProjection2か?

回答:


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)。呼び出されると、投影に従ってタプルのa Listを返します(String, Int)

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

    この場合、理解度のyield節で必要な投影法を定義しましたfor

  2. 今について<>Bar.unapply

    これにより、マップされた投影と呼ばれるものが提供されます。

    これまで、scalを使用して、列(または計算値)のプロジェクションを返すクエリを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。2つの引数Bar, Bar.unapply _ は、この(Int, String)プロジェクションをケースクラスにマッピングする方法をスリックに指示します。

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

    どこunapplyから来たの?これは、任意の通常の場合クラスで利用可能な標準のScala方法である-ちょうど定義がBarあなたに与えBar.unapplyているその抽出戻って取得するために使用することができるidnameこと Barで構築されました:

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

     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理解をどのように使用するかについてのコメントに応えて:

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

    内包表記はコレクションだけに固有のものではありません。これらはあらゆる種類のモナドで使用でき、コレクションは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 (つまり、メソッドモナドは)で使用することができる forの代わりに理解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では、クエリはモナディックです-それらはmapflatMapfilterメソッドを持つ単なるオブジェクトです。したがって、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生成するために使用されているQueryのを繰り返し変換によってQuery(Bars) それぞれの呼び出しでfiltermap。コレクションの場合、これらのメソッドは実際にコレクションを反復してフィルタリングしますが、SlickではSQLの生成に使用されます。詳細はこちら: Scala SlickはどのようにしてScalaコードをJDBCに変換しますか?


「1」の説明ブロックでは、「val q =」がWrappingQueryであることは明らかではありません。コードの読み取り中は、List <Projection2>のように見えます。それがクエリに変換することはどのように可能ですか?(私はまだそれがどのように機能するかを理解するためにあなたの説明で遊んでいます。これをありがとう!)
セス

@ses-これについて(少し長い)説明を追加しました...また、stackoverflow.com / questions / 13454347 / monads-with-java-8 /を参照してください...- ほとんど同じ内容であることに気付きました。
ファイズ

不可解なコンパイルエラーが発生した場合は、fooを使用してください。Option [T]列の場合、タイプの不一致が読みにくくなります。ありがとう、ファイズ!
sventechie 2013年

1
これは素晴らしい答えです... Slick 3.0のために更新できれば素晴らしいでしょう
Ixx

6

他の誰も答えていないので、これはあなたが始めるのに役立つかもしれません。私はスリックをよく知りません。

Slickドキュメントから:

埋め込みの解除:

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

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


うん。そして、Projectionは列の単なる表現です。のように:final class Projection2 [T1、T2](override val _1:Column [T1]、override val _2:Column [T2])extends Tuple2(_1、_2)with Projection [( T1、T2)] {..
ses

さて、どうしてそれが起こったのですか:Barには 'unapply'メソッドがありますか?
SES

2
Aha ..-すべてのケースクラスはProductトレイトを実装し、適用解除はProductのメソッドです。マジック。
SES
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.