web-dev-qa-db-fra.com

scala méthode lisse que je ne comprends pas jusqu'à présent

J'essaie de comprendre certaines œuvres de Slick et ce qu'elles nécessitent.

Voici un exemple:

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

Quelqu'un pourrait-il m'expliquer quel est le but de * méthode ici, qu'est-ce que <>, pourquoi unapply? et qu'est-ce que la Projection - méthode ~ 'renvoie l'instance de Projection2?

87
ses

[MISE À JOUR] - ajout (encore une autre) explication sur for compréhensions

  1. La méthode *:

    Cela renvoie la projection par défaut - c'est ainsi que vous décrivez:

    'toutes les colonnes (ou valeurs calculées) qui m'intéressent généralement'.

    Votre table peut avoir plusieurs champs; vous n'avez besoin que d'un sous-ensemble pour votre projection par défaut. La projection par défaut doit correspondre aux paramètres de type de la table.

    Prenons-le un à la fois. Sans le truc <>, Juste le *:

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

    Juste une définition de table comme celle-ci vous permettra de faire des requêtes comme:

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

    la projection par défaut de (Int, String) conduit à une List[(Int, String)] pour les requêtes simples comme celles-ci.

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

    Quel est le type de q? Il s'agit d'un Query avec la projection (String, Int). Lorsqu'il est invoqué, il renvoie un List de (String, Int) Tuples selon la projection.

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

    Dans ce cas, vous avez défini la projection souhaitée dans la clause yield de la compréhension for.

  2. Maintenant environ <> Et Bar.unapply.

    Cela fournit ce qu'on appelle Projections cartographiées.

    Jusqu'à présent, nous avons vu comment slick vous permet d'exprimer des requêtes en Scala qui retournent une projection de colonnes (ou des valeurs calculées); Ainsi, lors de l'exécution de ces requêtes vous devez penser à la ligne de résultat d'une requête comme un Scala Tuple . Le type du Tuple correspondra à la Projection définie (par votre compréhension for comme dans l'exemple précédent, ou par la projection par défaut *). Ceci est pourquoi field1 ~ field2 renvoie une projection de Projection2[A, B]A est le type de field1 et B est le type de 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 
    }
    

    Nous avons affaire à des tuples, ce qui peut être lourd si nous avons trop de colonnes. Nous aimerions penser aux résultats non pas comme TupleN mais plutôt comme un objet avec des champs nommés.

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

    Comment cela marche-t-il? <> Prend une projection Projection2[Int, String] Et renvoie une projection mappée sur le type Bar. Les deux arguments Bar, Bar.unapply _ Indiquent à Slick comment cette projection (Int, String) Doit être mise en correspondance avec une classe de cas.

    Il s'agit d'une cartographie bidirectionnelle; Bar est le constructeur de la classe de cas, c'est donc les informations nécessaires pour passer de (id: Int, name: String) À un Bar. Et unapply si vous l'avez deviné, c'est l'inverse.

    D'où vient unapply? Il s'agit d'une méthode standard Scala disponible pour toute classe de cas ordinaire - définir simplement Bar vous donne un Bar.unapply Qui est un extracteur qui peut être utilisé pour récupérer les id et name avec lesquels le Bar a été construit avec:

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

    Ainsi, votre projection par défaut peut être mappée à la classe de cas que vous prévoyez le plus d'utiliser:

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

    Ou vous pouvez même l'avoir par requête:

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

    Ici, le type de q1 Est un Query avec une projection mappée vers Baz. Lorsqu'il est invoqué, il renvoie un List d'objets Baz:

     val result: List[Baz] = q1.list
    
  3. Enfin, en passant, le .? Offre l'option de levée - la Scala façon de traiter avec valeurs qui peuvent ne pas l'être.

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

    Ce qui, en conclusion, fonctionnera parfaitement avec votre définition originale de 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. En réponse au commentaire sur la façon dont Slick utilise les compréhensions for:

    D'une manière ou d'une autre, les monades parviennent toujours à se présenter et demandent à faire partie de l'explication ...

    Car les compréhensions ne sont pas spécifiques aux collections uniquement. Ils peuvent être utilisés sur tout type de Monad, et les collections ne sont qu'un des nombreux types de types de monade disponibles dans Scala.

    Mais comme les collections sont familières, elles constituent un bon point de départ pour une explication:

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

    Dans Scala, un pour la compréhension est un sucre syntaxique pour les appels de méthode (éventuellement imbriqués): Le code ci-dessus est (plus ou moins) équivalent à:

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

    Fondamentalement, tout ce qui a des méthodes filter, map, flatMap (en d'autres termes, une Monad) peut être utilisé dans une for compréhension à la place de ns. Un bon exemple est le Option monad . Voici l'exemple précédent où la même instruction for fonctionne à la fois sur les monades List et 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
    

    Dans le dernier exemple, la transformation ressemblerait peut-être à ceci:

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

    Dans Slick, les requêtes sont monadiques - ce ne sont que des objets avec les méthodes map, flatMap et filter. Ainsi, la compréhension de for (indiquée dans l'explication de la méthode *) Se traduit simplement par:

    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
    

    Comme vous pouvez le voir, flatMap, map et filter sont utilisés pour générer un Query par la transformation répétée de Query(Bars) avec chacun invocation de filter et map. Dans le cas des collections, ces méthodes itèrent et filtrent la collection, mais dans Slick, elles sont utilisées pour générer du SQL. Plus de détails ici: Comment Scala Slick traduit-il Scala code en JDBC?

195
Faiz

Étant donné que personne d'autre n'a répondu, cela pourrait vous aider à démarrer. Je ne connais pas très bien Slick.

De la documentation Slick :

Intégration levée:

Chaque table nécessite une méthode * contenant une projection par défaut. Ceci décrit ce que vous récupérez lorsque vous retournez des lignes (sous la forme d'un objet table) à partir d'une requête. La projection de Slick * ne doit pas nécessairement correspondre à celle de la base de données. Vous pouvez ajouter de nouvelles colonnes (par exemple avec des valeurs calculées) ou omettre certaines colonnes à votre guise. Le type non levé correspondant à la projection * est donné comme paramètre de type au tableau. Pour les tables simples non mappées, ce sera un type de colonne unique ou un tuple de types de colonne.

En d'autres termes, slick doit savoir comment gérer une ligne renvoyée par la base de données. La méthode que vous avez définie utilise leurs fonctions de combinateur d'analyseur pour combiner vos définitions de colonne en quelque chose qui peut être utilisé sur une ligne.

6
Dominic Bou-Samra