*
метод:
Це повертає проекцію за замовчуванням - ось як ви описуєте:
'усі стовпці (або обчислені значення), які мені зазвичай цікаві'.
Ваша таблиця може мати кілька полів; вам потрібна лише підмножина для вашої проекції за замовчуванням. Проекція за замовчуванням повинна відповідати типовим параметрам таблиці.
Візьмемо по черзі. Без <>
речей, просто *
:
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?