wake-up-neo.com

scala schicke Methode, die ich bisher nicht verstehen kann

Ich versuche einige Slick-Werke zu verstehen und was es erfordert.

Hier ein Beispiel:

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

Könnte mir jemand erklären, wozu die Methode * Dient, was <> Ist, warum unapply? und was ist Projektionsmethode ~ 'gibt die Instanz von Projection2 zurück?

87
ses

[UPDATE] - (noch eine) Erklärung zum for Verständnis hinzugefügt

  1. Die * Methode:

    Dies liefert die Standardprojektion - wie Sie es beschreiben:

    'Alle Spalten (oder berechneten Werte), an denen ich normalerweise interessiert bin'.

    Ihre Tabelle kann mehrere Felder enthalten. Sie benötigen nur eine Teilmenge für Ihre Standardprojektion. Die Standardprojektion muss mit den Typparametern der Tabelle übereinstimmen.

    Nehmen wir es nacheinander. Ohne das Zeug <> Nur das Zeug *:

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

    Mit einer solchen Tabellendefinition können Sie Abfragen wie die folgenden durchführen:

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

    die Standardprojektion von (Int, String) führt bei einfachen Abfragen wie diesen zu einem 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.
    

    Was ist die Art von q? Es ist ein Query mit der Projektion (String, Int). Beim Aufrufen wird ein List von (String, Int) - Tupeln gemäß der Projektion zurückgegeben.

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

    In diesem Fall haben Sie die gewünschte Projektion in der yield -Klausel des for -Verständnisses definiert.

  2. Nun zu <> Und Bar.unapply.

    Dies liefert sogenannte Mapped Projections.

    Bisher haben wir gesehen, wie Sie mit Slick Abfragen in Scala, die eine Projektion von Spalten zurückgeben (oder berechnete Werte) ausdrücken können. Also, wenn Sie diese Abfragen ausführen Sie müssen sich die Ergebniszeile vorstellen einer Abfrage als Scala Tuple Der Tupeltyp entspricht der definierten Projektion (durch Ihr for Verständnis wie im vorherigen Beispiel oder durch die Standardprojektion *. Deshalb field1 ~ field2). ] gibt eine Projektion von Projection2[A, B] zurück, wobei A der Typ von field1 und B der Typ von field2 ist.

    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 
    }
    

    Es handelt sich um Tupel, die bei zu vielen Spalten möglicherweise umständlich sind. Wir möchten uns die Ergebnisse nicht als TupleN vorstellen, sondern als ein Objekt mit benannten Feldern.

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

    Wie funktioniert das? <> Erstellt eine Projektion Projection2[Int, String] Und gibt eine zugeordnete Projektion vom Typ Bar zurück. Die beiden Argumente Bar, Bar.unapply _ Geben an, wie diese (Int, String) - Projektion einer Fallklasse zugeordnet werden muss.

    Dies ist eine bidirektionale Zuordnung. Bar ist der Fallklassenkonstruktor, das sind also die Informationen, die benötigt werden, um von (id: Int, name: String) zu einem Bar zu gelangen. Und unapply, wenn Sie es erraten haben, ist für das Gegenteil.

    Woher kommt unapply? Dies ist eine Standardmethode Scala für jede gewöhnliche Fallklasse - wenn Sie nur Bar definieren, erhalten Sie einen Bar.unapply - Extraktor , mit dem die id und name zurückgegeben werden können, mit denen Bar erstellt wurde:

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

    So kann Ihre Standardprojektion der Fallklasse zugeordnet werden, die Sie voraussichtlich verwenden werden:

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

    Oder Sie können es sogar per Abfrage haben:

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

    Hier ist der Typ von q1 Ein Query mit einer zugeordneten Projektion auf Baz. Beim Aufruf wird ein List von Baz Objekten zurückgegeben:

     val result: List[Baz] = q1.list
    
  3. Abgesehen davon bietet der .? Die Option Lifting - die Scala= Art und Weise, mit Werten umzugehen, die möglicherweise nicht vorhanden sind.

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

    Was zum Schluss gut mit Ihrer ursprünglichen Definition von Bar zusammenarbeitet:

    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. Als Antwort auf den Kommentar, wie Slick for -Verständnisse verwendet:

    Irgendwie schaffen es Monaden immer, sich zu zeigen und zu fordern, Teil der Erklärung zu sein ...

    Denn das Verständnis bezieht sich nicht nur auf Sammlungen. Sie können für jede Art von Monad verwendet werden, und Sammlungen sind nur eine der vielen Arten von Monadentypen, die in Scala verfügbar sind.

    Da Sammlungen jedoch vertraut sind, bieten sie einen guten Ausgangspunkt für eine Erklärung:

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

    In Scala ist a für das Verständnis syntaktischer Zucker für Methodenaufrufe (möglicherweise verschachtelt): Der obige Code ist (mehr oder weniger) äquivalent zu:

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

    Grundsätzlich kann alles mit den Methoden filter, map, flatMap (mit anderen Worten eine Monade) in einem for Verständnis anstelle von ns. Ein gutes Beispiel ist die Option monad . Hier ist das vorherige Beispiel, in dem dieselbe for -Anweisung sowohl für die List- als auch für die Option-Monaden gilt:

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

    Im letzten Beispiel würde die Transformation vielleicht so aussehen:

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

    In Slick sind Abfragen monadisch - es handelt sich lediglich um Objekte mit den Methoden map, flatMap und filter. Das for -Verständnis (gezeigt in der Erklärung der * - Methode) übersetzt sich einfach in:

    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
    

    Wie Sie sehen können, werden flatMap, map und filter verwendet, um durch wiederholte Transformation von Query(Bars) mit jedem einen Query zu erzeugen Aufruf von filter und map. Bei Auflistungen iterieren und filtern diese Methoden die Auflistung, in Slick werden sie jedoch zum Generieren von SQL verwendet. Weitere Details hier: Wie übersetzt Scala Slick Scala Code in JDBC?

195
Faiz

Da noch niemand geantwortet hat, kann dies den Einstieg erleichtern. Ich kenne Slick nicht sehr gut.

Aus der Slick-Dokumentation :

Aufgehobene Einbettung:

Jede Tabelle erfordert eine * -Methode, die eine Standardprojektion enthält. Dies beschreibt, was Sie erhalten, wenn Sie Zeilen (in Form eines Tabellenobjekts) aus einer Abfrage zurückgeben. Die * -Projektion von Slick muss nicht mit der in der Datenbank übereinstimmen. Sie können neue Spalten hinzufügen (z. B. mit berechneten Werten) oder einige Spalten weglassen, wie Sie möchten. Der nicht angehobene Typ, der der * -Projektion entspricht, wird als Typparameter für Tabelle angegeben. Bei einfachen, nicht zugeordneten Tabellen ist dies ein einzelner Spaltentyp oder ein Tupel von Spaltentypen.

Mit anderen Worten, Slick muss wissen, wie mit einer aus der Datenbank zurückgegebenen Zeile umgegangen wird. Die von Ihnen definierte Methode verwendet ihre Parser-Kombinatorfunktionen, um Ihre Spaltendefinitionen in etwas zu kombinieren, das für eine Zeile verwendet werden kann.

6