Записи, макросы, скала, вывод типов, SQL
В очередной раз, в связи с макросами подниму свой любимый вопрос:
Можно ли поиметь в языках типа скалы строгую типизацию, не объявляя типы записей заранее?
Идея в следующем: у меня в Clojure большая часть DSL основана на том, что я могу в любое место описания строк-колонок-ячеек отчета (условно говоря, Excel-like таблица фиксированной формы, заполняемая автоматом из проводок) в любой момент добавить атрибут типа :skip-in-vat true и обработать его. При этом мне нужно это изменить ровно в двух местах - описание документа ("данные") и расчетный алгоритм ("код"). Типов тут нет, а вместо них - структуры данных, скомбинированные из словарей, массивов и атомарных данных.
Но с динамически типизированными языками, а особенно с Clojure с ее destructring и прагматичным подходам к совместимости типов имеется постоянно проблема вида "сунул лишние скобки, на место числа попал массив и через 100500 вызовов когда дело дошло до суммирования, оператор + сдох от попытки просуммировать ссылку на массив со стек-трейсом на три страницы". Причем если мне такие ошибки чинить достаточно легко, то менее опытным коллегам это вырывает мозг.
В F# и прочих типизированных языках же мне придется сначала ползти в описание записи (если там записи вообще есть), добавлять поле или хуже того - еще один вариант для алгебраического типа, править вызовы конструкторов (если оверлоадинга нет) и прочая и прочая.
Кроме того, часто хочется такого: "тип записи создается в SQL запросе и далее используется в функции, обрабатывающей результат запроса". В clojure это опять же - словарь, с кейвордами в качестве ключей.
Языки же кроме F#, Scala и Clojure можно не рассматривать вообще - на них аналогичные задачи или сводятся к "наняли 100 ртишников по одному на объект предметной области и написали вручную" или к "написали самодельный лисп с хаскелем на дельфи/C#" или к "используем безумные ORM с еще более дикими, чем SQL языками и внешними декларативными описаниями".
Т.е. я не говорю, что они не пригодны - но затраты ресусов на решение задачи на таких языках заметно больше.
Можно ли поиметь в языках типа скалы строгую типизацию, не объявляя типы записей заранее?
Идея в следующем: у меня в Clojure большая часть DSL основана на том, что я могу в любое место описания строк-колонок-ячеек отчета (условно говоря, Excel-like таблица фиксированной формы, заполняемая автоматом из проводок) в любой момент добавить атрибут типа :skip-in-vat true и обработать его. При этом мне нужно это изменить ровно в двух местах - описание документа ("данные") и расчетный алгоритм ("код"). Типов тут нет, а вместо них - структуры данных, скомбинированные из словарей, массивов и атомарных данных.
Но с динамически типизированными языками, а особенно с Clojure с ее destructring и прагматичным подходам к совместимости типов имеется постоянно проблема вида "сунул лишние скобки, на место числа попал массив и через 100500 вызовов когда дело дошло до суммирования, оператор + сдох от попытки просуммировать ссылку на массив со стек-трейсом на три страницы". Причем если мне такие ошибки чинить достаточно легко, то менее опытным коллегам это вырывает мозг.
В F# и прочих типизированных языках же мне придется сначала ползти в описание записи (если там записи вообще есть), добавлять поле или хуже того - еще один вариант для алгебраического типа, править вызовы конструкторов (если оверлоадинга нет) и прочая и прочая.
Кроме того, часто хочется такого: "тип записи создается в SQL запросе и далее используется в функции, обрабатывающей результат запроса". В clojure это опять же - словарь, с кейвордами в качестве ключей.
Языки же кроме F#, Scala и Clojure можно не рассматривать вообще - на них аналогичные задачи или сводятся к "наняли 100 ртишников по одному на объект предметной области и написали вручную" или к "написали самодельный лисп с хаскелем на дельфи/C#" или к "используем безумные ORM с еще более дикими, чем SQL языками и внешними декларативными описаниями".
Т.е. я не говорю, что они не пригодны - но затраты ресусов на решение задачи на таких языках заметно больше.
no subject
Ну, не всё так плохо. В последнем F# есть Type Providers - они решают именно эту задачу: например, из SQL базы построить строго типизированные F#-классы.
no subject
no subject
no subject
no subject
no subject
Или сделать полноразмерные unit- и functionality- тэсты и прочее CI.
Можно и то и другое.
no subject
(no subject)
no subject
можно рассматривать окамл, где данная проблема решается "расширяемыми записями", всё типизировано.
> Кроме того, часто хочется такого: "тип записи создается в SQL запросе и далее используется в функции, обрабатывающей результат запроса".
в окамле это -- синтаксическое расширение, генерирующее тип по схеме и запросу. Для некоторых вещей есть http://pgocaml.forge.ocamlcore.org/ и http://www.strauss-acoustics.ch/macaque-pgocaml.html .
no subject
(no subject)
(no subject)
(no subject)
(no subject)
(no subject)
(no subject)
(no subject)
no subject
no subject
no subject
Еще есть ocaml. Там можно создавать объекты по месту (без описания класса) и он сам определит, какой у них тип.
В том же Хаскеле можно работать с записями не так:
а так
Тогда не надо будет править конструкторы.
Ну и в принципе, можно создавать такие же словари в типизированных языках.
no subject
(no subject)
(no subject)
(no subject)
(no subject)
(no subject)
(no subject)
(no subject)
(no subject)
(no subject)
no subject
http://www.typescriptlang.org/Playground/
no subject
no subject
Впрочем, многим это доставляет удовольствие, так что не хочу больше мешать.
no subject
И чтобы оно все из базы данных приходило без издевательств над здравым смыслом вида "а теперь мы схему базы данных пишем не на SQL а на руби".
Пока единственный вариант - либо генерить код, либо динамическая типизация.
(no subject)
(no subject)
(no subject)
(no subject)
(no subject)
(no subject)
(no subject)
(no subject)
(no subject)
(no subject)
(no subject)
(no subject)
no subject
no subject
Но с динамически типизированными языками, а особенно с Clojure с ее destructring и прагматичным подходам к совместимости типов имеется постоянно проблема вида "сунул лишние скобки, на место числа попал массив и через 100500 вызовов когда дело дошло до суммирования, оператор + сдох от попытки просуммировать ссылку на массив со стек-трейсом на три страницы". Причем если мне такие ошибки чинить достаточно легко, то менее опытным коллегам это вырывает мозг.
Это говорит только о том, что не поставленны внутренние проверки, которые должны отловить абнормальную ситуацию сразу. Причём, расставить такие проверки в нужных местах могут даже "менее опытные коллеги"
no subject
(no subject)
(no subject)
no subject
no subject
no subject
В местах использования структурно квантифицироваться по нужным полям.
Если речь не о аргументах метода, а о непосредственном использовании, то можно и без квантификации: getOstatki(...).someField
no subject
Мне кажется, секрет в фиксации инвариантов — если сама функция использует какое-то поле пришедших данных, то она должна контролировать его наличие, но только и исключительно его (а не «типа рекорда», например). Желательно — в том числе статически (но я не знаю примеров этого кроме костыля в typed clojure), желательно в рантайме тоже с минимальным количеством усилий. Насколько я понимаю, ничего готового нет; но можно попробовать, как я уже упомянул выше, пользоваться стилем из Erlang'а: выматчивать в голове функции все куски данных, которые нужны для неё или её потомков.