Python conf in Kharkiv, Nov 16 with Intel, Elastic engineering leaders. Prices go up 21.10

Kyiv Haskell Study Group Fall 2019

Радий повідомити, що ми запускаємо набір на Haskell Study Group! Більше інфо на kyivlambda.com/haskell-study-group

LinkedIn
Допустимые теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Допустимые теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Есть возможность участвовать удаленно?

Ви можете самостійно проходити матеріал та задавати питання в Slack-каналі, не бачу проблем.

А есть примеры реальных приложений на хаскеле, чтобы в проде, не лабораторка, не модуль на 3 файла с какой-то логикой?

Есть крупные компании, у которых в проде есть хаскель.Тинькоф, например.

Они заменили ту же систему, написанную на с++. Я бы радовался только самому факту замены. Это была чисто бизнес логика. По факту использовали фп для управления потоком выполнения. Выглядит норм, но я как раз спрашивал, чтобы не так. И вообще пример фанга с одним из разработчиков компилятора не очень пример

Мм, та ні. Вони замінили DSL (на чому він там реалізований я не знаю), на розробку якого витрачалося багато сил, та який був спеціалізованим для фб, на Хаскел, при цьому отримавши кращу швидкодію, паралелізм, та зробивши так, що люди тепер практикуються на Хаскелі замість закритої спеціалізованої мови.

Ось більш широкий список: github.com/erkmos/haskell-companies

Плюс є такі стартапи типу мого, яких там очевидно поки нема.

Якщо цікаво опенсорс приклад реального веб апп — подивіться на мій meetup.events

Хотелось бы увидеть как раз проект, где больше 1 человека.
Пример.
У вас есть какой-то сервис, состоящий, допустим, из микросервисов. Скорее всего есть какие-то низкоуровневые модули для данных, транспорта, логирования и тд.
Вопросы:
1. Сколько времени будет занимать процесс «накарирования» и частичного приминения этих самых модулей под нужды в конкретном сервисе?
2. Чисто даже теоретически как карирование и частичное приминение (ручное объявление графа зависимостей) может быть сравнимо с DI (автоматическое объявление графа зависимостей)
3. Допустим хочется логировать. И хочется делать это в рандомных слоях. Что делать?
4. Инфраструктура и библиотеки?

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

На моїй поточній та минулій роботі більше однієї людини)) але архітектура від цього особливо не змінюється.

1. Сколько времени будет занимать процесс «накарирования» и частичного приминения этих самых модулей под нужды в конкретном сервисе?

Що таке «накарирование модулей»? Дуже незрозуміле питання. Нічим подібним займатись не доводиться.

2. Чисто даже теоретически как карирование и частичное приминение (ручное объявление графа зависимостей) может быть сравнимо с DI (автоматическое объявление графа зависимостей)

Граф залежностей визначається тим, яка функція яку викликає. Знову ж таки, буду вдячний за більш доступний опис.

3. Допустим хочется логировать. И хочется делать это в рандомных слоях. Что делать?

Викликати функцію logInfo (або logError). В чому проблема?

4. Инфраструктура и библиотеки?

Все є!

Викликати функцію logInfo (або logError). В чому проблема?

Наверное в том, что логирование — это IO, что в pure functions делать не получится.

А, ну так, більшість коду як правило і є в IO. Якщо хочеться логіювати код не з IO — поверніть дані, які хочете логіювати, в IO, та там його логіюйте.

main = do
let (res, stuffToLog) = myPureComputation
log stuffToLog

Будь добр, потрудись раскрыть мысль, чтобы было понятно про какие эффекты ты говоришь в pure functions.

typelevel.org/...​t/typeclasses/effect.html вот это. Заметание сайд эффектов под ковёр среды исполнения, так чтобы код оставался максимально похожим на pure. НЯЗ, в хаскеле прямо встроенные средства для этих целей есть.

Да, есть еффекты, но разе logInfo или logError — это эффекты?

Если есть небезопасное взаимодействие с внешним миром, то вполне себе да. Если ваша приложунька вертится в GCP и пишет в Stackdriver, то это самый настоящий эффект.

Я так розумію, мається на увазі щось типу Writer Monad, де ви можете чистий код писати, додаючи операцію log ніби у вас код не чистий, а воно загорнетьсяв по суті повертання всіх лог-записів kseo.github.io/...​7-01-21-writer-monad.html

Звичайно, в реальному житті мало кому це все потрібно, і зазвичай, якщо чистий код потребує логінг, він перестає бути чистим (точніше ділиться на чистий код «до» та «після» логгінгу).

Берите скалу, для реального мира более чем подходит.

Штука в том, что, когда код хорошо стрктурирован, логировать где-попало не нужно, логика получается pure, а IO выносится на верхний уровень. Вот интересная статья и доклад по теме: blog.ploeh.dk/...​-to-dependency-rejection www.youtube.com/watch?v=cxs7oLGrxQ4 и еще вот это blog.ploeh.dk/...​1/asynchronous-injection.

Да, согласен, фугкциональщина работает отлично в репозиториях на несколько файлов аля лабораторка

Есть уметь в проектирование, то обработка каждой команды будет

на несколько файлов аля лабораторка

Если да кабы. Там вот выше пару вопросов простых задал, но к сожалению без ответов...

Так лапшать можно на любом языка. На фунциональном труднее, наверное. А на какие вопросы нет ответа? DI и логирование разобраны по ссылкам, которые я привел выше.

Да я читал симана. Там как раз partial application is dependency injection, что есть как бы правда.
Но только в теории как это обычно происходит с функциональными языками. На деле вам необходимо одну и ту же зависимость внедрять каждый раз вручную и таким образом вручную описать весь граф зависимостей. Который в свою очередь тем больше, чем больше проектов и функционала.
А вот эти штуки про хорошо спроектировано.. Что-то мне подсказывает, что у вас либо что-то маленькое либо каскадное одноразовое.

Но, когда добираешься до чистых функций, что есть чаще управление ходом выполнения или логика, тогда конечно есть бенефиты в сравнении с универсальными классическими языками и подходами

Марк, как раз, пишет, что зависимости не нужны.

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

Я начал с того, а есть ли на примете реальный проект, а не лабораторка, где это используется и без слез.
Хотя с его описанием проблемы я согласен.

Мы в команде используем похожий подход. У нас C#, а не Haskell и без наворотов, как у Марка, но принципы те же.

Проект довольно большой и уже использовали C#, когда я пришел. Переписывать на F# нецелесообразно на данный момент. Плюс это learning curve для всей команды, а мы сейчас двигаемся а k8s, что тоже требует много времени на обучение. Так что пока другие приоритеты 🙂.

Понял. Просто тоже прошел через похожий paradigm shift: C# -> functional-oriented C# -> F#. Переписал (в спартанском режиме, правда, за неделю) кое-что на F# — эффект поразительный, codebase ужался многократно, простота и поддерживаемость выросла. (Дело конечно не только в языке, изменилась парадигма и подход, поэтому тут не только вина C# против F#...).

А порог вхождения в F# кстати может быть «сглажен», ведь язык очень демократичный и кстати имхо довольно простой, намного проще той же Скалы. Долгое время будучи лютым фанатом C# я тоже считал, что для многих вещей F# вообще не нужен, хватит и C#, и поначалу пробовал все эти симановские/ФПшные штучки пихать в C#, но когда попробовал F#, возвращаться к C# больше вообще не хочется)

думаю, вам просто надо решиться, позже = никогда !

Вы все функции описываете в виде статических методов или я что-то пропустил?

Нет, конечно, вы что-то неправильно прочитали/поняли.

Наверное. Вы ведь о том, что в с# проекте используете partial application вместо di и все выглядит не убого за счет очень простых обработчиков запросов?
Если да, то как вы это делаете в с# и куда делись сложные запросы с хоть какой-то логикой?

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

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

Так как у нас Mongo, мы используем oplog tailing чтобы паблишить ивенты из записанной сущности, а так как большая база кода, некоторые ивенты мы генерим неявно по полям, которые изменились в сущности, чтобы сохранить обратную совместимость. Получаются общие ивенты Created, Updated, Deleted. В общем случае это антипаттерн.

Наведіть конкретний приклад проблеми, яку ви вирішуєте. Зазвичай di називають просто передачу функцій в якості параметрів.

К сожалению, лучше чем предыдущие 2 раза я не опишу. Поэтому спросил про реальные проекты на что получил ссылку, извиняюсь, на проект реально уровня лабораторки по объему.

В нас закритий код та продукт в поточній та минулій компанії, ссилку давати нікуди, але принципово не розумію що це змінює. Така ж архітектура, ті ж проблеми. Що ж, шкода що не змогли порозумітись.

Вот выше был пример симана. blog.ploeh.dk/...​/02/dependency-rejection
Попробуем его. Сам по себе он ужасный, т.к. резервации у него проверяются в коде не атомарно, но допустим. Там есть функция композиции вызовов tryAcceptComposition. Все классно с ней.
Но откуда берутся DB.Xxx? Кто и где это конфигурирует?

Подивився приклад, він дійсно жахливий, але з трохи інших, більш простих причин. Можливо я просто чогось не розумію та ви їх роз’ясните?

Одже, в першій частині blog.ploeh.dk/...​/02/dependency-rejection має пояснюватись проблема, яку ми вирішуємо. Там наведено http handler, що валідує дані та робить резервацію (або кидає помилку).

Далі автор пише:

> They are dependencies. Could you make the Post method take them as arguments?

Навіщо це робити? Яку це проблему вирішить?

На що вам не відповіли? Я попросив уточнити формулювання, що не мають сенсу. З задоволенням на все відповім більш розгорнуто.

Это проблемы, которые возникают уже на 2й день после того, как на проекте появляются общие компоненты и модули.
Проблема может быть выведена чисто теоретически на основании того, что зависимости между функциями являются чем-то в духе dag.
Простые выводы:
1. Описание дага тем сложнее, чем больше его размер.
Т.е. на каждую зависимость нужно делать композицию функций для использования под конкретные нужды. К примеру, внедрение логирования, конфигурирование и тд.
В di описываются только ноды, сам же граф строится автоматически.
2. Низкая толерантность к изменениям.
Накомпозировали вы функции в разных подпроектах, настроили. А тут хопа и контрактик поменялся, параметр добавился. И нужно переколбасить весь граф.
В di обычно абстракции более стойкие к изменениям, а изменения описанные выше чаще будут внедрены автоматически в конструктор, которого нет в функциях.

П.с. Я это пишу не потому что фп плохо. Просто есть вещи, с которыми справляется плохо.

П.с.2 под словом di я подразумеваю внедрение зависимостей на основании типов, которое используется при ооп парадигме

Проблема циклічних залежностей, як правило, виникає, коли два компоненти один від типів іншого, як правило вирішується додатковим модулем «.Types», в якому зберігаються типи, та залежність розбивається на окремо залежність від типів та модулів. ООП-мови страждають, тому що в них дані та методи відділяти важче. На моєму багаторічному проекті цього було достатньо для боротьби з цією проблемою.

П.с.2 под словом di я подразумеваю внедрение зависимостей на основании типов, которое используется при ооп парадигме

Ви можете виконувати інший код залежно від типу за допомогою тайп-класів, але знову ж таки, важко сказати що краще використовувати без більш конкретного опису проблеми, більш точними термінами. Більшість DI-проблем в ООП-мовах, які я бачив, в функційних мовах просто не виникає, через те, що тут відділяють типи, дані та функції.

Я досі не розумію, чому воно погано на вашу думку працює в більших кодових базах. Мені навпаки, здається що чим більше кодова база, тим більший бенефіт, бо ви менше боїтесь торкатись чужого коду. На Хаскелі, принаймі, в мене саме такий досвід, що я можу взяти чужий код річної давнини та спокійно його рефакторити та змінювати.

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

Це просто неправда. Карування можете не використовувати якщо не хочете, я за останні шість років функцію curry викликав разів п‘ять. Не розумію звідки у вас настільки хибні уявлення про фп.

Ну, воно використовується іноді для зручності, але теж не є якоюсь центральною частиною побудови програм, і я досі не розумію значення ось цієї фрази

Как минимум потому что чисто функциональный подход предполагает ручную композицию функций через частичное приминение

В ООП виклик методу не є ручною композицією? Взагалі не розумію що таке ручна композиція і що таке неручна.

Список репозиториев с лабораторками на несколько файлов: github.com/...​?l=haskell&o=desc&s=stars

Спасибо за ссылку. Начал смотреть и вспомнил, что еще одна фича — огромные файлы 😊. И это тоже вполне себе выводится теоретически, вполне себе by design

Да и в проектах побольше. И там она даже получше работает чем ИП+ООП ибо настолько взрывного эффекта снежного кома там не возникает.

Вопрос был не про нужно/не нужно, а «что делать, если хочется?». Хочется логировать, хоть всрись!

Хочется — логируй. В чем проблема?

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

1. Сколько времени будет занимать процесс «накарирования» и частичного приминения этих самых модулей под нужды в конкретном сервисе?

ну это же всё просто вызов функции либо Config => Module либо Config => F[Module]. Иногда он бывает ассинхронный, если в какой-то сервис надо сходить и коннекшн открыть.

ожет быть сравнимо с DI

зачем нужно DI в языках типа хаскеля и скалы?

Я не говорю, что он там нужен. Я приводил сравнение, что сложность конфигурирования функций выглядит как описание узлов и их связей в dag, в то время как di это только описание узлов, что в теории проще.
А на практике описание связей вручную больше выглядит как рутинная работа.
Предположим у вас система состоит из множества микросервисов. Реально микросервисов — маааленьких. В таком случае сам процесс настройки функций под нужды сервиса может требовать больше времени чем написание самой логики сервиса. Ведь каждый раз нужно описать dag вручную.

Есть такая замечательная вещь как имплиситы и тайпклассы. Специально для этих целей придумано — чтобы много не писать. Вопреки распространённому мнению, что имплиситы фу(и в скале, и в хаскеле) и кроме инстансов стандартных тайпклассов(в скале) лучше их не юзать и не объявлять вообще(в хаскеле) — данный юзкейс вполне опрадан. Это нужно для того чтобы по типу параметра находить аргументы в области видимости и подставлять их в нужные места. Используя типы можно вполне убрать бойлерплейт прозрачным образом.

docs.scala-lang.org/...​erviews/core/futures.html Условно, «конфигурация» операции map и flatMap в стандартной скала-фьюче, которая говорит на каком пуле потоков нужно её исполнять.

Ну раз такой пример не нравится, вот тебе физз-баз. Код писал от балды, ибо цель была продемонстрировать как тут работают имплиситы. Внимание на то, что ни энв ни продукт не был вписан в параметры, оно нашлось само компилятором. scastie.scala-lang.org/...​/efLILMS2S4i7z4OiRe7gCg/1

Ну раз такой пример не нравится

за примеры большое спасибо (без сарказма).

Ниже разговор не пойдет за чистоту функций вообще.

Про паттерны компоновки зависимостей в фп:
1. Cake pattern
мы создаем вложенные traits (ущербный module в f#, с которыми так вроде не получится): внешний используется для конфигурирования зависимостей, а внутренний уже как что-то очень полезное в программе, что использует ту самую зависимость.
немного похоже на старые добрые модули в js
2. Implicits (scala only?)
где-то в области видимости явно определяем implicit «глобальную» переменную, в функциях используем «implicit» директиву аргумента. и магическим образом получаем встроенный в компилятор (или рантайм?) DI. аргумент резолвится по типу.
3. reader monad
делаем огромный объект-реестр всех зависимостей и вхерячиваем передаем его по цепочке функций. и совсем не похоже на service locator
4. partial application
имеем тонну функций, которые принимают функции, которые в свою очередь имеют свои зоны ответственности.
в итоге в среднего размера приложении мы имеем DAG из функций: конфигурация, которая передается в опции, которые в свою очередь являются одним из параметров DAL функции, которая в свою очередь является параметром какой-то BAL функции... и т.д.

Немного предыстории.
где-то в этом топике гуляет ссылка на серию статей марка симана, почему хороший OOP приближается к фп (моя интерпретация названия).
один из примеров статей был о том, что если вы православный адепт ООП, то перед сном повторяете заповеди SOLID и для этого у вас есть только одна причина. Последние в свою очередь, кроме всего прочего, ведут к сегрегации интерфейсов и по сути классам 1 метода. что есть много писанины (интерфейсик опиши, классик имплементируй и т.д.). и вроде как в итоге мы имеем просто 1 метод, т.е. 1 функцию. а все зависимости — функции, которые имеют свои зоны ответственности и т.д.

Так вот действительно на первый взгляд ведь если просто начать писать все то же самое на f# (подставить свой любимый язык), писанины становится меньше. кода меньше. меньше работать, больше времени на алкоголь и женщин. Все примеры выглядят, как манна небесная, можно использовать в предвыборной программе.

На деле как-то так не получается.
1. Cake pattern — писанины становится как минимум не меньше, в то время как конфигурирование выглядит более убого, чем кошерные DI контейнеры с тонной встроенного функционала. сущностей мы меньше не имеем. точка конфигурирования на вид отсутствует или как минимум становится более избыточной.
2. Implicits. Это выглядит неплохо, но на первых взгляд хочется оторвать руки тому, кто будет это использовать на уровне проекта особенно с кросс проектными зависимостями, а не файла максимум. это очень неявно, что нам и пытается донести автор названием.
3. Reader Monad
тут особо и писать не о чем. это костыль. лучшее из придуманного в фп выглядит как худшее из придуманного в ООП. Требует тоже больше писанины, но все же не так много как если определять 100500 интерфейсов.
4. Partial application
это то, с чего я начал свои комментарии. с того, что нужно ручками описать связи целого DAG, в то время как в кошерном DI связи определяются автоматически

// где-то разбросано по модулям все нижеописанное добро

let config = { ConnectionString: «blablabla» }
let dbOptions = {
connectionString: string
}

let dbWrite options key value =
let connection = FoxProXxl.Connect options
session.write key value

let makeBusinessHappy writer businessObject =
writer businesObject.key businessObject.value

// а теперь мы начинаем писать приложение
let dbOptions = {ConnectionString: config.ConnectionString}
let myWriter = dbWriter dbOptions
let realHappiness = myWriter
let somewhere (obj) =
realHappiness obj

*прошу код не использовать в продакшне, NDA.

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

что из этого удобно использовать для компоновки зависимостей?
здесь могла бы быть ваша реклама.

Если необходимость сократить количество писанины — пользуйте тайпклассы, импортите инстансы те которые нужно, и IDE (INTELLIJ по крайней мере) умеет искать какой имплисит куда залетает, но обычно, есть правило, если есть тип, то один инстанс и без шедоуинга. Для упрощения есть ньютайпы, и какая-то либа для специального менеджмента инстансов тайпклассов по модулям а-ля DI по фп шному. Parital application — по прежнему самая good looking thing на мой взгляд, если не сетапить там флоу с разветвлениями и не разрастать его до килострочек. Writer это точно дичь, и использовать его для этих целей не нужно.

тестировать все эти инстансы функций

не нужно, нужно тестировать функции что они пашут при любых своих аргументах комбинаторным тестированием. Тогда и любой инстанс будет работать правильно, если то что подсовывается — соблюдает контракт.

целого DAG

никогда не испытавал проблем по этому поводу, приложения на ФП получаются обычно меньше в терминах архитектурных излишеств(сущностей которые нужно непосредственно просовывать вне тайпклассов и имплиситов очень и очень мало), и если у вас не ОАО «Монолит», то особо там ничего прописывать и не нужно. Слышал товарищи пишут магию с солвером на типах, «чтоб как в кошерном DI», но в релизе пока этого нет, наверное потому что никто монструозных аппов не пишет.

На проекте, где все хорошо, какой размер кодобазы, сколько слоев, сколько сервисов и независимых компонентов?

На проекте, где все хорошо, какой размер кодобазы, сколько слоев, сколько сервисов и независимых компонентов?

Если хочется узнать про прямо таки сириоус бузинесс, то это лучше к инженерам тинька обращатся, у них большая контора.

какой размер кодобазы, сколько слоев, сколько сервисов

сам факт использования фп вырезает несколько слоёв абстракции. Например если у вас есть необходимость кодирования в несколько форматов обмена типа json /bson/etc вы просто бахаете github.com/danslapman/morphling и не паритесь. −1 слой. Если нужно городить мапперы из одной модели в другую, вы бахаете github.com/milessabin/shapeless или магнолию. Ещё −1 слой. Если вам нужно городить «сложную логику» с перобразованием для моделей — slick.lightbend.com или doobie, или ещё что. Если нужно городить сложную штуку для всякого-там стриминга и прочей реактивной лабуды, решения типа fs2 тоже есть. В итоге от моструозного аппа с тоннами слоёв и «зе коде из солид» остаётся только чуть-чуть, и читабельность семантика не ломается.

По этому слоки и слои сравнивать проблематично — некоторые слои ооп аппа не с чем сопоставить, особенно учитывая что это присыпано тонной синтаксического сахара. У читывая уменьшенный эффект снежного кома, который есть в ООП аппах при увеличении размеров, параллели проводить ещё сложнее.

Если честно, без размера обсуждать сложно и смысла не имеет.

Где вы были когда я был студентом?

Фигасе список спонсоров, я там надеюсь везде хаскель в прадакшене юзают ?

Ні, в основному це ініціатива співробітників цих компаній, що були на минулій групі по Хаскелю. Але всі компанії зі списку люблять функційне програмування, це факт.

В чем профит от таких групп для изучающаго? Проще самому изучить. Более того, материала полно.

Для изучающих хз, а вот на продаже книги кто-то явно заработает))

Ага, автори отримають суму, що приблизно дорівнює половині дня роботи програмістом в США. Скажені гроші!

А это не важно)
Важно как это выглядит со стороны))

Дякую за вашу думку, якщо з‘явиться ідея — перефразую щоби виглядало краще.

У всіх свої підходи до вивчення, якщо вам більше підходить самостійне вивчення — я тільки за.

Зі свого досвіду проведення груп з навчання та від відгуків тих, хто на них був:

— в групі ви отримуєте можливість подивитись код інших (а в деяких домашках у нас буде взагалі peer to peer review)
— на зустрічах ви можете запитати все, що вас цікавить, отримати допомогу з домашок, які не вийшло зробити
— зазвичай я намагаюсь на проекторі додатково розказати щось цікаве
— багато кому (мені в тому числі) разом щось вчити — веселіше

Ну зависит от публики, скорее всего , это либо студенты, либо новички в программинге. Думаю синьоры сами способны освоить, либо нанять ментора.
При изучении любого ЯП все же лучше смотреть код популярных проектов, чем таких же начинающих.

З досвіду минулої групи — по різному, студентів майже не було

Добре, що займаєшся викладанням

Але слово Fall звучить як Fail, краще придумати більш звучну назву для теми

Fall — це осінь в перекладі з американської англійської

Подписаться на комментарии