метод scala slick, якого я поки не можу зрозуміти


89

Я намагаюся зрозуміти деякі роботи Slick і те, що для цього потрібно.

Ось приклад:

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 - метод ~'повертає екземпляр 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)веде до a 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? Це a Queryз проекцією (String, Int). Коли Ви телефонуєте, він повертає Listз (String, Int)кортежів відповідно з виступом.

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

    У цьому випадку ви визначили потрібну проекцію в yieldпункті forрозуміння.

  2. Тепер про <>і Bar.unapply.

    Це забезпечує так звані картографічні проекції .

    Дотепер ми бачили, як slick дозволяє виражати запити в Scala, які повертають проекцію стовпців (або обчислених значень); Отже, виконуючи ці запити, ви повинні думати про рядок результатів запиту як про кортеж Scala . Тип кортежу буде відповідати Проекції, яка визначена (за Вашим forрозумінням, як у попередньому прикладі, за *проекцією за замовчуванням ). Ось чому field1 ~ field2повертається проекція, Projection2[A, B]де Aє тип field1і Bє тип 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. Два аргументи чітко Bar, Bar.unapply _ говорять про те, як ця (Int, String)проекція повинна бути зіставлена ​​з класом справи.

    Це двостороннє відображення; Barє конструктором класу case, тому це інформація, необхідна для переходу від (id: Int, name: String)до Bar. І unapply якщо ви здогадалися, це навпаки.

    Звідки береться unapply? Це стандартний метод Scala, доступний для будь-якого звичайного класу case - просто визначення Barдає вам a, Bar.unapplyякий є екстрактором, який можна використовувати для поверненняid і nameщо 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. Коли Ви телефонуєте, він повертає Listз Bazоб'єктів:

     val result: List[Baz] = q1.list
    
  3. Нарешті, як осторонь, .?пропонуються варіанти підйому - шкала способу роботи з цінностями, яких може не бути.

     (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. У відповідь на коментар про те, як Slick використовує forрозуміння:

    Так чи інакше, монадам завжди вдається з'явитися і вимагати бути частиною пояснення ...

    Бо розуміння не стосується лише колекцій. Їх можна використовувати на будь-яких видах Монад , а колекції - лише один із багатьох видів монад, доступних у Scala.

    Але оскільки колекції знайомі, вони служать гарною відправною точкою для пояснення:

    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 a для розуміння - це синтаксичний цукор для викликів методів (можливо, вкладених): Наведений вище код (більш-менш) еквівалентний:

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

    В принципі, нічого з filter, map, flatMap методи (іншими словами, Монада ) може бути використаний в 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)) 
    

    У сліки, запити Монадический - вони просто об'єкти з 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
    

    Як ви можете бачити, flatMap, mapі filterвикористовуються для генерації Queryшляхом багаторазового перетворення Query(Bars) з кожним викликом filterі map. У випадку з колекціями ці методи фактично перебирають та фільтрують колекцію, але в Slick вони використовуються для створення SQL. Детальніше тут: Як Scala Slick перекладає код Scala у JDBC?


У блоці пояснення "1": Не очевидно, що "val q =" є WrappingQuery, він виглядає як List <Projection2> під час читання коду. Як це можливо, якщо воно перетворюється на Query ..? (Я все ще граюся з вашими поясненнями, щоб зрозуміти, як це працює. Дякую за це!)
ses

@ses - додав (трохи довге) пояснення з цього приводу ... Крім того, подивіться на stackoverflow.com/questions/13454347/monads-with-java-8/… - я зрозумів, що це майже однаковий вміст.
Faiz

Зверніть увагу на тих, хто зазнає загадкових помилок компіляції, використовуйте foo.? для стовпців Option [T], або ви отримаєте важку для зчитування невідповідність типу. Дякую, Фаїз!
sventechie

1
Це чудова відповідь ... було б чудово, хоча б її можна було оновити для Slick 3.0
Ixx

6

Оскільки ніхто не відповів, це може допомогти вам розпочати. Я не дуже добре знаю Сліка.

З документації Slick :

Підняте вбудовування:

Кожна таблиця вимагає методу *, що містить проекцію за замовчуванням. Це описує те, що ви отримуєте назад, коли повертаєте рядки (у вигляді об’єкта таблиці) із запиту. Проекція * Slick не повинна збігатися з проекцією в базі даних. Ви можете додати нові стовпці (наприклад, з обчисленими значеннями) або пропустити деякі стовпці, як вам подобається. Непіднятий тип, що відповідає проекції *, заданий як параметр типу для таблиці. Для простих невіднесених таблиць це буде тип одного стовпця або набір типів стовпців.

Іншими словами, slick повинен знати, як поводитися з рядком, повернутим із бази даних. Визначений вами метод використовує функції комбінатора синтаксичного аналізатора для об'єднання визначень стовпців у щось, що може використовуватися в рядку.


ой а Проекція - це просто подання стовпців .. як: кінцевий клас Projection2 [T1, T2] (перевизначення val _1: Стовпець [T1], перевизначення val _2: Стовпець [T2]) розширює Tuple2 (_1, _2) за допомогою Проекції [( Т1, Т2)] {..
SES

Тепер .. як це: у барі є метод «не застосовувати»?
ses

2
Ага .. - усі класи case реалізують ознаку Product, а unapply - це метод Product. Магія.
ses
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.