Scala поддержка, подобная коллекции, как в LINQ

Насколько я понимаю, единственное, что поддерживает LINQ, которое Scala в настоящее время не использует в своей библиотеке коллекции, - это интеграция с базой данных SQL.

Насколько я понимаю, LINQ может "накапливать" различные операции и может выдавать "весь" оператор в базу данных при запросе на ее обработку, предотвращая, чтобы простой SELECT сначала копировал всю таблицу в структуры данных VM.

Если я ошибаюсь, я был бы рад, если бы меня исправили.

Если нет, что необходимо для поддержки того же в Scala?

Невозможно ли написать библиотеку, которая реализует интерфейс коллекции, но не имеет каких-либо структур данных, поддерживающих ее, а String, которая собирается со следующей коллекцией в требуемый оператор базы данных?

Или я совершенно не согласен с моими наблюдениями?

Ответ 1

Как автор ScalaQuery, мне нечего добавить в объяснение Стилгара. Часть LINQ, отсутствующая в Scala, действительно является деревом выражений. Именно по этой причине ScalaQuery выполняет все свои вычисления в типах столбцов и таблиц вместо основных типов этих объектов.

Вы объявляете таблицу как объект таблицы с проекцией (кортежем) ее столбцов, например:

class User extends Table[(Int, String)] {
  def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
  def name = column[String]("name")
  def * = id ~ name
}

User.id и User.name теперь имеют тип Column [Int] и Column [String] соответственно. Все вычисления выполняются в монаде Query (это более естественное представление запросов к базе данных, чем SQL-запросы, которые должны быть созданы из него). Возьмите следующий запрос:

val q = for(u <- User if u.id < 5) yield u.name

После некоторых неявных преобразований и desugaring это означает:

val q:Query[String] =
  Query[User.type](User).filter(u => u.id < ConstColumn[Int](5)).map(u => u.name)

Методы фильтра и карты не должны проверять свои аргументы как деревья выражений, чтобы построить запрос, они просто запускают их. Как вы можете видеть из типов, то, что внешне выглядит так: "u.id:Int < 5: Int" на самом деле "u.id:Column[Int] < u.id:Column[Int]". Запуск этого выражения приводит к запросу AST, например Operator.Relational( "<", NamedColumn ( "пользователь", "id" ), ConstColumn (5)). Аналогично, методы "фильтр" и "карта" монадии Query фактически не выполняют фильтрацию и сопоставление, а вместо этого создают AST, который описывает эти операции.

Затем QueryBuilder использует этот AST для создания фактического оператора SQL для базы данных (с синтаксисом, специфичным для СУБД).

Альтернативный подход был сделан ScalaQL, который использует плагин компилятора для непосредственной работы с деревьями выражений, чтобы они содержали только языковое подмножество, которое разрешено в запросах базы данных и статически статирует запросы.

Ответ 2

Следует отметить, что Scala имеет экспериментальную поддержку для деревьев выражений. Если вы передаете анонимную функцию в качестве аргумента методу, ожидающему параметр типа scala.reflect.Code[A], вы получите AST.

scala> import scala.reflect.Code      
import scala.reflect.Code 
scala> def codeOf[A](code: Code[A]) = code
codeOf: [A](code:scala.reflect.Code[A])scala.reflect.Code[A]
scala> codeOf((x: Int) => x * x).tree 
res8: scala.reflect.Tree=Function(List(LocalValue(NoSymbol,x,PrefixedType(ThisType(Class(scala)),Class(scala.Int)))),Apply(Select(Ident(LocalValue(NoSymbol,x,PrefixedType(ThisType(Class(scala)),Class(scala.Int)))),Method(scala.Int.$times,MethodType(List(LocalValue(NoSymbol,x$1,PrefixedType(ThisType(Class(scala)),Class(scala.Int)))),PrefixedType(ThisType(Class(scala)),Class(scala.Int))))),List(Ident(LocalValue(NoSymbol,x,PrefixedType(ThisType(Class(scala)),Class(scala.Int)))))))

Это было использовано в библиотеке генерации байт-кода "Mnemonics", которая была представлена ​​ ее автором Йоханнесом Рудольфом в Scala Days 2010.

Ответ 3

С LINQ компилятор проверяет, скомпилировано ли lambda-выражение для IEnumerable или IQueryable. Первый работает как коллекции Scala. Второй компилирует выражение в дерево выражений (т.е. Структуру данных). Сила LINQ заключается в том, что сам компилятор может перевести lambdas в деревья выражений. Вы можете написать библиотеку, которая строит деревья выражений с интерфейсом, подобным тому, что у вас есть для коллекции, но как вы собираетесь сделать компилятор для построения структур данных (вместо JVM-кода) из lambdas?

Сказав это, я не уверен, что Scala предоставляет в этом отношении. Возможно, из lambdas можно построить структуры данных в Scala, но в любом случае я считаю, что вам нужна аналогичная функция в компиляторе для создания поддержки баз данных. Имейте в виду, что базы данных не являются единственным базовым источником данных, для которого вы можете создавать поставщиков. Существует множество поставщиков LINQ, например, таких как Active Directory или Ebay API.

Изменить: Почему не может быть только API?

Чтобы делать запросы, вы не только используете методы API (фильтр, Where и т.д.), но также используете лямбда-выражения в качестве аргументов этих методов. Где (x = > x > 3) (С# LINQ). Компиляторы переводят lambdas на байт-код. API должен создавать структуры данных (деревья выражений), чтобы вы могли перевести структуру данных в базовый источник данных. В основном вам нужен компилятор, чтобы сделать это для вас.

Отказ от ответственности 1: Может быть (возможно, есть) есть способ создать прокси-объекты, которые выполняют lambdas, но перегружают операторов для создания структур данных. Это приведет к чуть более низкой производительности, чем фактическое LINQ (время выполнения и время компиляции). Я не уверен, возможна ли такая библиотека. Возможно, библиотека ScalaQuery использует аналогичный подход.

Отказ от ответственности 2: Возможно, язык Scala на самом деле может обеспечить лямбды как проверяемые объекты, чтобы вы могли получить дерево выражений. Это сделало бы лямбда-функцию в Scala эквивалентной лямбда-функции в С#. Возможно, библиотека ScalaQuery использует эту гипотетическую функцию.

Изменить 2: Я немного порылся. Кажется, что ScalaQuery использует библиотечный подход и перегружает кучу операторов для создания деревьев во время выполнения. Я не совсем уверен в деталях, потому что я не знаком с терминологией Scala и с трудом читаю сложный код Scala в статье: http://szeiger.de/blog/2008/12/21/a-type-safe-database-query-dsl-for-scala/

Как и каждый объект, который можно использовать или возвращать из запроса, таблица параметризуется типом значений, которые она представляет. Это всегда кортеж отдельных типов столбцов, в нашем случае целые и строковые (обратите внимание на использование java.lang.Integer вместо Int, об этом позже). В этом отношении SQuery (как и назвал его сейчас) ближе к HaskellDB, чем к LINQ, потому что Scala (как и большинство языков) не дает вам доступа к выражениям AST во время выполнения. В LINQ вы можете писать запросы, используя реальные типы значений и столбцов в своей базе данных, а также выражения запроса AST, переведенные в SQL во время выполнения. Без этой опции мы должны использовать мета-объекты, такие как Table и Column, для создания нашего собственного AST.

Очень классная библиотека. Я надеюсь, что в будущем он получит любовь, которой он заслуживает, и станет настоящим готовым продуктом.

Ответ 4

Вероятно, вы хотите что-то вроде http://scalaquery.org/. Он делает именно то, что предлагает @Stilgar, за исключением только SQL.