Ответ 1
[ОБНОВЛЕНИЕ] - добавлено (еще одно) объяснение в for
понятиях
-
Метод
*
:Это возвращает проекцию по умолчанию - вот как вы описываете:
'все столбцы (или вычисленные значения) меня обычно интересуют.
В вашей таблице может быть несколько полей; вам нужно только подмножество для ваша проекция по умолчанию. Проекция по умолчанию должна соответствовать типу параметры таблицы.
Пусть возьмем его по одному. Без содержимого
<>
просто*
:// 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)
приводит к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
? ЭтоQuery
с проекцией(String, Int)
. При вызове он возвращаетList
корней(String, Int)
в соответствии с проекцией.val result: List[(String, Int)] = q.list
В этом случае вы определили нужный прогноз в предложении
yield
пониманияfor
. -
Теперь о
<>
иBar.unapply
.Это обеспечивает так называемые Mapped Projections.
До сих пор мы видели, как 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 sake.
Как это работает?
<>
принимает проекцию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") // 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)]
Итак, ваша проекция по умолчанию может быть сопоставлена с классом 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) // 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
-
Наконец, в стороне
.?
предлагает Option Lifting - Scala способ которые не могут быть.(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]
-
В ответ на комментарий о том, как 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 для понимания используется синтаксический сахар для метод (возможно, вложенный) метод: приведенный выше код (более или менее) эквивалентно:
ns.filter(i => i*i % 2 == 0).map(i => i*i)
В принципе, все с
filter
,map
,flatMap
методы (другими словами, Монада) могут быть использованы вfor
понимание вместоns
. Хороший пример это Option monad. Здесь предыдущий пример где тот же операторfor
работает как наList
, а такжеOption
monads:// (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))
В Slick запросы являются монадическими - это просто объекты с методы
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
используются для сгенерируйте aQuery
повторным преобразованиемQuery(Bars)
с каждым вызовомfilter
иmap
. В случае коллекции эти методы на самом деле итерации и фильтрации коллекции но в Slick они используются для генерации SQL. Подробнее здесь: Как Scala Slick переводит код Scala в JDBC?