*
Метод:
Это возвращает проекцию по умолчанию - как вы описываете:
"все столбцы (или вычисленные значения), которые меня обычно интересуют".
В вашей таблице может быть несколько полей; вам нужно только подмножество для вашей проекции по умолчанию. Проекция по умолчанию должна соответствовать параметрам типа таблицы.
Давайте возьмем это по одному. Без <>
всего этого *
:
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)
ведет к List[(Int, String)]
для таких простых запросов, как эти.
val q =
for (b <- Bars if b.id === 42)
yield (b.name ~ 1)
Какой тип q
? Это 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)
проекция должна быть сопоставлена с классом case.
Это двустороннее отображение; Bar
- конструктор класса case, поэтому информация, необходимая для перехода от (id: Int, name: String)
к Bar
. И unapply
если вы догадались, все наоборот.
Откуда unapply
взялось? Это стандартный метод Scala, доступный для любого обычного класса case - простое определение Bar
дает вам, 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)
Таким образом, ваша проекция по умолчанию может быть сопоставлена с классом case, который вы больше всего ожидаете использовать:
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
Наконец, в стороне, .?
предлагается Option Lifting - способ Scala работать со значениями, которых может не быть.
(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
В ответ на комментарий о том, как Слик использует for
понимание:
Каким-то образом монады всегда удается появиться и потребовать участия в объяснении ...
Ибо понимания относятся не только к коллекциям. Их можно использовать в монадах любого типа , а коллекции - это лишь один из многих типов типов монад, доступных в Scala.
Но поскольку коллекции знакомы, они являются хорошей отправной точкой для объяснения:
val ns = 1 to 100 toList;
val result =
for { i <- ns if i*i % 2 == 0 }
yield (i*i)
В Scala для понимания - это синтаксический сахар для вызовов методов (возможно, вложенных): приведенный выше код (более или менее) эквивалентен:
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?