metaclass: (дзедline)
metaclass ([personal profile] metaclass) wrote2013-02-20 05:04 pm
Entry tags:

Записи, макросы, скала, вывод типов, SQL

В очередной раз, в связи с макросами подниму свой любимый вопрос:
Можно ли поиметь в языках типа скалы строгую типизацию, не объявляя типы записей заранее?

Идея в следующем: у меня в Clojure большая часть DSL основана на том, что я могу в любое место описания строк-колонок-ячеек отчета (условно говоря, Excel-like таблица фиксированной формы, заполняемая автоматом из проводок) в любой момент добавить атрибут типа :skip-in-vat true и обработать его. При этом мне нужно это изменить ровно в двух местах - описание документа ("данные") и расчетный алгоритм ("код"). Типов тут нет, а вместо них - структуры данных, скомбинированные из словарей, массивов и атомарных данных.

Но с динамически типизированными языками, а особенно с Clojure с ее destructring и прагматичным подходам к совместимости типов имеется постоянно проблема вида "сунул лишние скобки, на место числа попал массив и через 100500 вызовов когда дело дошло до суммирования, оператор + сдох от попытки просуммировать ссылку на массив со стек-трейсом на три страницы". Причем если мне такие ошибки чинить достаточно легко, то менее опытным коллегам это вырывает мозг.

В F# и прочих типизированных языках же мне придется сначала ползти в описание записи (если там записи вообще есть), добавлять поле или хуже того - еще один вариант для алгебраического типа, править вызовы конструкторов (если оверлоадинга нет) и прочая и прочая.

Кроме того, часто хочется такого: "тип записи создается в SQL запросе и далее используется в функции, обрабатывающей результат запроса". В clojure это опять же - словарь, с кейвордами в качестве ключей.

Языки же кроме F#, Scala и Clojure можно не рассматривать вообще - на них аналогичные задачи или сводятся к "наняли 100 ртишников по одному на объект предметной области и написали вручную" или к "написали самодельный лисп с хаскелем на дельфи/C#" или к "используем безумные ORM с еще более дикими, чем SQL языками и внешними декларативными описаниями".
Т.е. я не говорю, что они не пригодны - но затраты ресусов на решение задачи на таких языках заметно больше.

[identity profile] vshabanov.livejournal.com 2013-02-20 05:56 pm (UTC)(link)
Уж очень они жесткие. Я обычно, если надо часто какое-то вложенное поле менять просто вспомогательную ф-ию завожу, типа modifyField1 и все становится более-менее неплохо (не так часто приходится менять что-то глубоко вложенное).

Линзы это примерно то же, только автоматизированно и со своими операторами. Может, если надо постоянно ковыряться с вложенными структурами, оно и подойдет, но мне как-то без них легче.

[identity profile] plumqqz.livejournal.com 2013-02-20 05:57 pm (UTC)(link)
Мне кажется, вы путаете данные с типами данных.
Впрочем, многим это доставляет удовольствие, так что не хочу больше мешать.

[identity profile] vshabanov.livejournal.com 2013-02-20 05:57 pm (UTC)(link)
Скала -- это еще менее гуманно чем lenses ;)

[identity profile] metaclass.livejournal.com 2013-02-20 06:13 pm (UTC)(link)
Ну да, я пытаюсь сколхозить зависимые типы малой кровью.
И чтобы оно все из базы данных приходило без издевательств над здравым смыслом вида "а теперь мы схему базы данных пишем не на SQL а на руби".
Пока единственный вариант - либо генерить код, либо динамическая типизация.

[identity profile] osdm.livejournal.com 2013-02-20 06:38 pm (UTC)(link)
В TypeScript такое есть - строгая типизация + вывод типов.

[identity profile] plumqqz.livejournal.com 2013-02-20 07:11 pm (UTC)(link)
Скажите, а вот если вам доведется писать компилятор, вы тоже будете пытаться для каждого исходника отдельный тип создавать?

[identity profile] metaclass.livejournal.com 2013-02-20 07:21 pm (UTC)(link)
Нет, этим будет заниматься компилятор :)

Речь о чем:
1) работать с результатами SQL запросов гораздо проще, если их выразить в виде типизированных последовательностей. По крайней мере, C# с LINQ делает именно так. Но у него есть недостаток - полученный в результате селекта тип анонимен и его нельзя вернуть толком за пределы функции.

2) Для того чтобы добится одновременно гибкости в стиле "структуры данных из словарей и массивов" и строгой типизации - действительно нужно генерировать тип, исходя из данных. Формально это возможно, данные доступны при компиляции и конечны, их типы поддаются унификации и в можно проверить валидность полученного результата, просто посмотрев на сгенерированный тип. Как минимум, можно выдать ворнинг, если в одном и том же поле в соседних записях лежат строки, числа и функции :)

[identity profile] jdevelop.livejournal.com 2013-02-20 07:33 pm (UTC)(link)
скала и имлициты над high-kind types

[identity profile] plumqqz.livejournal.com 2013-02-20 07:33 pm (UTC)(link)
Нет, этим будет заниматься компилятор :)
Судя по тому, что вы пишете ниже, я не могу удержаться от ощущения, что вы бы предпочли заняться этим самостоятельно.

работать с результатами SQL запросов гораздо проще, если их выразить в виде типизированных последовательностей
Ну так оно и есть - у вас есть последовательность типов вида DBRecord. Или хочется завести что-то вида rec.username или rec.email? rec.phone? rec.salary? А занафига? А если его там не оказалось? Отняли у пользователя masha права на просмотр юзернеймов - и что делать теперь Маше, ставить отдельный билд, где .username отсуствует? Ну, в принципе, можно и отдельные билды для таких случаев иметь, отчего бы и нет. И даже строить их автоматически... В случае 8 таких самостоятельных полей потребуется всего-то 256 билдов; при 32 полях ситуация будет, конечно, похуже - но можно и завести отдельный пункт в договоре о поддержке.

Формально это возможно, данные доступны при компиляции
Данные при компиляции, увы, недоступны.
Edited 2013-02-20 19:35 (UTC)

[identity profile] theiced.livejournal.com 2013-02-20 07:50 pm (UTC)(link)
ну ты тупооооой

[identity profile] permea-kra.livejournal.com 2013-02-20 08:22 pm (UTC)(link)
Навскидку два варианта.
1) научить макрос лазить в базу при компиляции и генерить по DDL описания типов. Насчет скалы не знаю. в хаскеле TH это в принципе позволяет.

2) использовать ФВП
procData :: (sqlData -> Record) -> sqlData -> result
fetchData :: ( (sqlData -> Record) -> sqlData -> result) -> connection -> result.

В этом случае procData не будет иметь никакого понятия о том, какой тип ей подсунули, и работать с ней можно будет только с помощью переданного словаря. Типизация будет соблюдена. Но лишних слов при этом писать больно уж много. В этом варианте можно придумать много вариаций на тему различной степени извращенности, но крепко не уверен, что оно того стоит.


А чем вам не нравится вариант "описывать структуру базы на руби" ?

[identity profile] metaclass.livejournal.com 2013-02-20 08:22 pm (UTC)(link)
Если поля не оказалось - программа должна при запуске уведомить о том, что версия устарела - это значит, что в наше отсутствие схема БД изменилась и никаких гарантий работоспособности мы дать не можем.

use-case "отняли у маши права на просмотр полей" это очень хорошая тема. Но требуется уточнение, что вы имеете в виду под этим: административное решение "маша не должна видеть поле username" или техническую реализацию в некоторых СУБД, позволяющих выделять права на select в разрезе отдельных полей таблиц?

В обоих случаях, насколько я понимаю, пользователи с разными правами используют разные запросы к БД, т.к.
в первом случае нужно спрятать username, а во втором одинаковый запрос просто сломается, если в нем будет username и он будет выполнен от имени пользователя у которого нет прав на просмотр этого поля.

Разные запросы - разные модули UI - разные билды - особой разницы нет, на чем комбинаторный взрыв устраивать. Или же генерировать SQL запросы, анализируя права доступа к полям?

[identity profile] metaclass.livejournal.com 2013-02-20 08:25 pm (UTC)(link)
Далее, насчет недоступных данных при компиляции:
1) Типы (схема БД) - тоже данные
2) Компиляцию не обязательно делать у разработчика, она может производится и при старте системы у заказчика. В нормальных языках, я имею в виду.

Формально валидация данных в БД при старте ничем не отличается от проверки валидности типов при компиляции.

[identity profile] plumqqz.livejournal.com 2013-02-20 08:51 pm (UTC)(link)
Если поля не оказалось - программа должна при запуске уведомить о том, что версия устарела - это значит, что в наше отсутствие схема БД изменилась и никаких гарантий работоспособности мы дать не можем.
"Вывести список заказов невозможно, так как имя пользователя неизвестно"? Ну пусть...

Но требуется уточнение, что вы имеете в виду под этим: административное решение "маша не должна видеть поле username" или техническую реализацию в некоторых СУБД, позволяющих выделять права на select в разрезе отдельных полей таблиц?
Имеется в виду административное решение путем технической реализации. Ну как я в бытность работы "в одном крупном телекоме" мог видеть номера телефонов, а вот колонка с ФИО владельца мне просто не показывалась - не было такой.

В обоих случаях, насколько я понимаю, пользователи с разными правами используют разные запросы к БД
Нет. В одном случае в select * from msisdn есть колонка с фио, в другом - ее нет.

[identity profile] plumqqz.livejournal.com 2013-02-20 08:54 pm (UTC)(link)
1) Типы (схема БД) - тоже данные
Да, но верить в них можно только в течение одной транзакции.

2) Компиляцию не обязательно делать у разработчика, она может производится и при старте системы у заказчика. В нормальных языках, я имею в виду.
В еще более нормальных они автоматически делаются при каждом изменении схемы :-)

[identity profile] metaclass.livejournal.com 2013-02-20 09:12 pm (UTC)(link)
Ага, значит выводом типов за нас занимается СУБД все таки.
Я правда, слышал, что select * from делать не принято, но в этом случае по другому не получится.

Подобная реализация подразумевает специфическую организацию системы. Как минимум, двухзвенку вместо трехзвенки, или вудуизм с протаскиванием аутентификационного токена пользователя от клиента, через сервер приложений до СУБД.
И, насколько я понимаю, этот функционал с запиливанием доступа к полям работает по разному (если вообще присутствует) в разных СУБД?

[identity profile] vit-r.livejournal.com 2013-02-20 09:45 pm (UTC)(link)

Но с динамически типизированными языками, а особенно с Clojure с ее destructring и прагматичным подходам к совместимости типов имеется постоянно проблема вида "сунул лишние скобки, на место числа попал массив и через 100500 вызовов когда дело дошло до суммирования, оператор + сдох от попытки просуммировать ссылку на массив со стек-трейсом на три страницы". Причем если мне такие ошибки чинить достаточно легко, то менее опытным коллегам это вырывает мозг.

Это говорит только о том, что не поставленны внутренние проверки, которые должны отловить абнормальную ситуацию сразу. Причём, расставить такие проверки в нужных местах могут даже "менее опытные коллеги"

[identity profile] metaclass.livejournal.com 2013-02-20 09:57 pm (UTC)(link)
Да, надо приучить себя инварианты проверять сразу.

[identity profile] voidex.livejournal.com 2013-02-21 06:44 am (UTC)(link)
Заменить на нормальных, конечно, лучше, но дольше и сложнее.

[identity profile] thedeemon.livejournal.com 2013-02-21 06:49 am (UTC)(link)
проверка типов вручную. проверка синтаксиса вручную. офигительный прогресс, слава лиспам!

[identity profile] veremeenko-alex.livejournal.com 2013-02-21 06:54 am (UTC)(link)
и рыбку съесть и ...

[identity profile] thedeemon.livejournal.com 2013-02-21 07:40 am (UTC)(link)
Запостил у себя пару примеров, когда такое доступно в статически типизированных языках, даже не прибегая к макросам.

[identity profile] isorecursive.livejournal.com 2013-02-21 09:20 am (UTC)(link)
Можно, конечно. Просто надо макросом вычитывать схему и генерировать функции-вытаскиватели данных как
def getOstatki(...) = {
  ...bd access generated code...
  new {
  ...schema-based generated code...
  }
}


В местах использования структурно квантифицироваться по нужным полям.
Если речь не о аргументах метода, а о непосредственном использовании, то можно и без квантификации: getOstatki(...).someField

[identity profile] jakobz.livejournal.com 2013-02-21 10:46 am (UTC)(link)
А ты какую либу пробовал?

[identity profile] vshabanov.livejournal.com 2013-02-21 12:29 pm (UTC)(link)
Никакую не пробовал.

Мне они как-то сразу не сильно понравились, когда еще давно читал бумажную статью по ним. А недавно читал про них в http://happstack.com/docs/crashcourse/AcidState.html#ixset_lens
(там data-lens вроде) и ужаснулся.

Page 2 of 3