stdray: (Default)
Читал пост про книги по дизайну ООП приложений, и внезапно осознал, что прочитал большую часть приведенных книг.

И не могу сказать, что мне как-то это помогло. Скорее наоборот, после прочтения количество полезного кода резко сокращалось, а популяция пауков-розеточников росла. Ведь рефакторинги, иерархии интерфейсов, попытки работать с rich domain model, - все было контрпродуктивно. Поскольку задачи константой (известной) сложности внезапно приобретали новый порядок пиздеца человекочасов, где количество рефакторинга и доступных для задачи паттернов существенно превышало непосредственно бизнес-логику.

Конечно, это вопрос вопрос личного опыта и критического (его отсутствия) отношения, так как в определенный момент начитавшись ПФП, изучив какую-то литературу про F#, я решил, что можно невозбранно применять этот подход в повседневной работе. Как следствие - безумный, бездумный, ад пиздеца "фп стиль", который я потом рефакторил в куда более простые и идиоматичные конструкции, когда понял, что я таки буду менять работу. К сожалению, недорефакторил, и там _сложно_.

Я полагаю, что моя главная ошибка была в том, что всё это происходило хаотично, что вместо продумывания высокоуровнего дизайна приложения, я занимался дизайном уровня функции/модуля/компонента/библиотеки, при этом "слои" либо разово определялись на начальном этапе проектирования, либо формировались ( или не формировались) походу дела. Занятно, что "слои" в любой литературе носят уморительный характер, формализуемый, допустим, у Фаулера, мол уровень (level) - чисто интуитивная хуита, а вот слой (layer) - это можно разнести по разным машинам. Ну понятно, что первое и второе (до разноса) являются квазисущностями, которые держит в голове только породивший их на свет.

С этим бекграундом я с интересом прочитал статью, которая навязывает некоторый высокоуровневый дизайн в условиях убогого однопроходного компилятора F#. Занятно, что ограничения компилятора ведут к совершенно конкретному дизайну, к которому я приходил, занимаясь разработкой на эфсярпе. Но я никогда не смотрел на это в таком ключе, мол у вас просто и выбора нет. У вас есть единственно верный anemic подход, конпелятор, который иное не переварит и профит.

Понятно, что любому ограничению можно найти оправдвание (например системам типов), как можно и не делить приложение на слои. Мне просто понравилась трактовка убогости конпелятора, как некоторой статической валидации дизайна на высоком уровне.
stdray: (Default)
Вынесу из комментов, а то меня меня в противники ФП из-за отрицания киллер-фичности отступов записали недавно.


Субъективно, компактность достигается выбрасыванием лишних сущностей.

ООП - это про:
- делегирование ответственности и сертифицированные способы это сделать (патттерны)
- "безопастность уровня разработчка", ведь любой объект в терминах ООП - потенциальная точка расширения как добавлением узко-специфичной логики (костыль) и наследниками нарушающими инвариант, откуда идут закрытые классы (малая реиспользуемость) и избыточное количество деклараций (интерфейсы, полностью абстрактыне классы). Отсюда же пачка модификаторов и бойлерплейт.
- делегирование ответственности во вне: инъекция зависимостей через конфигурацию (xml spring ioc и ему подобные).
- сильную связанность (coupling), поскольку ООП-методы абстрагирования по спирали приводят к ней раз за разом, в виде того же жесткого каркаса на новом уровне абстракции. При этом растут затраты на отслеживаниее этих зависимостей.
- проблемы с тестированием (в силу предыдущего пункта), поскольку модульные тесты имитируют "архитектуру" системы. Формализм объектов - ДКА, и даже mock-объект должен имитировать его состояния.
- высокий порог вхождения. Несмотря на номинально низкий порог, разобраться в этом зверинце практически невозможно. Пока новички комплексуют от классов с пачкой статических методов, прошедшие этот этап налегают на тулинг и накапливают опыт.

ФП - это про:
- first class citizen функции
- безопастность для разработчика, через иммутабельность и чистоту функций
- решение конкретной задачи
- простое тестирование при отсутствие состояния
- высокий порог вхождения, несмотря на концептуальную простоту и доступность обучающих материалов, разбираться в этом зверинце никто не хочет. Пока неофиты пугаются ауры "научности", новички метаются между языками, книгами и прочим, прошедшие этот этап не имеют возможности применять знания технологии на практике.

ЗЫ: пишу про модульные тесты, потому что не понимаю, что такое юнит-тесты и не пишу их. А затраты на интеграционное тестировние (по моей практике) нивелируют разницу в подходах.

ЗЗЫ: ФП очень про анемичную модель (в терминах ООП). И, субъективно, такой подход проще (банально). Например, такая презентация.
stdray: (Default)
Я тут проводил какие-то тесты над многомерными массивами дотнета, результаты которых меня несколько удивили.

Во-первых, было непонятно, что такое многомерный массив в дотнете. Он моментально выделяется и имеет относительно большое время доступа к элементам. Хотя в сравнении с моей реализацией на коленке трехмерного массива, он просто летает. Относительно Flat3d-поделки предположили, что дело в дорогих арифметических операциях и вызовах методов, которые не инлайнятся. Убрал лишний метод и немного оптимизировал арифметику. В итоге

Было (http://pastebin.com/mwqnm2re): Test fill flat array in xyz order: 26059ms (20 repeats, avr 1302ms)
Стало (http://pastebin.com/2QVsRuyP): Test fill flat array in xyz order: 10392ms (20 repeats, avr 519ms)

Скорость выросла в 2 с лишним раза, а значит компилятор не поинлайнил и JIT ничегошеньки не оптимизировал.

Во-вторых, почему доступ к элементам многомерного массива в два раза массивы-миллисекунды-указатели-коровники )
stdray: (Default)
Я тут проводил какие-то тесты над многомерными массивами дотнета, результаты которых меня несколько удивили.

Во-первых, было непонятно, что такое многомерный массив в дотнете. Он моментально выделяется и имеет относительно большое время доступа к элементам. Хотя в сравнении с моей реализацией на коленке трехмерного массива, он просто летает. Относительно Flat3d-поделки предположили, что дело в дорогих арифметических операциях и вызовах методов, которые не инлайнятся. Убрал лишний метод и немного оптимизировал арифметику. В итоге
Было (http://pastebin.com/mwqnm2re): Test fill flat array in xyz order: 26059ms (20 repeats, avr 1302ms)
Стало (http://pastebin.com/2QVsRuyP): Test fill flat array in xyz order: 10392ms (20 repeats, avr 519ms)
Из того, что скорость выросла в 2 с лишним раза следует, что компилятор не иинлайнил и JIT ничегошеньки не оптимизировал.

Во-вторых, почему доступ к элементам многомерного массива в два раза массивы-миллисекунды-указатели-коровники )
stdray: (Default)
Выбирает, видимо, себе язык под проект мой, можно сказать, знакомый. Набигает с нехитрой задачей: создать трехмерный массив определенного размера, а потом пробежаться по нему 20 раз, присваивая значения счетчика. Потрать 5 минут, говорит, сделай на F#, чтобы его показатели тоже были. Мне нетрудно - взял и написал не идиоматический код с мутабельностью и форами. Многомерный массив? Кто такой? )
stdray: (Default)
Почему в F# и Nemerle списки, которые в коробке, являются энергичными, ведь с их помощью нельзя обрабатывать сколько-нибудь значительные объемы данных? В том же F# списки можно использовать только для написания симпатичных примерчиков вроде
  1. let rec insertions x = function  
  2.     | []             -> [[x]]  
  3.     | (y :: ys) as l -> (x::l)::(List.map (fun x -> y::x) (insertions x ys))  
  4.   
  5. let rec permutations = function  
  6.     | []      -> [ [] ]  
  7.     | x :: xs -> List.concat ( List.map (insertions x) (permutations xs) )  

но даже его, для реального использования необходимо переписывать через sequence. А sequence, в свою очередь, является простой оберткой на IEnumerable, для которого не определена определена операция Cons(head, tail), то есть в сопоставлении с образцом использовать нельзя, кроме как введением костылей вроде
  1. let (|SeqEmpty|SeqCons|) (xs: 'a seq) = //'  
  2.   if Seq.isEmpty xs then SeqEmpty  
  3.   else SeqCons(Seq.head xs, Seq.skip 1 xs)  

которые безбожно тормозят, поскольку каждый Skip порождает новую цепочку вычислений, которую надо разворачивать с самого начала. То есть матчить sequence нельзя, а для повторного использования результатов вычислений его постоянно оборачивают в Seq.cache. А на все вопросы относительно такого поведения, так называемые гуру дают ответ, что надо просто использовать LazyList из FSharpPowerPack. Ленивый список ок, его можно матчить и семантически он аналогичен кешированному Seq, но для него не определены computation expression, хотя это совсем незначительный эстетический недостаток.

В Nemerle существует некоторый хаос. То есть стандартизированных ленивых списков нет, зато существует огромное колличество подпорок, в которых можно затеряться

Так же, у меня сложилось мнение, что немерлисты односвязанному ииммутабельному списку предпочитают родные дотнетовские List[T] и oh shi~ Array[T] с соответствующими императивными паттернами, даже специальная конструкция для выхода из множества вложенных циклов есть. То есть ЧИСТОТА не в почете.

Есть какие-то разумные объяснения подобной ситуации? Почему они встраивают энергичные списки в язык, если ими физически невозможно пользоваться?


PS: Забыл сказать, что идет июньский конкурс по функциональному программированию. Мое текущее решение на Nemerle отрабатывает за 42 секунды на нетбуке. Хочу попробовать переписать код с использованием ассинхронных вычислений, надеюсь побыстрее будет. Может кто-то хочет показать класс, решив задачу еще быстрее?

August 2017

S M T W T F S
  12345
6789101112
13141516171819
20212223242526
27282930 31  

Syndicate

RSS Atom

Most Popular Tags

Style Credit

Expand Cut Tags

No cut tags