*метод:
Це повертає проекцію за замовчуванням - ось як ви описуєте:
'усі стовпці (або обчислені значення), які мені зазвичай цікаві'.
Ваша таблиця може мати кілька полів; вам потрібна лише підмножина для вашої проекції за замовчуванням. Проекція за замовчуванням повинна відповідати типовим параметрам таблиці.
Візьмемо по черзі. Без <>речей, просто *:
object Bars extends Table[(Int, String)]("bar") {
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
def name = column[String]("name")
def * = id ~ name
}
Просто таке визначення таблиці дозволить вам робити такі запити:
implicit val session: Session =
val result = Query(Bars).list
проекція за замовчуванням (Int, String)веде до a List[(Int, String)]
для простих запитів, таких як ці.
val q =
for (b <- Bars if b.id === 42)
yield (b.name ~ 1)
Що це за тип q? Це a Queryз проекцією (String, Int). Коли Ви телефонуєте, він повертає Listз (String, Int)кортежів відповідно з виступом.
val result: List[(String, Int)] = q.list
У цьому випадку ви визначили потрібну проекцію в yieldпункті forрозуміння.
Тепер про <>і Bar.unapply.
Це забезпечує так звані картографічні проекції .
Дотепер ми бачили, як slick дозволяє виражати запити в Scala, які повертають проекцію стовпців (або обчислених значень); Отже, виконуючи ці запити, ви повинні думати про рядок результатів запиту як про кортеж Scala . Тип кортежу буде відповідати Проекції, яка визначена (за Вашим
forрозумінням, як у попередньому прикладі, за *проекцією за замовчуванням ). Ось чому field1 ~ field2повертається проекція, Projection2[A, B]де
Aє тип field1і Bє тип field2.
q.list.map {
case (name, n) =>
}
Queury(Bars).list.map {
case (id, name) =>
}
Ми маємо справу з кортежами, які можуть бути громіздкими, якщо у нас занадто багато стовпців. Ми хотіли б думати про результати не як про TupleNякийсь об'єкт з іменованими полями.
(id ~ name)
case class Bar(id: Int, name: String) // For now, using a plain Int instead
(id ~ name <> (Bar, Bar.unapply _))
Query(Bars).list.map ( b.name )
Як це працює? <>бере проекцію 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")
val Bar(id, name) = bar1
val bars: List[Bar] =
val barNames = bars.map {
case Bar(_, name) => name
}
val x = Bar.unapply(bar1)
Тож ваша проекція за замовчуванням може бути зіставлена з класом випадку, який ви найчастіше використовуєте:
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)
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
Нарешті, як осторонь, .?пропонуються варіанти підйому - шкала способу роботи з цінностями, яких може не бути.
(id ~ name)
(id.? ~ name)
Що, завершуючи, буде добре працювати з вашим початковим визначенням Bar:
case class Bar(id: Option[Int] = None, name: String)
val q0 =
for (b <- Bars if b.id === 42)
yield (b.id.? ~ b.name <> (Bar, Bar.unapply _))
q0.list
У відповідь на коментар про те, як Slick використовує forрозуміння:
Так чи інакше, монадам завжди вдається з'явитися і вимагати бути частиною пояснення ...
Бо розуміння не стосується лише колекцій. Їх можна використовувати на будь-яких видах Монад , а колекції - лише один із багатьох видів монад, доступних у Scala.
Але оскільки колекції знайомі, вони служать гарною відправною точкою для пояснення:
val ns = 1 to 100 toList;
val result =
for { i <- ns if i*i % 2 == 0 }
yield (i*i)
У Scala a для розуміння - це синтаксичний цукор для викликів методів (можливо, вкладених): Наведений вище код (більш-менш) еквівалентний:
ns.filter(i => i*i % 2 == 0).map(i => i*i)
В принципі, нічого з filter, map, flatMap
методи (іншими словами, Монада ) може бути використаний в
forрозумінні замість ns. Хороший приклад - монада Option . Ось попередній приклад , де ж forоператор працює на обох
List, а також Optionмонади:
val result =
for {
i <- ns
i2 <- Some(i*i)
if i2 % 2 == 0
} yield i2
def evenSqr(n: Int) = {
val sqr = n*n
if (sqr % 2 == 0) Some (sqr)
else None
}
result =
for {
i <- ns
i2 <- evenSqr(i)
} yield i2
В останньому прикладі перетворення могло б виглядати так:
val result =
ns.flatMap(i => Some(i*i)).filter(i2 => i2 %2 ==0)
result =
ns.flatMap(i => evenSqr(i))
У сліки, запити Монадический - вони просто об'єкти з map, flatMapі filterметоду. Отже, forрозуміння (показано в поясненні *методу) просто перекладається на:
val q =
Query(Bars).filter(b => b.id === 42).map(b => b.name ~ 1)
val r: List[(String, Int)] = q.list
Як ви можете бачити, flatMap, mapі filterвикористовуються для генерації Queryшляхом багаторазового перетворення Query(Bars)
з кожним викликом filterі map. У випадку з колекціями ці методи фактично перебирають та фільтрують колекцію, але в Slick вони використовуються для створення SQL. Детальніше тут:
Як Scala Slick перекладає код Scala у JDBC?