metode licin scala saya tidak bisa mengerti sejauh ini

89

Saya mencoba memahami beberapa karya Slick dan apa yang dibutuhkannya.

Ini contohnya:

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 _)
}

Bisakah seseorang menjelaskan kepada saya apa tujuan *metode di sini, apa <>, mengapa unapply? dan apa Projection - method ~'mengembalikan instance dari Projection2?

ses
sumber

Jawaban:

198

[UPDATE] - menambahkan (lagi) penjelasan tentang forpemahaman

  1. The *Metode:

    Ini mengembalikan proyeksi default - seperti yang Anda gambarkan:

    'semua kolom (atau nilai yang dihitung) yang biasanya saya minati'.

    Tabel Anda dapat memiliki beberapa bidang; Anda hanya memerlukan subset untuk proyeksi default Anda. Proyeksi default harus sesuai dengan jenis parameter tabel.

    Mari kita lakukan satu per satu. Tanpa <>barang, hanya *:

    // 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)
    

    Hanya definisi tabel seperti itu yang akan memungkinkan Anda membuat kueri seperti:

    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)]
    

    proyeksi default dari (Int, String)prospek ke List[(Int, String)] untuk kueri sederhana seperti ini.

    // 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.
    

    Apa jenisnya q? Ini adalah Querydengan proyeksi (String, Int). Saat dipanggil, ia mengembalikan a Listdari (String, Int)tupel sesuai proyeksi.

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

    Dalam hal ini, Anda telah menentukan proyeksi yang Anda inginkan dalam yieldklausa forpemahaman.

  2. Sekarang tentang <>dan Bar.unapply.

    Ini memberikan apa yang disebut Proyeksi yang Dipetakan .

    Sejauh ini kita telah melihat bagaimana slick memungkinkan Anda untuk mengekspresikan kueri di Scala yang mengembalikan proyeksi kolom (atau nilai yang dihitung); Jadi saat menjalankan kueri ini, Anda harus memikirkan baris hasil kueri sebagai tupel Scala . Jenis tupel akan cocok dengan Proyeksi yang ditentukan (oleh forpemahaman Anda seperti pada contoh sebelumnya, dari *proyeksi default ). Inilah sebabnya mengapa field1 ~ field2mengembalikan proyeksi di Projection2[A, B]mana Ajenis field1dan Bjenisnya 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 
    }
    

    Kita berurusan dengan tupel, yang mungkin merepotkan jika kita memiliki terlalu banyak kolom. Kami ingin menganggap hasil bukan sebagai TupleNobjek dengan bidang bernama.

    (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.
    

    Bagaimana cara kerjanya? <>mengambil proyeksi Projection2[Int, String]dan mengembalikan proyeksi yang dipetakan pada tipe Bar. Kedua argumen tersebut Bar, Bar.unapply _ menjelaskan secara apik bagaimana (Int, String)proyeksi ini harus dipetakan ke kelas kasus.

    Ini adalah pemetaan dua arah; Baradalah konstruktor kelas kasus, jadi itulah informasi yang diperlukan untuk beralih dari (id: Int, name: String)ke a Bar. Dan unapply jika Anda sudah dapat menebaknya, itu sebaliknya.

    Dari mana unapplyasalnya Ini adalah metode Scala standar yang tersedia untuk semua kelas kasus biasa - hanya dengan mendefinisikan yang Barmemberi Anda Bar.unapplysebuah ekstraktor yang dapat digunakan untuk mendapatkan kembali iddan nameyang Bartelah dibangun dengan:

    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)]
    

    Jadi proyeksi default Anda dapat dipetakan ke kelas kasus yang paling ingin Anda gunakan:

    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 _)
    }
    

    Atau Anda bahkan dapat memilikinya per kueri:

    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 _))
    

    Di sini tipe q1 adalah a Querydengan proyeksi yang dipetakan ke Baz. Saat dipanggil, ini mengembalikan a Listdari Bazobjek:

     val result: List[Baz] = q1.list
    
  3. Terakhir, sebagai tambahan, .?penawaran Option Lifting - cara Scala menangani nilai-nilai yang mungkin tidak.

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

    Yang mana, sebagai penutup, akan bekerja dengan baik dengan definisi asli Anda tentang 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. Menanggapi komentar tentang bagaimana Slick menggunakan forpemahaman:

    Entah bagaimana, monad selalu muncul dan menuntut untuk menjadi bagian dari penjelasan ...

    Untuk pemahaman tidak khusus untuk koleksi saja. Mereka dapat digunakan pada semua jenis Monad , dan koleksinya hanyalah salah satu dari banyak jenis monad yang tersedia di Scala.

    Tetapi karena koleksi sudah familiar, mereka menjadi titik awal yang baik untuk penjelasan:

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

    Dalam Scala, pemahaman for adalah gula sintaksis untuk panggilan metode (mungkin bersarang): Kode di atas (lebih atau kurang) setara dengan:

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

    Pada dasarnya, apa-apa dengan filter, map, flatMap metode (dengan kata lain, sebuah Monad ) dapat digunakan dalam forpemahaman di tempat ns. Contoh yang bagus adalah Option monad . Berikut contoh sebelumnya di mana sama forpernyataan bekerja pada kedua Listserta Optionmonads:

    // (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
    

    Pada contoh terakhir, transformasi mungkin akan terlihat seperti ini:

    // 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)) 
    

    Di Slick, kueri bersifat monadik - mereka hanyalah objek dengan metode map, flatMapdan filter. Jadi forpemahaman (ditunjukkan dalam penjelasan *metode) hanya diterjemahkan menjadi:

    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
    

    Seperti yang Anda lihat, flatMap, mapdan filterdigunakan untuk menghasilkan Queryoleh transformasi berulang Query(Bars) dengan setiap permintaan dari filterdan map. Dalam kasus koleksi, metode ini sebenarnya mengulangi dan memfilter koleksi tetapi di Slick mereka digunakan untuk menghasilkan SQL. Lebih jelasnya di sini: Bagaimana Scala Slick menerjemahkan kode Scala ke JDBC?

Faiz
sumber
Dalam blok penjelasan '1': Tidak jelas bahwa 'val q =' adalah WrappingQuery, terlihat seperti Daftar <Proyeksi2> saat membaca kode. Bagaimana mungkin itu berubah menjadi Query ..? (Saya masih bermain dengan penjelasan Anda untuk memahami cara kerjanya. Terima kasih untuk ini!)
ses
@ses - menambahkan penjelasan (agak panjang) tentang ini ... Juga, lihat stackoverflow.com/questions/13454347/monads-with-java-8/… - Saya menyadari bahwa isinya hampir sama.
Faiz
Catatan bagi mereka yang mengalami kesalahan kompilasi misterius, gunakan foo.? untuk kolom Option [T] atau Anda akan mendapatkan ketidakcocokan tipe yang sulit dibaca. Terima kasih, Faiz!
sventechie
1
Ini adalah jawaban yang bagus ... alangkah baiknya jika dapat diperbarui untuk Slick 3.0
Ixx
6

Karena tidak ada orang lain yang menjawab, ini mungkin membantu Anda memulai. Saya tidak terlalu mengenal Slick.

Dari dokumentasi Slick :

Penyematan yang Diangkat:

Setiap tabel membutuhkan metode * yang sesuai dengan proyeksi default. Ini menjelaskan apa yang Anda dapatkan kembali saat Anda mengembalikan baris (dalam bentuk objek tabel) dari kueri. Proyeksi Slick * tidak harus cocok dengan yang ada di database. Anda dapat menambahkan kolom baru (misalnya dengan nilai yang dihitung) atau menghilangkan beberapa kolom sesuka Anda. Tipe tak terangkat yang sesuai dengan proyeksi * diberikan sebagai parameter tipe ke Tabel. Untuk tabel sederhana yang tidak dipetakan, ini akan menjadi tipe kolom tunggal atau tupel tipe kolom.

Dengan kata lain, slick perlu mengetahui cara menangani baris yang dikembalikan dari database. Metode yang Anda tentukan menggunakan fungsi kombinator parsernya untuk menggabungkan definisi kolom Anda menjadi sesuatu yang dapat digunakan dalam satu baris.

Dominic Bou-Samra
sumber
ook. dan Proyeksi hanyalah representasi dari kolom .. seperti: kelas akhir Proyeksi2 [T1, T2] (timpa val _1: Kolom [T1], timpa val _2: Kolom [T2]) meluas Tuple2 (_1, _2) dengan Proyeksi [( T1, T2)] {..
ses
Sekarang .. kenapa: Bar memiliki metode 'tidak berlaku'?
ses
2
Aha .. - semua kelas kasus menerapkan sifat Produk, dan tidak menerapkan metode Produk. Sihir.
ses