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

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

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

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

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

Языки же кроме F#, Scala и Clojure можно не рассматривать вообще - на них аналогичные задачи или сводятся к "наняли 100 ртишников по одному на объект предметной области и написали вручную" или к "написали самодельный лисп с хаскелем на дельфи/C#" или к "используем безумные ORM с еще более дикими, чем SQL языками и внешними декларативными описаниями".
Т.е. я не говорю, что они не пригодны - но затраты ресусов на решение задачи на таких языках заметно больше.
metaclass: (Default)
Вспомнил один срач на тему однопроходных компиляторов, где Steve Yegge критиковал Clojure за то, что в ней объявления видны не во всем модуле, а только ниже объявления. (Ну, за исключением declare которые что-то вроде forward-объявлений).
Сижу приделываю новую фичу к кодогенератору на F# - и таки внезапно оказывается, что это не только в Clojure, но так же и F#, и, что самое печальное, - в долбаном SQL, который я генерирую.
Сижу вот, сортирую объекты из которых генерируется SQL по зависимостям.

А как с этим дела обстоят в Scala?
Я тут подумываю, что надо бы провести сравнение F# и Scala на моих задачах, все равно уже полная работа JVM и жаб, так может, выводилка типов в Scala для меня окажется более приемлемой, чем дикий ад в F# (теперь я понимаю, почему его [livejournal.com profile] thesz критикует). Ну и макросы в скале [livejournal.com profile] xeno_by прикрутил вроде уже.
Хотя единственное, что мне приходит в голову на тему приличного использования макросов - это при их выполнении долбится в БД или модель этой БД и генерировать код.
metaclass: (Default)
Оказывается, в F# есть что-то вроде структурных типов/констрейнты на наличие членов класса:
https://gist.github.com/3523947
http://codebetter.com/matthewpodwysocki/2009/09/28/generically-constraining-f-part-ii/
http://codebetter.com/matthewpodwysocki/2009/06/11/f-duck-typing-and-structural-typing/

Но: синтаксис объявления и поведение вывода типов в этом случае выглядит крайне противоестественно, особенно в том плане, что касается автогенерируемых компилятором методов.

PS: Мерзость. Если inline функцию объявить в другом модуле и сослаться на приватные члены этого модуля - все это перестает тайп-чекится вообще, а без inline такое сделать невозможно - тайп-чек не проходит вообще.
"The value 'dumpNamed' was marked inline but its implementation makes use of an internal or private function which is not sufficiently accessible"
или же, без inline:
"This code is not sufficiently generic. The type variable ^T when ^T : (member get_Name : ^T -> string) could not be generalized because it would escape its scope."
metaclass: (Default)
Элементарно.
Экспортируем из опердени датасет с 381 записью по 40 полей в каждой в формат исходника F# и пытаемся это компилировать. Каких-то несчастных 10 тыс строк и 660 килобайт кода. Компилятор сдыхает с переполнением стека. Так же как и хаскель когда-то в такой же ситуации.
Придется все-таки для таких целей написать адекватную сериализацию, чтобы данные лежали не в исходнике, а отдельно, а исходник содержал только метаданные и код чтения.

Вообще, изначальная идея такова - часто есть необходимость произвести некое нетривиальное вуду над отчетом для проверки, и мне кажется, что экспорт отчета в виде исходника, а затем загрузка этого исходника в интерактивную консоль хаскеля или F# - это самый простой способ надругаться над данными отчета функциональщиной.
Например, сегодня была надобность из метаданных SQL запроса генерировать более другой запрос, чтобы эти самые пресловутые 40 полей не повторять 10 раз вручную.
metaclass: (Default)
И таки несамочевидное решение "сделать одноразовую опердень по-человечески, с кодогенератором, F# и тестершами" показало свою полезность и адекватность в первый же день использования оной опердени.
Пользователи по результатам уточнили требования, а внутреннее тестирование обнаружило небольшой баг, что привело к необходимости добавить одно поле в таблицу. Если бы не кодогенерация - я бы сейчас искал и менял вручную с десяток мест, где нужно добавить это поле - объект данных, его дампы, dao-классы, sql-запросы, гриды для отображения, подписи к этим гридам и прочая, и прочая.
А так доработка даже с учетом того, что кодогенератор пока не умеет миграцию БД ни в каком виде, заняла 40 минут.
metaclass: (Default)
Домудрился с вложенными ленивыми секвенсами вперемежку с обращениями к БД до такой степени, что сломал компилятор F#, похоже. Во всяком случае, он у меня теперь умудряется сделать Dispose команде доступа к БД до того как начнет генерить последовательность из нее. Причем, что удивительно - не всегда, а только при определенном паттерне обращения к другим командам.

Read more... )

Однозначно, что ленивость+внешние ресурсы это не совсем то, что следует делать, да и отладке это не подлежит в принципе, ибо монады. Но как превратить это дело в fold-like алгоритм, хрен его знает, да еще с учетом того, что там внутри не просто итератор по плоским записям, а внутри этих записей еще вложенные итераторы, а из них ссылки на элементы из других итераторов, в общем, ад тот еще.
Вот еще смех будет, если это не в F# проблема, а в ADO.NET провайдере Firebird.

PS: Судя по всему, все хорошо и пень все таки я - таки ленивые вложенные итераторы можно использовать только вложенно, а не так как я - сначала пройтись по внешнему, найти нужную запись, а потом из нее читать вложенные. Адекватный вариант - сделать их неленивыми. Обычно в этом помогает Seq.cache, но тут он, похоже, тоже вызывается в неподходящий момент.

PPS: Ухуху, и решение в этом случае действительно, как и советуют гуру ленивости - перевернуть все в fold-like алгоритм, передав две функции - одну для fold внешнего итератора и одну для fold внутреннего (и вроде в общем случае, еще две - одну которая будет из внешнего итератора и состояния делать начальное состояние для fold внутреннего, и вторая будет комбинировать результат от fold внутреннего итератора и состояние внешнего).
Это получается, что для fold в случае вложенных итераторов количество передаваемых параметров-функций прилично возрастает: по одной функции на каждый итератор в иерархии и +2 на каждый случай вложенного итератора, причем у самих этих функций количество передаваемых в них параметров тоже особой радости не доставляет.
Зато соответствующим набором таких функций, судя по всему, можно вычислить произвольную функцию от базы данных, представленной в виде набора разнообразно вложенных итераторов :)
metaclass: (Default)
Читаю про катаморфизмы и FSharp. Вообще, изначально это я искал ответ на этот вопрос, но зачитался и забыл.

Там приводится пример, как выразить foldBack (оно же foldr) через fold(оно же foldl) с помощью continuations. И выглядит это как будто мы заменяем использование стека в случае не-tail-recursive функции таким же использованием памяти из head. Причем в случае стека выделение и удаление памяти достаточно дешевое, а вот что будет в случае обычной памяти - вопрос загадочный. Непонятно, есть ли тогда какие-нибудь преимущества у такого преобразования не-tail-recursive функции в tail-recursive?
metaclass: (Default)
Ради теста обновил F# с 1.9.7.8 до 2.0.0.0. Сломалось:
1) fslex, fsyacc ставятся отдельно с FSharp PowerPack, в основную инсталляцию не входят
2) Warning насчет имени модуля в начале файла стал полноценной ошибкой - пришлось поправить исходники парсера и опцию --module для fsyacc чтобы это имя модуля появилось в сгенерированных файлах.
3) Самое неприятное для меня изменение - теперь optional parameters могут быть только в конце сигнатуры метода, а у меня было штук 5 конструкторов с опциональными параметрами в начале - это AST для генерируемого кода и там по логике идет что-то вроде такого:
([опциональные атрибуты] [опциональный модификатор видимости] тип имя [опциональный код инициализации])
В общем, это обходится досточно просто - делаем вместо одного конструктора несколько - один со всеми параметрами, и остальные - с наиболее часто встречающимися вариациями вызовов.

ChangeLog для F# 2.0 я найти не в состоянии, может меня конечно гугл лично ненавидит в этом плане, не знаю.
И вообще, F#, конечно, язык весьма удобный, но ставить его в продакшен за пределы личного использования я как-то опасаюсь - очень уж разрозненные сведения по нему в интернетах и выглядит это все, как будто он живет усилиями Дона Сайма, проталкивающего его всюду.

Т.е. что я хочу сказать - пихать в продакшен что-то не мейнстримовое можно, только имея или личный контакт с девелоперами, или надмозгов на работе, которые в случае чего смогут разгрести внезапно возникший ад угара.
metaclass: (Default)
200 килобайт кода (5000 строк) на F# из модели, содержащей 81(типы)+44(таблицы)+276(поля) записей в БД генерит 1.1 мегабайта sql+xml+C# (22 тысяч строк).

Что меня в моем стиле писанины на F# немного напрягает, так это постоянно получающиеся в исходниках строки по 100-150 символов, т.к. на широкоэкранном монике они не мешают, а получающиеся методы сильно читабельнее, т.к. в основном весь смысл строки где-нибудь в начале ("какой ADT создаем") а дальше идут только параметры.

И вот с этими параметрами такая длина и вылазит - осмысленный набор конструкторов данных для половины ADT содержит что-то вроде None, из-за чего эти None путаются между собой и стандартным типом Option (он же - Maybe, т.е. Option.None == Nothing) и для них приходится явно указывать префиксы имени типа (ReadOnlyFieldModifier.None, CSharpVisibility.None, CSharpModifier.None). Сокращать имена типов не хочу, они осмысленные, Можно было бы добавить коротенькие префиксы прямо в имена конструкторов данных, тогда можно было бы обойтись без явного указания имени типа.

Еще было бы неплохо поиметь "параметры по умолчанию", типа вместо параметра пишешь _, а компилятор подставляет то, что мы указали дефолтным где-нибудь в описании функции или конструктора. При небольшом количестве параметров это лечится функциями - умными конструкторами, а при большом задолбаться перебирать комбинации.
metaclass: (Default)
Спор про F# (там рекаптча если шо)

Товарищ прицепился к тому, что в F# нельзя написать функцию, которая бы по индексу поля в кортеже возвращала бы значение поля.

Т.е. вообще-то ее написать можно, через Reflection, но 1) это все только благодаря дотнету, но никак не системе типов; 2) это противоречит духу статической типизации и является способом стрелять себе в ногу.

Никак не могу человеку объяснить разницу между статической типизацией и натягиванием совы на глобус реализацией динамической типизации поверх языка со статической, чем занимаются 90% либ доступа к БД (все эти ADO.NET, ODBC, JDBC, BDE и прочая).

Вуду^5

Mar. 31st, 2010 05:40 pm
metaclass: (Default)
Генерил кодогенератором опердень, понадобилось добавить способ описания предикатов проверки прав доступа пользователей (типа "этот пользователь видит только данные своего филиала, или же только относящиеся к налоговой инспекции в городе где этот филиал"). Ну отдельные проверки понятно - запрос типа "exists(select 1 from UserDepartments where UserID=current_user and DeptID=OperdenTable_DeptID)" но мне понадобилось их объединять в итоговый предикат отдельно описанными логическими функциями.
Не нашел ничего лучше, как вкрутить первый попавшийся в гугле под руки парсер s-выражений на F#, который был там приведен как пример использования fsyacc и fslex, затем буквально за 5 минут написал конвертор этих выражений в условия для where в sql-запросах.

Т.е., ко всей этой опердени на смеси SQL,F#,дельфи и жутких xml-метаданных еще добавилось и подмножество лиспа :)

Кстати, весь модуль опердени (2 справочника, 4 ведомости, куча проверок, внешних ключей и зависимостей от других модулей)), ради которого это все затевалось, за полдня на оном безумии сделала специально вышедшая из декрета сотрудница.

Но, тем не менее, теперь я понимаю, почему нормальных готовых решений с кодогенерацией опердени почти не существует. Там надо учесть столько мелочей (в модели и кодогенераторе), что это пригодно либо только для очень ограниченных решений, либо становится настолько сложным, что никто этим пользоваться не станет.
metaclass: (Default)
Смотрю .NET Reflector на внутренности программы на F# и всячески торчу.
50 строк на F# разворачивается в четыре автосгенеренных класса-замыкания, автоматически управляющих десятком объектов для доступа к базе данных и все это в итоге представляет собой функцию "коннект к Firebird -> хитрозаколдованная структура с данными".

А я еще собираюсь и сам код на F# сгенерить из результатов парсинга запросов в БД, чтобы вообще руками ничего не писать, кроме SQL запросов. Т.е. "чтобы было все и мне за это ничего не было".

Т.е. идея примерно такая:
1)есть структура реляционной БД - мне в ее категориях проще проектировать и думать.
2)есть запросы к ней и связи между ними - которые мне тоже проще придумать, чем делать маппинги в ORM или писать запросы на LINQToSQL, который вообще не факт что поддерживается для Firebird.

Я описываю запросы на SQL, кодогенератором автоматически конвертирую их в функции F# вида ("коннект к БД" -> "параметр запроса 1" -> "параметр запроса 2" -> ... -> "ленивая последовательность записей с результатами").
Затем полученные функции я собираю в законченную структуру, описывая связи между ними уже на F# и получаю на выходе что-то вроде "Запись, поля которой представляют собой ленивые последовательности с результами запросов, причем внутри эти результатов могут быть такие же поля - ленивые последовательности с результатами вложенных запросов".

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

Profile

metaclass: (Default)
metaclass

April 2017

S M T W T F S
      1
2345678
9101112 131415
16171819202122
23242526272829
30      

Syndicate

RSS Atom

Most Popular Tags

Style Credit

Expand Cut Tags

No cut tags
Page generated Jun. 10th, 2025 07:06 pm
Powered by Dreamwidth Studios