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] qehgt.livejournal.com 2013-02-20 02:40 pm (UTC)(link)
>В F# и прочих типизированных языках же мне придется сначала ползти в описание записи
Ну, не всё так плохо. В последнем F# есть Type Providers - они решают именно эту задачу: например, из SQL базы построить строго типизированные F#-классы.

[identity profile] theiced.livejournal.com 2013-02-20 02:57 pm (UTC)(link)
я вижу только одну проблему: "то менее опытным коллегам это вырывает мозг" - заменить этих коллег на нормальных.
(deleted comment)

[identity profile] theiced.livejournal.com 2013-02-20 04:16 pm (UTC)(link)
ололо

[identity profile] v-l-a-d.livejournal.com 2013-02-20 05:45 pm (UTC)(link)
ребе, вы ж сами в подзамке у ждевелопа писали, что нормальных коллег хрен найдешь

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

[identity profile] tzirechnoy.livejournal.com 2013-02-20 03:04 pm (UTC)(link)
Написать на clojure статический анализатор кода. Вроде lintа. Но с хинтами по поводу возвращаемых запросами типов.

Или сделать полноразмерные unit- и functionality- тэсты и прочее CI.

Можно и то и другое.

[identity profile] metaclass.livejournal.com 2013-02-20 03:21 pm (UTC)(link)
Второе есть. Склоняюсь прикрутить первое, для ускорения поиска ошибок заранее.

(no subject)

[identity profile] sum-erman.livejournal.com - 2013-02-20 15:53 (UTC) - Expand

[identity profile] gds.livejournal.com 2013-02-20 03:10 pm (UTC)(link)
> Языки же кроме F#, Scala и Clojure можно не рассматривать вообще

можно рассматривать окамл, где данная проблема решается "расширяемыми записями", всё типизировано.

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

в окамле это -- синтаксическое расширение, генерирующее тип по схеме и запросу. Для некоторых вещей есть http://pgocaml.forge.ocamlcore.org/ и http://www.strauss-acoustics.ch/macaque-pgocaml.html .

[identity profile] theiced.livejournal.com 2013-02-20 03:23 pm (UTC)(link)
окамл негуманен

(no subject)

[identity profile] gds.livejournal.com - 2013-02-20 15:29 (UTC) - Expand

(no subject)

[identity profile] theiced.livejournal.com - 2013-02-20 15:56 (UTC) - Expand

(no subject)

[identity profile] gds.livejournal.com - 2013-02-20 15:57 (UTC) - Expand

(no subject)

[identity profile] jdevelop.livejournal.com - 2013-02-20 16:52 (UTC) - Expand

(no subject)

[identity profile] gds.livejournal.com - 2013-02-20 16:56 (UTC) - Expand

(no subject)

[identity profile] jdevelop.livejournal.com - 2013-02-20 17:02 (UTC) - Expand

(no subject)

[identity profile] avnik.livejournal.com - 2013-02-20 17:33 (UTC) - Expand

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

[identity profile] metaclass.livejournal.com 2013-02-20 04:17 pm (UTC)(link)
Оно самое.

[identity profile] vshabanov.livejournal.com 2013-02-20 04:06 pm (UTC)(link)
Лучше всего это сделано в Ur/Web. Там пишешь SQL запрос, или рисуешь какую-нибудь HTML формочку и он сам определяет, какие будут поля в результате. Там правда есть проблема с километровыми сообщениями об ошибках, когда он выводит всю запись (но это по идее решаемо, если выводить только разницу).

Еще есть ocaml. Там можно создавать объекты по месту (без описания класса) и он сам определит, какой у них тип.

В том же Хаскеле можно работать с записями не так:
r = MyRecord { mrField1 = 1, mrField2 = "asdf" }
case r of
    MyRecord { mrField1 = f1, mrField2 = f2 } ->
    -- или MyRecord f1 f2

а так
instance Default MyRecord where 
    def = MyRecord { mrField1 = 0, mrField2 = "" }
r = def { mrField1 = 1, mrField2 = "asdf" }
case r of
    MyRecord {..} -> mrField1 + 2
-- или просто mrField1 r + 2

Тогда не надо будет править конструкторы.

Ну и в принципе, можно создавать такие же словари в типизированных языках.

[identity profile] jdevelop.livejournal.com 2013-02-20 04:53 pm (UTC)(link)
беда записей в цацкеле - это неудобство их использования

(no subject)

[identity profile] vshabanov.livejournal.com - 2013-02-20 17:25 (UTC) - Expand

(no subject)

[identity profile] jdevelop.livejournal.com - 2013-02-20 17:33 (UTC) - Expand

(no subject)

[identity profile] vshabanov.livejournal.com - 2013-02-20 17:57 (UTC) - Expand

(no subject)

[identity profile] metaclass.livejournal.com - 2013-02-20 17:39 (UTC) - Expand

(no subject)

[identity profile] vshabanov.livejournal.com - 2013-02-20 17:56 (UTC) - Expand

(no subject)

[identity profile] jakobz.livejournal.com - 2013-02-21 10:46 (UTC) - Expand

(no subject)

[identity profile] vshabanov.livejournal.com - 2013-02-21 12:29 (UTC) - Expand

(no subject)

[identity profile] jakobz.livejournal.com - 2013-02-21 13:24 (UTC) - Expand

(no subject)

[identity profile] vshabanov.livejournal.com - 2013-02-21 14:28 (UTC) - Expand

[identity profile] jakobz.livejournal.com 2013-02-20 04:09 pm (UTC)(link)
Блин, хотел камент отредактировать, а стер его вместо этого. Короче мне вот хочется чего-то типа TypeScript, но чтобы работало в гораздо большем количестве случаев.

function makeObject() {
	return {
		a : 1,
		b : 2
	}
}

function alterObject(obj) {
	obj.c = 3
	return obj
}

var o = makeObject()
console.log(o.c) //The property 'c' does not exist on value of type '{ a: number; b: number; }'
var o2 = alterObject(o)
console.log(o2.c) // Тут всё ок
console.log(o.c)  // The property 'c' does not exist on value of type '{ a: number; b: number; }' -- а такое не осиливает


http://www.typescriptlang.org/Playground/

[identity profile] avnik.livejournal.com 2013-02-20 05:37 pm (UTC)(link)
В питоне (точнее Zope) можно приводить один тип к другому (точнее к интерфейсу в рамках zope.interface),или кинь исключение/верни дефолт если не получилось (+можно приводить много значений к одному) -- но это не свойство языка, это библиотечный костыль

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

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

(no subject)

[identity profile] plumqqz.livejournal.com - 2013-02-20 19:11 (UTC) - Expand

(no subject)

[identity profile] metaclass.livejournal.com - 2013-02-20 19:21 (UTC) - Expand

(no subject)

[identity profile] plumqqz.livejournal.com - 2013-02-20 19:33 (UTC) - Expand

(no subject)

[identity profile] theiced.livejournal.com - 2013-02-20 19:50 (UTC) - Expand

(no subject)

[identity profile] metaclass.livejournal.com - 2013-02-20 20:22 (UTC) - Expand

(no subject)

[identity profile] plumqqz.livejournal.com - 2013-02-20 20:51 (UTC) - Expand

(no subject)

[identity profile] metaclass.livejournal.com - 2013-02-20 21:12 (UTC) - Expand

(no subject)

[identity profile] metaclass.livejournal.com - 2013-02-20 20:25 (UTC) - Expand

(no subject)

[identity profile] plumqqz.livejournal.com - 2013-02-20 20:54 (UTC) - Expand

(no subject)

[identity profile] permea-kra.livejournal.com - 2013-02-20 20:22 (UTC) - Expand

(no subject)

[identity profile] nivanych.livejournal.com - 2013-02-22 08:17 (UTC) - Expand

(no subject)

[identity profile] jdevelop.livejournal.com - 2013-02-20 19:33 (UTC) - Expand

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

[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)
Да, надо приучить себя инварианты проверять сразу.

(no subject)

[identity profile] thedeemon.livejournal.com - 2013-02-21 06:49 (UTC) - Expand

(no subject)

[identity profile] si14.livejournal.com - 2013-02-23 11:24 (UTC) - Expand

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

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