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] jakobz.livejournal.com 2013-02-21 01:24 pm (UTC)(link)
Коментарий твой в спам попал из-за ссылки.

Я как-то пробовал как-то поюзать data-lens. Мне жутко не понравилось как оно приделано: необходимость прятать точку из prelude, подчеркивания в именах штатных геттеров, чтобы линзы с ними различать, всякое такое.

Но сама концепция - очень даже, эдакие first-class property, которые кроме собственно get/set можно хранить, передавать и слеплять. Всякое такое получается:
changePhone newPhone = (phone.contactInfo.person) ^= newPhone

К слову, автор на нее забил и сделал модуль lens (Control-Lens). Залечил там проблему с точкой. Там какой-то совершенно адский для меня матан с вовлечением всех этих Foldable, Applicative и т.п. Но через это получаются дичайшие вещи, типа такого:
>over (mapped._2) length [("hello","world"),("leaders","!!!")]
[("hello",5),("leaders",3)]

Буду пробовать, с записями местами жить просто никак нельзя.

[identity profile] vshabanov.livejournal.com 2013-02-21 02:28 pm (UTC)(link)
В целом, линзы ничего, если надо много с записями работать (у меня просто редко встречаются вложенные записи, возможно из-за неудобства работы с ними стараюсь обходиться без них).

Но очень уж у них много вариантов операторов.

[identity profile] nivanych.livejournal.com 2013-02-22 08:17 am (UTC)(link)
> научить макрос лазить в базу при компиляции
Точнее, лазить в некоторую внешнюю (по отношению к хацкелёвой программе) схему, которая может и обновляться при компиляции.

[identity profile] si14.livejournal.com 2013-02-23 11:24 am (UTC)(link)
В Erlang это достигается тем, что везде и всюду используется pattern matching (что фиксирует инварианты). Теоретически с core.match можно писать в таком же стиле.

[identity profile] si14.livejournal.com 2013-02-23 08:43 pm (UTC)(link)
Отличный пост. В сочетании с комментариями особенно. Заставляет меня думать, что вы и ваши комментаторы подразумеваете разного рода гарантии и разного рода рекорды: если вам хочется иметь возможность *в рантайме* цеплять какие-то новые поля к рекордам (теоретически даже с вычислением ключей), то они говорят исключительно о разнообразных статических извращениях и хитром выводе типов. В то же время последнее является решением не той проблемы: вопрос не в статических гарантиях, а в проверке инвариантов как можно раньше (безусловно, компиляция это один из наиболее ранних моментов, но *исключительно* при компиляции всё это может работать только в полностью статических языках, что неприятно). Отсюда непонимание и страдание.

Мне кажется, секрет в фиксации инвариантов — если сама функция использует какое-то поле пришедших данных, то она должна контролировать его наличие, но только и исключительно его (а не «типа рекорда», например). Желательно — в том числе статически (но я не знаю примеров этого кроме костыля в typed clojure), желательно в рантайме тоже с минимальным количеством усилий. Насколько я понимаю, ничего готового нет; но можно попробовать, как я уже упомянул выше, пользоваться стилем из Erlang'а: выматчивать в голове функции все куски данных, которые нужны для неё или её потомков.

Page 3 of 3