metaclass: (Default)
Очередной раз стукнулся об ленивость.

Делаю фоновый расчет отчетов - клиент просит у сервиса "посчитай мне отчет", тот ему возвращает идентификатор-ссылку на future со считалкой отчета и далее клиент опрашивает сервис на предмет "а не готов ли наш отчет" и тот ему отвечает либо "не готов" либо "готов, вот тебе данные".
Ну, склепал очередь отчетов - атом, в нем мап, в мапе идентификаторы запрошенных отчетов и future к ним. Запускаю - а оно мгновенно говорит, "да, вот отчет готов, забирай" и давай его считать и отдавать. В потоке запроса, не в рабочем потоке future.
Ленивость. Оно возвращает башку последовательности данных отчета сразу, "на тэбэ, дорогой, форси меня". И запрос готовых данных (в том числе, даже просто попытка в целях отладки посмотреть на мап с future) - вызывает форсинг последовательности.

PS: И еще обнаружил очередную фичу кложури - thread-local bindings протаскиваются во все вызовы, которые перекладывают работу в другие потоки. Т.е. конкретно тут - я в запросе часть общих параметров (типа имени пользователя) складываю в thread-local binding чтобы не протаскивать их руками через 100500 функций, думал, что при вычислении внутри future придется их доставать и перекладывать во второй поток - а оно уже сделано.
Авторы кложури не перестают удивлять своей крайней адекватностью и практичностью языка.
metaclass: (дзедline)
Столкнулся с тем, что наиболее внятно задача, которую я решаю, укладывается в Clojure и не укладывается (без частичного выкидывания статической типизации) в скалы-хаскели-C#-F#.
Вернее даже не выкидывание, а просто получается имитация типичного для Clojure типа данных Map[Symbol,Any] и использование его во все поля.

Можно, конечно, было бы убрать вообще всю логику, вплоть до SQL-запросов, из БД и писать запросы для ORM типа slick, но это в данном случае натягивание совы на глобус, более чем бессмысленное, т.к. статически типизированный результат запроса в ORM нужно будет превратить обратно в нихрена не типизированнный датасет типа Seq[Map[Symbol,Any]] и сериализовать его в json для клиента.
Или прикрутить shapeless (https://github.com/milessabin/shapeless) и навернуть extensible records поверх HList на предмет того, чтобы типовыводилка генерила еще и метаданные, но в итоге получится что-то совсем печальное и малопонятное.
metaclass: (дзедline)
Скачал вчера EAP 12.1 Intellij IDEA.
Еле нашел, потому как гугл выдает ссылку на 12, которое уже релиз, но в нем по некоей причине не ставится Scala плагин.

Жаба-хелло-ворлд в идее делается и запускается, как положено, за 1 минуту.

Ради эксперимента импортировал проект на Clojure. Количество чернейшего вуду, которое при этом откопалось, не поддается осмыслению.
Во-первых, пришлось в конце-концов разобраться как правильно обращаться к файлам .properties, которые лежат в classpath.
Во-вторых, отсутствие явного разделения фаз "сборка" и "запуск" играет злые шутки с auto-make в IDEA - у меня импорт некоторых файлов (который выполняется класс-лоадером на этапе компиляции) вызывает обращение к БД, а обращение к БД при отсутствующем файле конфига кидает совершенно дикие ошибки вида "Clojure Compiler: org.apache.commons.dbcp.SQLNestedException: Cannot create PoolableConnectionFactory (GDS Exception. 335544382. Invalid clumplet buffer structure: buffer end before end of clumplet - clumplet too long".
Это так Firebird JDBC драйвер кидает ошибку, когда не указано имя пользователя, пароль или роль. Без многолетнего опыта общения с этим говнищем - никогда в жизни по сообщению не догадаешься, что сделано не так.

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

Зато в идее логичный auto-completion и есть (наконец-то!!) поиск символа по всему проекту по горячей клавише. Причем именно так, как это должно быть - "вызвал поиск, нашел, нажал enter, оказался где надо".
И есть в хелпе первым пунктом поиск действий по описанию.
metaclass: (дзедline)
Сидел до утра пилил модуль в опердени на кложуре.
Наткнулся в процессе на классическую шизу - "ошибка в обработке ошибок", некоторые ошибки в исходниках (при macroexpand) в принципе не показывают информацию о месте, где произошла ошибка.
Мне-то хорошо, я hg diff глянул и сразу вижу где чернь, потому что я коммиты делаю после каждой заработавшей функции, но такие ошибки напрягают.
Полез искать в исходники, вроде починил, сообщение об ошибке чуть более аккуратно оформил, информации добавил, оформил баг-репорт с патчем.
Даже если в апстрим попадет нескоро - возможность самостоятельно починить исходники в дебрях языка или либ сильно спасает. И является тестом на пригодность к использованию разного рода не особо мейнстримных языков - если можно их починить самому за пару часов - пригоден, иначе - нет.
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)
Макрос with-db-comment выполняет указанную функцию с параметрами в контексте коннект+транзакция, с логом комментария к транзакции в БД.
dump-seq-debug скармливает последовательность в log4j для отладки.

вот такое падает с NPE:
(with-db-comment "(get-subaccounts 20)"
(dump-seq-debug (get-subaccounts "20")))

вот такое работает:
(with-db-comment "(get-subaccounts 20)"
dump-seq-debug (get-subaccounts "20"))

Можно ли увидеть разницу с первого раза и понять, откуда там NPE? :)

Вот эта содомия с лишними или пропущенными скобками, не обнаруживаемыми компилятором работу с кложурелиспами делает немного неудобной.
И практически не пригодной для осмысленного использования людьми без встроенной типовыводилки в голове.
metaclass: (дзедline)
http://tonsky.livejournal.com/271470.html
Все написано правильно. А теперь про недостатки:
1) совершенно невменяемые стек-трейсы(как и везде, где есть ленивые коллекции)
2) очень легко где-нибудь сунуть лишние [] или забыть их и вектор чисел становится внезапно вектором векторов, или наоборот. В общем, сунуть объект не того типа. При этом, в силу ленивости и вообще абстракций, это вызовет адов стектрейс совершенно не там где это началось.
metaclass: (Default)
https://gist.github.com/4037621
Если убрать заворачивание промежуточных результатов reduce в vec (строка 15), которое делает из ленивого результата map энергичный - сей сабж дохнет с StackOverflowError, в трейсе которого много-много раз повторяется следующее:

clojure.core/map/fn--4094 (core.clj:2443)
clojure.lang.LazySeq.sval (LazySeq.java:42)
clojure.lang.LazySeq.seq (LazySeq.java:60)
clojure.lang.RT.seq (RT.java:473)
clojure.core/seq (core.clj:133)
metaclass: (Default)
Вчера [livejournal.com profile] ivan_gandhi сделал замечание что я, пользуясь динамически типизированной кложурью, при этом требую, чтобы в Java проверяли входные параметры на валидность. (Если что, проверка валидности в дотнете есть на каждом шагу, а объяснения вида "экономят циклы и не делаю проверки" в контексте жабы, тяжелого железа, JIT и прочего звучат крайне странно).
Собирался на эту тему устроить срач с утра, но [livejournal.com profile] thedeemon уже начал, так что я продолжу :)

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

Например, я хочу использовать кортеж с именованными полями (потому что позиционные кортежи нихера нечитабельны и их тип вида int*string*smallint*money*bool*Chervie ни о о чем не говорят). От входа в F# при этом нужно:
1) объявить этот чертов record где-то
2) сослаться на модуль с объявлением везде где он нужен
3) создавать экземпляры рекорда кривопачвярными конструкциями, причем оставить поля значениями по умолчанию нельзя.
4) паттерн матчинг с декомпозицией вроде не работает с рекордами.

Хаскель сразу закапываем - там на каждый случай имеется 3-4 расширения и 10 пакетов в hackage различной степени недоделанности, идиоматический подход - писать в point-free style, чтобы коллеги не разобрались, а работать в продакшене можно только с теми сторонними библиотеками, которые я могу сам починить.

При этом, у меня при работе с оперденями постоянно ситуации вида: есть запись с тремя полями, полученная из БД, мне нужно произвести обработку этой записи и добавить результат обработки в виде четвертого поля, получив новый тип записи.
Я НЕ хочу объявлять каждый раз такое руками и в Clojure это делается элементарно, добавлением нового ключа в map в функции-обработчике записей.

При этом РЕАЛЬНО динамическую типизацию я не использую. Она мне почти не нужна, потому что единственная ситуация, где вменяемый человек будет на одном цикле биндить к имени число, на втором строку, на третьем - список записей - это когда по условию задачи нужна, например, EAV-модель во все поля. И то - обычно EAV делается от безысходности, потому что пользователь не может нормально работать со схемой БД, а задача требует чего-нибудь вроде "добавить к части записей атрибут "фаза луны в которую производилась приемка товара"". В норме должны быть зависимые типы и миграции и пользователи бы пользовались той же системой типов что и разработчик.
Т.е. нормальный вывод типов - это когда программа берет типы из тут же описанного SQL-запроса, а рекорды расширяемые и объявлять их не нужно.

Второй use-case, где "вроде бы динамическая типизация" - это когда я делаю документы в виде кложурных структур данных, подгоняя комбинации списков-мапов-массивов-множеств под предметную область. В кложуре же это делается в лоб, а в хаскеле в один список не положишь три разных по структуре(типу) раздела документа. Но на самом деле, то что я делаю в кложури - это просто алгебраический тип данных "для бедных", без объявления заранее и без явно выделенных-именованных конструкторов данных. Если бы была возможность делать расширяемые и объявляемые по месту типы данных (чтобы каждый раз при разработке не переключаться между объявлением типа и конструированием данных по этому типу) - то было бы то же самое что в кложури - но статически типизированное.

PS: На ту же тему: http://justy-tylor.livejournal.com/190153.html
metaclass: (Default)
Есть такой оккультный стек-трейс: https://gist.github.com/def1fa8666a8ac83130a.
Причина в том, что в деконструктор массивов кложури вида (let [sql & params] some-array] передан пустой массив и строка sql в таком случае становится null и оный null передается в jdbc драйвер.
Строка на которой валится NPE, выглядит следующим образом:
protected boolean checkForEscapes(String sql) {
sql = sql.toLowerCase();

А вопрос, вообще говоря, в следующем: почему никто не проверяет параметр на валидность хотя бы в одной точке входа в одной из трех либ (clojure.java.jdbc, apache dbcp или jaybird)?

Выглядит так, как будто нормальную проверку параметров на валидность на границах API в жабе делать не принято.

PS: [livejournal.com profile] artureg упорно убеждает меня, что "пусть валится", потому что проверка должна быть "в другом слое". Судя по всему, все разработчики думают, что обработка ошибок должна быть в другом слое :)
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)
По мотивам: http://tonsky.livejournal.com/266027.html
Часто ли нужно найти последний элемент последовательности?

Впрочем, я не очень понимаю, причем тут полиморфизм к оптимизации last для векторов - по-моему, другое имя для метода работающего O(1) для конкретной реализации (вектор) вместо O(n) для произвольных последовательностей - это вполне разумная идея.

Рассуждать об производительности алгоритма гораздо проще, зная, что вызов всегда отрабатывает за известное время, а не "в зависимости от того, что за реализацию мы туда передали".
По-моему, очевидно, что не нужно делать обобщенную реализацию, например quicksort, передавая ей абстракцию "последовательность"+"функция получения n-ного элемента", потому что единственный вариант, в котором это работает - мутабельный массив с O(1) для доступа и замены элемента. Соответственно и алгоритмы, использующие last обобщать и надеятся на оптимальную реализацию в некотором роде бессмысленно.
metaclass: (Default)
https://plus.google.com/u/0/110981030061712822816/posts/KaSKeg4vQtz

But the resemblance to a liberal language ends there. Clojure's community came pre-populated with highly conservative programmers from the pure-functional world: basically Haskell/ML types (lots of puns today!) who happen to recognize the benefits of Lisp's tree syntax. So under its expressive covers, everything about Clojure is strongly conservative, with a core overriding bias towards protecting programmers from mistakes.

Короче, еще немного, и в кложуру заползут статические типы и про остальные языки можно будет забыть :)

PS: нашел еще срач с его участием: https://groups.google.com/forum/#!msg/seajure/GLqhj_2915A/deiRPQMfMT0J[1-25]

Clojure's community seems to have been populated by Schemers and folks from
various statically-typed camps like ML and Haskell. They generally whine and bitch
whenever anyone tries to work around Clojure's pedantic insistence on functional style
-- for instance, the crapped all over the folks who've tried to port the CL loop macro.
metaclass: (Default)
https://github.com/csete/gqrx/blob/master/pulseaudio/pa_device_list.h (код не мой, просто я в нем сейчас разбираюсь)
По ссылке есть класс pa_device, единственное назначение которого - хранить три поля описания pulseaudio девайсов. Занимает это дело 23+8 - 31 строку.

Когда у меня возникает надобность на обычных языках делать такие классы, у меня не хватает терпения их писать вручную и я их генерирую или из t4-шаблонов или из описания на Clojure, например, этот класс я бы описал так, сделав макрос def-gen-class:
(def-gen-class pa_device 
  "комментарий к классу"
  index uint "комментарий к полю"
  name string "..."
  description string "...")

в итоге получится переменная pa_device, внутри которой лежит описание класса в удобном для кодогенерации виде. Можно map, можно список кортежей, без разницы, благо кложурь имеет развитый механизм разборки структур данных на составляющие.

Полученное описание можно при желании расширить генерацией, например, пользовательского интерфейса или сериализации-десериализации.
Для своей опердени я таким образом генерю классы документов, которые умеют загружать-сохранять себя в JSON. Из 90 строк кода на кложури создается 1226 строк на дельфи.
metaclass: (Default)
Использовал concat внутри шага для reduce чтобы объединить списки последовательностей. Получил StackOverflow на больших наборах данных.
Причем последовательности были пустыми, но это их не спасло.
Т.е. у меня получилась последовательность вызывов (concat (concat (concat ... где глубина стека определялась количеством данных в БД.
Заменил concat на into стало все ок.

Clojure.

Aug. 3rd, 2012 12:49 pm
metaclass: (Default)
(defn concat-inner-seqs
  "concat inner sequencies in sequence of tuples"
  [seq-of-seqs]
  (apply (partial map concat) seq-of-seqs))


(println
 (concat-inner-seqs [[[1] [2] [:a]]
                     [[3] [4] [:b]] 
                     [[5] [6] [:c]]]))

; ((1 3 5) (2 4 6) (:a :b :c))
А теперь вот это придется объяснить всем заинтересованным сотрудникам.
PS: Версия с мапами вместо туплов: Read more... )
Сделана заменой map на merge-with. Можно использовать именованные поля и разное их количество.

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 Sep. 24th, 2017 05:38 pm
Powered by Dreamwidth Studios