Pull to refresh

Comments 267

А каким образом Хаскелю удаётся транслировать математические абстракции в машинный код? Например, я не понимаю, как учитывается переполнение стека при рекурсии. Любое применение функции — это Just x | bottom. И я не понимаю, как с этим жить.

UFO just landed and posted this here
От переполнения стека спасает ленивость. То есть функция не вычисляет все значение на своем стеке, а возвращает его недовычисленным. Рекурсивные вызовы для довычисления частей результата происходят уже после в выхода из функции и удаления фрейма стека.
Фактически, это встроенный в язык паттерн «трамплин».
Конечно, компилятор делает и оптимизации хвостовых вызовов.
UFO just landed and posted this here
Для понимания собственно ООП вполне хватает здравого смысла и среднеобывательского словарного запаса.

Вот паттерны − это специфическая вещь. Начиная с того, что книжка Банды Четырёх − это продукт борьбы (как минимум троих) авторов с особенностями и недостатками языка Java. Соответственно, понятна она только в этом контексте.
UFO just landed and posted this here
Дык натянуть-то можно. Плюсы, я б сказал, ещё ничего. Некоторые на пайтоне синглтоны пишут. На скотском серьёзе, да.

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


Какие преимущества даёт абстрактная алгебра для реального программирования всё ещё никто не продемонстрировал.

UFO just landed and posted this here

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

UFO just landed and posted this here

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

UFO just landed and posted this here

eDSL и детерминированные stateful вычисления не пишут без монад? ИМХО там гораздо проще допустить какую-нибудь логическую ошибку с монадами из-за лишнего синтаксического шума.


Пример из жизни по паттернам ООП. Есть что-то подобное про монады?

UFO just landed and posted this here

А в чем недетерминированность параллелизма с мьютексами и семафорами, например?


Инкапсулировать в функцию — это имеется ввиду возвращать функцию-исполнитель из функции-конструктора, возвращающей ту или иную функцию в зависимости от контекста? Да, неплохая идея! А если нам нужно несколько функций, то можно их вернуть в виде списка «ключ-значение». И функцию-конструктор, чтобы было понятно, назвать WidgetConstructor или WidgetFactory… Постойте-ка, да мы же изобрели ООП!

UFO just landed and posted this here

Что подразумевается под «гарантией»? Формальная верификация?


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

UFO just landed and posted this here
Зачем это называть фабрикой?

Можете предложить более удачное название?

UFO just landed and posted this here
Любое другое незанятое

Например, какое, и чем оно будет лучше-то?

UFO just landed and posted this here
Ну ещё раз. То, что предложено в туториале по ссылке выше, не похоже на фабрику в том смысле, в котором она определена на википедии.

В каком смысле не похоже? Там же одно и то же.

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

Тех кто устраивает фабрику ради «вынесения кода в абстрактный класс» (или отдельную функцию) надо бить линейкой по голове, пока не протрезвеют.

Фабричный метод — это не фабрика, это уже паттерн «Строитель» (Builder) — декомпозиция сложного конструктора.

Не надо путать. Это разные вещи.
UFO just landed and posted this here
Жизненный пример:
Есть абстрактный класс компонента и тьма тьмущая его наследников.
На вход поступает некий конфиг, допустим JSON, по данным этого JSONа однозначным образом создаётся какой-то компонент.
Вот функция, которая реализует логику выбора подходящего класса наследника компонента, создаёт и возвращает экземпляр оного класса — получила гордое название ComponentFactory )))
UFO just landed and posted this here
В рантайме выбирать ничего из этой пары сотен не надо было.

Вам — не надо было, а нам — надо.
С сервера приходит инструкция (ага, JSONом) «Нарисуй-ка компонент с таким именем и такими параметрами». Клиент, соответственно, проверяет, «какой-там у меня класс лежит в мапе по этому имени?..»
UFO just landed and posted this here
А зачем тут какая-то фабрика?

Название такое.
А зачем тут какая-то фабрика?

В вашем примере — не нужна, но если вам надо будет написать какую-то ф-ю, которая сама не знает, какой объект ей надо создать (Foo или Bar), а решается это на call site — то у вас появится фабрика.

Если я правильно понял код, то там именно это и происходит: функция на основании параметров решает какого класса компонент создавать.
Это способ реализовать фабрику на функциональном языке.
UFO just landed and posted this here
функция на основании параметров решает какого класса компонент создавать.

Ну вот если она не решает, а решает внешняя ф-я — то это и будет фабрика. Сама ф-я не должна быть в курсе, какой объект она создает.

Если решает внешняя функция, то в каком виде это решение передаётся во внутреннюю?
Если решает внешняя функция, то в каком виде это решение передаётся во внутреннюю?

Вот в виде конкретной фабрики внешняя функция во внутреннюю эту информацию и передает.

UFO just landed and posted this here

Ровно за тем же самым, за чем нужна любая high-rank полиморфная ф-я.


Допустим, у вас есть ф-я f, она вызывает ф-ю g. Ф-я g во время своей работы должна сконструировать некоторый объект с интерфейсом interface (я подразумеваю сейчас интерфейс в бщем смысле, вне зависимости от деталей реализации — оопшные, тайпклассы, какие-то свои велосипеды — тут не важно). С-но, ф-я g знает интерфейс, но не знает какой конкретно это будет тип.
Мы могли бы, конечно, сконструировать просто конкретное значение в f и напрямую закинуть в g, но:


  1. возможно нам надо в g создать несколько объектов соответствующего типа и что-то с ними поделать. тогда придется внутри f все эти объекты создать и потом в g закинуть
  2. возможно при создании объектов нужны будут какие-то дополнительные параметры, за проброс которых в конструктор, опять же, будет отвечать в данном случае f

Мы, допустим, не хотим, чтобы эта логика была в f, мы как раз хотим, чтобы она была в g. Тогда вместо того, чтобы совать в g конкретное значение, мы суем туда сам конструктор.


Вот такой конструктор, который засовывается в некоторую g, и при этом g — знает, что он конструирует объекты определенного интерфейса, но не знает, какие конкретно — это и есть фабрика.

UFO just landed and posted this here
Тогда непонятно, почему f вызывает g, а не наоборот. Выглядит как какой-то кривой дизайн.

Потому что f нужен результат g, а не наоборот :)


Почему?

Ну по той же причине, по которой вы передаете фунарг в map, например. Хотите разделить логику. Можете, конечно, передавать map в ее аргумент, а не наоборот. Но зачем? :)

UFO just landed and posted this here
То есть, она и объекты создаёт, и результат ей нужен?

Нет, f объектов не создает. f сует в g фабрику, а g — уже создает. f возможно вообще не знает как именно надо объекты создавать (какие аргументы совать в конструктор), но знает какой именно тип объектов надо создавать. А g — знает как создавать, но не знает конкретный тип (только интерфейс).


Так с map как раз отличный пример фунаргов. Но вот примера такой наркоманской фабрики всё нет, увы.

Ну вот есть у вас карандаши, ручки и фломастеры, ими можно рисовать. Ф-я g — умеет рисовать, но при этом обобщенно, через интерфейс (рисует и фломастерами и карандашами и ручками, при этом не обращая внимания на то, что перед ней).
Вы в f берете коробку конкретных объектов (например, карандашей), суете в g и говорите, что нарисовать. Вот эта коробка — и есть фабрика, т.к. g может при помощи нее получить объект, который рисует нужным цветом (при этом f вообще может ничего не знать о рисовании и цветах). И потом возвращает вам в f рисунок, написанный нужным штрихом. Потом в f делаете с этим рисунком что вам угодно.

UFO just landed and posted this here
Мне сложно представить себе практическую нужность такой архитектуры.

Ну обычное разделение ответственности. Все так пишут, в том числе и на хаскеле постоянно :)


Ну так у меня там неявно третья функция, которая и есть фабрика

Это вы про какую неявную функцию?


Я всё ещё не понимаю обсуждаемой проблемы.

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

UFO just landed and posted this here
Так в том и дело, что оно там необычное и неокончательное.

Что неокончательное, в каком смысле?


Та коробка, которую я беру в f и сую в g.

Так а где она, хоть и неявная?

UFO just landed and posted this here
В смысле, что функция отвечает и за то, что выбирает, что рисовать, и за создание коробки карандашей отвечает.

Ну тут все правильно, ведь "чем рисовать" это часть "что".


Ну вот эта вот коробка отсюда:

Она тут вполне явная, почему неявная-то?

Мне сложно представить себе практическую нужность такой архитектуры.


Я вспомнил ещё один пример из жизни.
Есть у меня компонент, который реализует визуальный интерфейс для разработки фильта (ну, переключалки для логических операций и и кнопки составления дерева из них, окошки для ввода констант, выбора переменных и т.д. и т.п.)
Он в ходе своей работы по мере того, как юзер натыкивает мышкой фильтр, создаёт вспомогательные компоненты (представляющие элементарные фильтры, мапа «имя: класс» которых пришла с сервера) — для этого у него есть фабрика компонентов (она создана заранее и была передана в конструктор компонента-фильтра).

Так вот, у нас в проекте два вида фильтров, чуть-чуть отличающихся логикой внутри своих вспомогательных компонентов, причём логика самого компонента не отличается.
Самым простым решением оказалось создать вторую фабрику, набить её мапу чуть-чуть другими компонентами, и использовать для представления разных фильтров один и тот же класс, но с разной фабрикой.
UFO just landed and posted this here
Я понимаю, что Вы не спорите, я просто привёл реальные примеры. Первый пример — та фабрика, вокруг которой у нас весть проект построен.
Второй пример — случай, когда объект может содержать в своём поле разные фабрики, чтобы выполнять над ними (и создаваемыми ими объектами) одни и те же действия.

Я долго думал и, кажется понял. Фабрика это функция, возвращающая копроизведение

UFO just landed and posted this here

Конкретный морфизм откуда? И на основании чего происходит выбор? Учитывая каррирование, частичное применение, представимость любого хом множества в категории hask, изоморфизм между a и ()->a это всё одно и то же.

UFO just landed and posted this here

В данном случае копроизведение — дизъюнктное объединение.

UFO just landed and posted this here
UFO just landed and posted this here
Функция или объект с единственным методом — это нюансы реализации.

Как-то однажды знаменитый учитель Кх Ан вышел на прогулку с учеником Антоном. Надеясь разговорить учителя, Антон спросил: "Учитель, слыхал я, что объекты — очень хорошая штука — правда ли это?" Кх Ан посмотрел на ученика с жалостью в глазах и ответил: "Глупый ученик! Объекты — всего лишь замыкания для бедных."


Пристыженный Антон простился с учителем и вернулся в свою комнату, горя желанием как можно скорее изучить замыкания. Он внимательно прочитал все статьи из серии "Lambda: The Ultimate", и родственные им статьи, и написал небольшой интерпретатор Scheme с объектно-ориентированной системой, основанной на замыканиях. Он многому научился, и с нетерпением ждал случая сообщить учителю о своих успехах.


Во время следующей прогулки с Кх Аном, Антон, пытаясь произвести хорошее впечатление, сказал: "Учитель, я прилежно изучил этот вопрос, и понимаю теперь, что объекты — воистину замыкания для бедных." Кх Ан в ответ ударил Антона палкой и воскликнул: "Когда же ты чему-то научишься? Замыкания — это объекты для бедных!" В эту секунду Антон обрел просветление.


Взято https://ru-lambda.livejournal.com/27669.html

Какие преимущества даёт абстрактная алгебра для реального программирования всё ещё никто не продемонстрировал.

Ну я уже где-то приводил неплохой пример полезного математического рассуждения.
Вот есть в js генераторы. Синтаксис генераторов изоморфен синтаксису do-нотации для call/cc монады без reentrance. При этом мы знаем, что любая монада имеет каноническое выражение через call/cc — значит, мы сразу знаем, что можно использовать синтаксис генераторов для любой монады, которая применяет фунарг внутри fmap'а не более раза. Например — та же async. А вот с list — канонически не выйдет.


Вполне себе полезный вывод. Более того — он конструктивен, т.к. доставляет универсальный способ использования синтаксиса генераторов в монадическом контексте и указывает конкретный критерий, по которому легко определить, когда — можно, а когда — нет.

Чем больше я живу и работаю с другими людьми, тем больше понимаю, что ООП не понимают ни они, ни, тем более, я. Или у всех какое-то свое понимание.

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

UFO just landed and posted this here
На монады не надо смотреть как паттерны, на монады (и прочие тайпклассы) надо смотреть как на доказательство (Карри-Говард, ага) утверждения «мой тип умеет то-то и то-то».

Это, конечно, можно, но штука в том, что код обычно пишется не для того, чтобы что-то доказать, а чтобы решить какую-то задачу. Так что такой взгляд совершенно неконструктивен. Кроме того:


надо смотреть как на доказательство (Карри-Говард, ага) утверждения «мой тип умеет то-то и то-то»

Это паттерны доказано "умеют то-то и то-то" (с-но, в определении паттерна и написано, что он умеет). А вот если я скажу: "Х — монада", то что умеет Х? У меня нет об этом никакой информации. Вообще говоря — ничего не умеет.

А если стоит задача доказать? В чем же тут неконструктивность? Доказательство один из способов верификации решения. Или тестирование и охрана ассертами это тоже неконструктивно?
Кто-то пишет библиотеки и возится с О-большими, корректностью, верификацией и прочим "матаном", а кто-то их комбинирует, не задумываясь, а считая, что "это уже сделано".

А если стоит задача доказать?

Если стоит, то, конечно, вопросов нет. Но в 99% случаев ее не стоит.

UFO just landed and posted this here
Боролись, боролись авторы с Java и, видимо, проиграли. Пришлось примеры к книге писать на C++ и Smalltalk.
Джонсон грокал смолток, остальные трое − джависты. Влиссидис имел бэкграунд в C++, но, наверное, больше работал с джавой на момент написания книги.

Я очень часто вижу, как программисты пытаются обособить себя от своего основного ЯП. С одной стороны, через академизацию своего опыта, мол, мы не программисты, мы computer scientists, мы не изучаем языки, а создаём их. С другой стороны, через ремесленничество − best tool for the job и прочие максимы. Но на практике я ни разу не наблюдал, чтобы кому-то удалось превзойти то форматирующее влияние, которое оказывает на способ мышления его основной инструмент. Одни мыслят категориями статически типизированных языков в динамически типизированных, другие − наоборот. Третьи плодят миллионы классов, ну и т. д.
Design Patterns вышла в 1994 году, а первый релиз Java в 1995 году. Беглый взгляд в википедию не обнаружил связь всех четырех с Sun Microsystems. Поэтому ваше заявление про борьбу с Java выглядит спорным. Зачем с ней было бороться, если в 1994 это был внутренний проект компании, где они не работали?
Вы меня уделали. У меня в голове Банда четырёх была почему-то связана с ростом популярности Java, и я до сих пор не удосужился сличить даты. Позор мне.

Гамма работал в IBM, занимался VisualAge, Eclipse и JDT. Видимо, оттуда мои ассоциации с Java. Хотя было это, конечно, добрый десяток лет спустя.
>Поскольку любой проект в конечном итоге предстоит
реализовывать, в состав паттерна включается пример кода на языке C++ (иногда
на Smalltalk), иллюстрирующего реализацию
Это цитата из перевода 2001 года. И да, слова Java в тексте книги нет вообще.
Влиссидис имел бэкграунд в C++, но, наверное, больше работал с джавой на момент написания книги.


Книга вышла раньше чем Java.
В книге приводятся листинги на примере языков Smalltalk и C++, разве нет? Или у Вас экзотическое/новое издание?

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

Для справки — книга банды четырех вышла в 1994 году. Java — где-то в 1996.
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here

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

UFO just landed and posted this here

Вот с-но от натягивания на глобус совиной жопы в виде "отсутствие значения — это эффект" или "множество значений — это эффект", люди потом монады и "не понимают" :)


Нет, отсутствие значений или множество значений — это не эффекты. Если только не приложить к сове очень значительное усилие :)

UFO just landed and posted this here
Чем не эффекты?

Ничем не эффекты. Хотя, конечно, вы всегда можете растянуть сову до такого размера, что сказать "Х — эффект" будет то же самое, что ничего не сказать. Тогда тот факт, что вы называете списки или ИО эффектами, становится бессодержателен. То есть назвать что-то эффектом тогда — то же самое, что никак не назвать.

Бедная сова. Дело в том, что кто-то определился с определениями, в частности, понятия "эффект" и работает с ним, понимая что и зачем делает. А кто-то спорит о словах и применяет "совиную" аргументацию.
Замените "эффект" на "контекст", может быть, это поможет понять, зачем это нужно. Но главное — если вам монады не нужны, не ссорьтесь, а просто не используйте их.

Дело в том, что кто-то определился с определениями

Ну давайте называть это не эффектом, а "тирьямпампацией" и никаких проблем же. Тогда каогда вы будете другим людям объяснять монады они поймут все гораздо лучше.
Когда вы выбираете в качестве именования некоторого явления уже существующее слово — надо чтобы по смыслу, интуитивно, в каком-то плане ваше явление этому слову соответствовало. Иначе у вас выходит сова.


Замените "эффект" на "контекст"

И лучше совсем не станет. Списки — это, очевидно, не контекст.


Смотрите, вот есть молоток. Им можно забить гвоздь, а можно забить человека до смерти. Несмотря на то, что и там и там — "забить", смысл происходящего совершенно разный, т.к. в первом случае молоток используется в качестве инструмента, а во втором — в качестве оружия. И нет какого-то разумного способа абстрагироваться и объединить два этих варианта использования. Аналогичная ситуация с монадами. нет никакого смысла говорить о "просто монаде" — т.к. это понятие с исчезающе малым содержанием. Нет и не может быть никакого объяснение, которое характеризует поведение монад "в общем". Это главное что надо понять. И объяснить потом человеку, не понимающему монады, чтобы он понял.

>Списки — это, очевидно, не контекст.
И чем это они не контекст? Выполнить некоторую функцию над всеми элементами списка (в контексте одного из них) — это одно из типовых применений монад.
А с динамической типизацией теряется весь смысл статических гарантий управления эффектами.

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

UFO just landed and posted this here
Монады в функциональном программировании используются для выполнения лишь одной роли — эмуляция эффектов характерных для императивного программирования. Непосредственно эмуляция выполняется в функции 'bind' (оператор >>= в Хаскеле). Никаких других мистических качеств у монад нет.
Если мы уже находимся в императивном окружении (ООП в общем его предполагает), то зачем там нужно как-то имитировать/реализовывать монады?
Все что нужно — инкапсуляция, возможности для сайдэффектов в любой ф-ции с параметрами по ссылке, возможность создания последовательности вычислений (с их прерыванием или протаскиванием состояния или генерирования исключения и т.п.) — все что угодно, для чего используются монады в ФП можно реализовать в ООП на императивном языке идиоматично для него вообще не прибегая даже к такому понятию.
Или я не прав? :)
UFO just landed and posted this here
То, что вы перечислили в качестве эффектов — это не более чем элементы программной функциональности. И в качестве таких «эффектов» можно указать много чего — в зависимости от того, что требуется программисту. И в этом смысле это конечно очень широкое понятие.
Суть в том, что в императивных языках привнесение их в программу это не удел монад, в этих языках имеются куда более естественные и идиоматические средства их создания и паттерны проектирования. Поэтому когда в контексте императивного ООП языка упоминают про монады, то это по-моему только лишь для красного словца.
Кстати стоит упомянуть, что самый главный эффект, который достигается с любой из перечисленных вами монад — задание последовательности вычислений (например функция может что-то вычислить, залогировать это, и потом еще что-то довычислить, залогировать и вернуть результат) в императивных языках имеется из коробки (этот привычный эффект и поэтому всегда пропускается, но в ФП его можно добиться только зависимостью по данным).
И в Хаскеле программирование с эффектами тоже не удел лишь монад, для этого можно использовать и аппликативы (или вообще использовать какую-то свою шайтан-конструкцию). Но я уверен, что при желании можно и аппликатив начать демонстрировать как он выглядит и объяснять что это такое например на С++ или Питоне, просто не нашлось ещё желающих просветить программистскую общественность на это счёт. :)
UFO just landed and posted this here
Прелесть в том, что если она в этих монадах не живёт, то логи она точно не пишет и состояние не ковыряет.

Ну никаких гарантий же у вас нет на самом деле. Точнее, они ровно того же уровня, что в логи не будет писать ф-я, у которой в имени нету log, то есть "мамой клянусь".

Есть, компилятор не пропустит.

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


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

UFO just landed and posted this here
Включу Safe Haskell, и у меня уже никто ничего не сломает. Срсли, почитайте ссылку, там клёво.

Ну потому что просто вы не можете использовать unsafePerformIO. Типы тут при чем?
Безопасность на типах — это когда у вас есть unafePerformIO но при этом вы не можете написать с ним некорректный код.


Мне вот в исходном комментарии очень хотелось написать, что, конечно, вы можете ту же State развернуть типах функции в явном виде, но это всё равно видно в типах функции. И -> (a, LogTy) тоже видно.

Ну так в итоге нету у вас никаких гарантий-то.


Ну хорошо, сделайте мне на C++ или JavaScript гарантии, что данная функция не пишет в файл и не посылает ничего по сеточке.

Делается в точности так же как в хаскеле — то есть просто убираете из языка все ф-и, которые пишут в файл и отсылают данные по сеточке, а вместо них добавляете ф-и, которые будут возвращать вам IO. В итоге написать программу, которая что-то куда-то пишет — просто невозможно, вне зависимости от типов.


Еще раз — если у вас просто нету ф-й, которые могут напортачить, то типы тут не при чем, это просто "мамой клянусь" разработчика языка. Вот добавил разработчик вам unsafePerformIO — и ваши типы ничем помочь уже не могут, с-но гарантий никаких не дают.


Гарантии уровня типов — это когда вы можете написать некорректный код (с тем же unsafePerformIO или с записью в файл), но компилятор вам выдаст ошибку. Когда вы просто не можете написать соответствующий код в принципе, то при чем тут типы? Если у вас нету ф-и, которая пишет в файл, то вы не сможете писать в файл ни на хаскеле ни на js.

UFO just landed and posted this here
Какое-то очень произвольное предположение. unsafePerformIO (как и assert_total какой-нибудь в более других языках) — это именно что способ нарушить типобезопасность

Но unsafePerformIO не нарушает типобезопасность. У нее вполне корректный тип. Интерпретатор прекрасно его чекает. Он вполне осмысленный, работает как надо. Все в порядке.


Почему? Вот функция возвращает Int, значит, она точно не пишет в лог и не ковыряет файлы.

Почему? Я могу внутри вызвать unsafePerofmIO, и она напишет.


Так а кто это IO выполняет-то? Или у вас просто какой-то скрипт для интерпретатора получается?

Я же сказал — как в хаскеле. То есть да, это просто какой-то скрипт для интерпретатора.


То есть, по итогу — вся безаопансость ИО в хаскеле заключается в том что вам разработчик стандартной библиотеки мамой клянется — у него все ф-и "правильные", а неправильные ф-и вы написать просто не можете, потому что любая композиция правильных ф-й — тоже правильная ф-я. Как только вы добавляете возможность получить неправильную ф-ю (unsafePerformIO), так сразу и превращается все в тыкву и типы никак не помогают.
Все в точности так же как было бы в каком-нибудь питоне.

UFO just landed and posted this here
Нарушает гарантии, даваемые типами.

Если гарантии, даваемые типами, у вас нарушаются так, что тайпчекер этого не замечает — то это в точности и означает, что никаких гарантий кроме "мамой клянусь" типы вам в данном случае не дают.


Кстати о последнем. unsafeCoerce-то хоть тоже в безопасном подмнжожестве языка можно отвергать, или тайпчекер и там что-то обязан?

Тайпчекер никому ничего не обязан. Он просто либо дает те или иные гарантии, либо не дает. Если тайпчекер позволяет вам написать некорректный код — то он не дает соответствующих гарантий корректности. Вы сами считаете иначе?


А кто в питоне мне статически проверяет правильность композиции функций?

А при чем тут композиция ф-й? Мы, вроде, про ИО говорили, конкретно — про гарантии того, что у вас какая-то там ф-я не пишет в файл или в сеть. Вот конкретно эти гарантии питон дает вам ровно настолько же, насколько дает хаскель — если вы уберете из языка все ф-и, которые позволяют написать в файл/сеть, то вы не сможете писать в файл/сеть, очевидно. Если вы такие ф-и добавите — то любая ф-я сможет писать в файл/сеть совершенно неконтролируемым образом. Что в хаскеле, что в питоне, не важно.


Я включил Safe Haskell, и функция теперь напишет только сообщение об ошибке посредством тайпчекера.

И какую конкретно ошибку типов вам выводит при использовании unsafePerformIO в Safe Haskell?

UFO just landed and posted this here
Если у вас есть магические функции типа unsafePerformIO, тела которых тайпчекер по большому счёту не видит (все эти хаки с распаковкой State — это именно что хаки и торчащие в хаскель-код кишки компилятора/рантайма), то ему просто проверять нечего.

Так а зачем проверять тело unsafePerformIO? Проверять надо тип. И в хаскеле вы не можете написать для unsafePerformIO такой тип, чтобы гарантировать корректное использование этой ф-и. В итоге корректное использование гарантируется тем, что мы просто эту ф-ю убираем с глаз долой из сердца вон. Но точно так же ее можно убрать в любом другом языке, в том числе динамическом. И получить такой же силы гарантии, таким образом. По-этому гарантии ИО в хаскеле обеспечиваются не на уровне типизации.
Ну что тут непонятного?


И примерно по этим причинам Safe Haskell не даёт вам иметь FFI-функции, живущие не в IO.

Но не за счет типов. Точно так же вы можете не давать иметь ффи-функции живущие не в ИО в питоне, ведь так? Никто же вам не мешает.


Нет, в хаскеле не сможет, если не использовать магию.

Дык и в питоне не сможете, если не использовать магию. В чем разница-то?


в хаскеле магия даётся компилятором и легко отключается/выгрепывается, а в питоне весь язык — одна сплошная магия.

Вот, легко отключается/выгрепывается — это верно. Только типы тут не при чем. Еще раз, берем питон и делаем любое ИО через unsafePerormIO. И, ВНЕЗАПНО, в питоне все столь же легко будет отключаться и выгрепываться.


мы получим

Но это не ошибка типов, это ошибка того, что вы импортируете то, что импортировать нельзя. То есть это инкапсуляция. Именно инкапсуляцией гарантии ИО и обеспечиваются в хаскеле. Не типами. И вы можете точно такую же инкапсуляцию устроить в любом языке, в том числе в языке без типов (условном питоне).

UFO just landed and posted this here
Тайпчекеры же не просто типы проверяют, тайпчекеры проверяют, что терм соответствует типу. А у вас тут терма нет.

Как нет? Есть. unsafePerformIO — вот ваш терм. И система типов хаскеля не позволяет присвоить этому терму такой тип, что этот терм можно было использовать гарантированно.


Дело не в том что чекер не выдает ошибку типов когда.


Я его нигде не могу написать, потому что семантика unsafePerformIO несовместима с тем, что IO — unescapable-монада.

Так ради бога, напишите для unsafePerformIO какой-нибудь другой тип. Но так, чтобы можно было с ней работать, а не жопу какую-нибудь.
Я же не говорю, что оно обязательно должно работать с типом IO a -> a, нет, выбирайте любой какой хотите.
Но не получится ни с каким. В этом проблема.


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

Да чего вас в сторону тайпчекера все время уносит? Мы же про монаду ИО говорит, а не про типы.


Давайте возьмем питон, уберем из него все ф-и которые пишут в сеть или в файлы, а вместо нхи будут ф-и, которые генерят лямбды, пишущие в сеть или в файл (то есть семантически пусть IO a = () -> a), причем лямбды будут врапнуты, с-но запустить через простой apply их будет нельзя, но будет специальная ф-я unsafePerformMagic, которая и сможет такие лямбды запускать. Пусть unsafePerformMagic по дефолту лежит где-то там и ее вроде как никто не использует, но у нас будет ф-я бинд, которая юзает ее внутри и клеит вычисления, а так же запускатор наших скриптов будет при запуске применять unsafePerformMagic к определенному в скрипте значению main. Назовем такой язык Pure Python.


И вот теперь, внимание — объясните мне, чем гарантии ИО хаскеля более гарантии, чем гарантии ИО для Pure Python?


Мы, кроме этого, убираем саму возможность её написать самому.

Ну ради бога, из Pure Python тоже убираем.


Но никто не мешает и давать.

Всмысле? Ну же убрали это из библиотеки, все.


В том, что в хаскеле магия сиротливо стоит в уголке и легко контролируема, а в питоне — нет.

Это так. Но, еще раз — при чем тут типы? В Pure Python магия тоже в уголке и легко контролируема, но Pure Pyhton — динамический ЯП. Там нет типов.


А кто гарантирует, что в ИО через другие функции не будет?

Разработчик языка, как и в хаскеле.


Мне проще о модулях рассуждать в терминах типов (и вам, возможно, тоже, вы ж тапл читали).

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

UFO just landed and posted this here
Ну камон, терм — это то, что справа от знака равенства в лет-байндинге, а не слева.

Все константы являются термами. И, кстати, если y = f(x), то замена терма y на терм f(x) в каком-то выражении (офк, когда все ок со связыванием) не обязательно будет всегда корректна.


Ну и хорошо, потому что IO — unescapable-монада, и такого типа в общем случае и нет.

Нет в хаскеле. Потому что система типов хаскеля — слабая.


ЧТД, система типов нас снова спасает!

Всмысле, как спасает? Еще раз — мы спокойно может писать некорректный код с unsafePerformIO и компилятор ничего с этим не делает. В том время как он должен зарезать некорректный код, оставляя корректный.


А кто сказал, что для unsafePerformIO вообще такой тип существует с той семантикой IO и unsafePerformIO, которую мы имеем сегодня?

Ну вот вы пишите бинд через unsafePerformIO и все прекрасно работает.
Еще раз — unsafePerformIO, семантически, это корректная ф-я. В ней нет ничего плохого, т.к. вы можете писать с ней корректный, осмысленный код.


Я не вижу проблемы. То, что недоказуемо безопасный терм (и, вернее, доказуемо опасный) нетипизируем — не проблема, а хорошее свойство системы типов, на мой взгляд.

Так терм доказуемо безопасный, об этом речь как раз.


Ну и потому, что гарантии рантайм-поведения мне так же важны, как само рантайм-поведение.

Да, но ИО тут при чем?


Тем, что в хаскеле я могу посмотреть на сигнатуру функции и сделать вывод, что её передача в unsafePerformMagic ни к каким IO-эффектам не приведёт.

Нет, не можете. Тайпчекер хаскеля не гарантирует вам, что внутри вашей ф-и с хорошим типом (без ИО), где-то внутри не вызывается unsafePerformIO. Знать тип для этой гарантии недостаточно. Более того — знание типа для этой гарантии не является и необходимым. Иными словами — гарантии ИО в хаскеле не зависят от знания вами типа ф-и.


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

Именно так.


У меня есть цель посмотреть на функцию и сказать, если там IO/логи/nullable-семантика, или нет.

Ну вот как вы в хаскеле это делаете? Видите ф-ю, смотрите на ее реализацию. Смотрите на реализации используемых в ней ф-й (и так далее рекурсивно). Если нигде не встречается unsafePerformIO — значит, все ок.
Как вы делаете это в pure python? Смотрите на ф-ю, смотрите ее реализацию. Смотрите реализации используемых ф-й. если нигде не встречается unsafePErformIO — значит, все ок.
Разница-то в чем?
И непонятно, при чем тут тип. Вам же код надо смотреть, а не тип.


Выше я описал, зачем там на самом деле типы

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

UFO just landed and posted this here
Так это не константа

Константа, конечно. С-но все "внешние байндинги" — это константы и есть. В частности, это константа может иметь значение, которое в самом языке вообще писаться не будет.


Ну так откажитесь уже от магии, наконец, и включите -XSafe.

Так сейф не режет некорректный код, она просто запрещает применение unsafePerformIO.


Это, понимаете, разница как между тайп-сейф доступом по индексу и полным запретом доступа по индексу. В первом случае у вас зависимые типы, а во втором — тот же условный питон, в котором просто нету доступа по индексу.


Это верно для любой функции, даже unsafeCoerce: a -> b

С unsafeCoerce не получится.


Да, бывают контексты, в которых она корректна, но и сложение числа со строкой бывает корректно (если строка пустая).

И что должно получиться при сложении числа со строкой?


Какой и где?

unsafePerformIO. В реализации бинда, например.


Гарантирует, если вы тайпчекер не обманываете магией.

Если вы не обманываете Pure Python, то он тоже гарантирует. Вы можете объяснить, в чем разница в гарантиях хаскеля и Pure Python?


Достаточно посмотреть на выведенную аннотацию модуля, safe он или не safe.

Отлично, в Pure Python все то же самое. В какой момент разница появляется?

UFO just landed and posted this here
Это внешнее по отношению к тайпчекеру.

Именно так. Важно лишь, чтобы у терма был корректный тип, который бы не ломал нам все.


Технически она запрещает импорт. Но это даже неважно: я включил -XSafe, и всё, с этого момента сигнатуры функций мне (и тайпчекеру) не врут. Вообще. Никогда.

Ну отлично, в Pure Python все будет то же самое — запрещаете импорт unsafePerformMagic и гарантированно никто из ИО никогда и никак не вылезет.


Прекрасная аналогия с завтипами. Почему даже в этих языках есть костыли, в чём-то похожие на unsafePerformIO?

Почему?


абсолютно семантически корректная программа

Да? Разве хаскель вам гарантирует, что на любой возможной реализации эта программа отработает с одним и тем же результатом?


Можно пример?

Ну реализация бинда для ИО-монады в грязных языках так делается же. bind f x = f $ unsafePerformIO x


Поскольку на выходе ИО одно, то все вызовы развернутся в одну цепочку и это гарантирует корректность (вы ваши World* сольете из начального в конечный через непрерывную последовательность операций).


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

Эм, нет, обмануть Pure Python вы можете тоже только через unsafePerformMagic. Просто потому что никаких других способов запустить ИО у вас нет, какие абстракции вы там не пытайтесь накручивать.

Гарантии есть. Их предоставляет компилятор. Чтобы ему помочь нужно приложить некоторые усилия. Стоит расслабиться и ваша программа превращается в одно большое IO, в дымке которого растворяются все парадигмы. Чтобы этого не происходило нужно потрудиться и разобраться.

Гарантии есть. Их предоставляет компилятор.

Какие гарантии ИО предоставляет вам компилятор хаскеля, но при этом не предоставляет компилятор питона (допустим, я в питоне написал bind-io, return-io и переписал все библиотечные ф-и так, что они возвращают io вместо того, чтобы сразу отрабатывать)?

Допустим я разогнался и врезался на байке в стену на скорости 200 километров в час. Тогда, действительно, никаких гарантий производитель шлема не даёт.
Повторюсь, нужно сделать усилие, и не пихать IO туда где без него можно обойтись. Тогда по сигнатуре функции будет видно что она делает и что ничего другого она сделать не может.

UFO just landed and posted this here
Допустим я разогнался и врезался на байке в стену на скорости 200 километров в час. Тогда, действительно, никаких гарантий производитель шлема не даёт.

Если не разгоняться, ехать по правилам и все такое — то и в питоне ничего плохого не случится.
Это тогда не гарантии, а фигня какая-то, извините.

Прелесть монад (и управления эффектами с их помощью) не в том, что если у меня функция живёт в какой-нибудь MonadWriter LogTy или State Foo, то я знаю, что она может писать логи типа LogTy или ковырять состояние типа Foo. Прелесть в том, что если она в этих монадах не живёт, то логи она точно не пишет и состояние не ковыряет. Чистое ФП нужно для того, чтобы функциям ничего лишнего не разрешать, а монады в нём позволяют разрешать только то, что нужно.
Да, я понял что вы имели ввиду когда говорили об ограничениях эффектов только теми местами, где они нужны.
К слову о шайтанах, вот.
Интересно, спасибо.
За вот это вот «монады − это просто, как раздватри» и последующую стену текста на эльфийском языке. «Ты с кем разговариваешь, папа?», хабр эдишен.

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

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

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

Я ничего не имею против математического языка, но на Хабр захожу, чтобы получить объяснение чего-то обычным языком, in layman's terms. Видимо, я не одинок.
А «нас» — это кого? К какой категории (извиняюсь за каламбур) вы меня отнесли?
К программистам-функциональщикам, конечно. А вы про кого подумали?
Про что-то более конкретное, типа хаскелистов или даже теоретиков категорий. Ни к тем, ни к другим, себя не отношу :)
Увы, я не умею конкретизировать в эту сторону. Я подумал, мало ли, может, вас отсылка к еврейскому анекдоту насторожила.
UFO just landed and posted this here
Обычный «нормальный» функтор F переводит морфизмы (a -> b) в (F a -> F b). Используя «однобокий правый» функтор K для перевода из морфизмов (a -> b) в (a -> K b) можно построить категорию Клейсли. Что получается при использовании «однобокого левого» функтора U из (a -> b) в (U a -> b)?
:)

Получится комонада, если получится, так как простой функториальности недостаточно. Вообще двойственность в категориях иногда играет злую шутку в стиле "комонада она такая же как монада, только ко".

Интересно кстати, что пустой тип void и пустой кортеж разделены. Никогда не задумывался о том что это разные сущности, наоборот казалось что пустой кортеж и void это одно и то же:)

Если вы про void, который в С-подобных языках, то это действительно то же самое, что и пустой кортеж. Т.е. то, что используется, если нам не важен результат:


void main(void) {..}

main :: IO ()

К сожалению, такая вот путаница с названиями.

Имхо, нет, void это не пустой кортеж, это пустой тип.

() — это тип, в котором есть ровно одно значение, называемое (). Например, можно написать return () или даже

x :: ()
x = ()


void — это тип, в котором нет ни одного значения. Нельзя написать return void; или void variable = void;.

В стандартной библиотеке хаскеля пустого типа нет; он есть в пакете void и называется Void :)
Если говорить о типах в Haskell, то, конечно, `Void` и `()` — совершенно разные вещи.
Если сравнивать пустой кортеж и `Void` в Haskell с тем, что обозначается словом `void` в С, то **сишный** `void` — аналог пустого кортежа в Haskell, но не аналог ненаселённого типа `Void`.
В книжке, на которую я ссылаюсь, это тоже есть: bartoszmilewski.com/2014/11/24/types-and-functions
UFO just landed and posted this here
и тем не менее в тех же плюсах
template [class F] // парсер — туды его в качель
auto g(F f) { return f(); }
void f() {}
g(f);
работает. return void в полный рост.
UFO just landed and posted this here
Блин, прочитал
вы написать, кстати, можете
как «вы написать, кстати, не можете.» сорри
Подумал тут, а можно ли сказать что аналогом Хаскелевскому void'у будет спецификация 'noreturn', которая обозначает что из функции вообще никогда не будет возврата? Например функция всегда бросает исключение или там принудительный выход из программы?
UFO just landed and posted this here

Константная стрелка она из терминального объекта, который не воид а юнит.

UFO just landed and posted this here

Вызвать получится если передать в качестве аргумента undefined.
То что юнит терминальный объект гарантирует что из любого объекта в него одна стрелка, про стрелки из него ничего не сказано.

UFO just landed and posted this here

Дико извиняюсь, перепутал константу и константную функцию.

«Монады с точки зрения программистов»… имхо, проще надо быть :) монада — это интерфейс из двух методов. И некоторый контракт на то, как эти методы должны работать вместе (ничего удивительного, вон см контракт между equals и hashCode). И больше ничего. Ну, синтаксический сахар с этим интерфейсом работает (do-нотация), к этому не привыкать (см. в яве цикл for и интерфейс Iterable).

В принципе, вся «наука» проектирования ПО (в технической части) — это про изобретение абстракций (наборов интерфейсов и их взаимодействия друг с другом). Интерфейсы бывают похуже и получше. Одна из метрик «хорошести» интерфейса — это «ортогональность», т.е. возможность при помощи малого количества простых методов выразить большое количество всяких сложных вещей (как из трёх координатных векторов можно всё трёхмерное пространство сделать). Другая / похожая метрика — composability, т.е. возможность комбинировать с большим количеством других интерфейсов множеством разных способов. Ну и есть метрика «универсальности» или «абстрактности» — насколько большое количество разных вещей могут имплементировать этот интерфейс.

Математики профессионально и целенаправленно занимаются изобретением абстракций и компоновкой из них других абстракций уже больше ста лет (если считать, например, от Гильберта; а до этого занимались тем же, но не так профессионально и целенаправленно). Они наизобретали много «хороших», т.е. «ортогональных», «композабельных» и «универсальных» абстракций — множества, функции, группы, категории… Так как эти вещи по построению абстрактны / универсальны, ничего удивительного нет в том, что множество сущностей в программировании имплементирует эти интерфейсы.

Программисты занимаются построением абстракций… ну, скажем, с 50х. Товарищи инженеры из «банды четырёх» обобщили кучу инженерного опыта за пару десятилетий и выписали десяток распространённых, «хороших» абстракций. Многие из этих абстракций являются велосипедами — математики их изобрели на полвека раньше.

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

Ну и см. «You might have invented monads» (есть перевод на хабре — habr.com/ru/post/96421).
UFO just landed and posted this here
Я честно старался… Интуитивно до этого понимал, что такое монада, но когда пытался сформулировать словами, впадал в ступор. Увидел статью и решил, что сейчас-то доберусь до истины. Но на fish операторе поплыл. Тем не менее, спасибо за труд.
Ну, это не самая простая статья на тему, реально. Были тут и попроще попытки. В картинках :)
Мне помогла такая ассоциация: «Монады — это программируемые точки с запятой». Вот у нас есть монада try… catch. В обычном случае точка с запятой вызывает переход к следующей команде. А в try..catch не всегда — только если предыдущий не бросил исключение. Появилось нетривиальное ПРАВИЛО исполнения последовательных операций.

Вот штука, говорящая что точки с запятой в некоей зоне будут вести себя как-то по-другому, и правило, говорящее — КАК ИМЕННО они себя будут вести, и есть монада.

Мне всегда интересно было, почему, например, Монаду нельзя описать как некую хрень с сайдэффектом (нечто, что, например, пишет инфу в базу, в поток) или как некую защитную обертку над другими типами (Maybe вообще шикарно соотвествует Nullable из какого-нибудь C#). Почему обязательно пытаться притягивать категории, функторы? Я понимаю, что высокая наука требует применения спецтерминов, чтобы доказать свою научность. Но инженерная практика работает то с чем-то более осязаемым...


И кстати, я случаем не пропустил в статье сказочное утверждение, что в хаскеле попав в монаду, из нее нельзя выйти?

> Почему обязательно пытаться притягивать категории, функторы?
Я думаю, в основном потому, что авторы хотят показать, откуда эта абстракция исторически возникла. С другой стороны, это не вредно: категории и функторы сами по себе являются полезными абстракциями, имеющими множество имплементаций.
Представьте, что вы рассказываете про паттерн «абстрактная фабрика» студенту, который ещё ни разу не сталкивался с проблемами, которые этот паттерн решает: студент будет удивляться, зачем такая сложная штука нужна. А вот если вы будете это рассказывать программисту, который такие проблемы решал во множестве, просто по какой-то причине не знал, что этот паттерн так называется — он скажет «а, понятно, ок».

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

> в хаскеле попав в монаду, из нее нельзя выйти?
Это не является универсальным свойством монад. Из монады IO «выйти» нельзя (и то, есть всякие unsafePerformIO, но это скорее костыли); из монады State «выйти» можно стандартной функцией runState.
Я думаю, в основном потому, что авторы хотят показать, откуда эта абстракция исторически возникла. С другой стороны, это не вредно: категории и функторы сами по себе являются полезными абстракциями, имеющими множество имплементаций.

У меня ни разу не возникло возражений при подобном описании монад в учебниках по Хаскелю, написаных скорее математиками для математиков, чем инженерами для инженеров (разве что только один раз видел исключение у Шевченко (https://www.ohaskell.guide)). Но когда в заголовке вижу "Монады с точки зрения программистов" ожидаю все-таки инженерный подход. Sorry.


Представьте, что вы рассказываете про паттерн «абстрактная фабрика» студенту, который ещё ни разу не сталкивался с проблемами, которые этот паттерн решает: студент будет удивляться, зачем такая сложная штука нужна.

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

Да Maybe = Nullable. Но монада — это не maybe. Наоборот maybe — это монада.
Для шарпщика проще объяснить через линк. Пусть у нас есть дженерик тип X[T] (например IEnumerable или Nullable или Task.
реализуем для всех типов экстеншен метод SelectMany. теперь мы можем писать:
var result =
from x in X() // внезапно query syntax — это аналог do натации в хаскеле
from y in Y()
select x + y;
а проблема в том что мы не можем абстрагировать этот код от типов IEnumerable/Task/Nullable. В хаскеле можно, поэтому там дин и тот же код работает и со списками и с асинхронными знаниями и тд. в шарпе для тасков у нас будет своя функция Sum(), а для нулаблов своя.
UFO just landed and posted this here
Мне всегда интересно было, почему, например, Монаду нельзя описать как некую хрень с сайдэффектом или как некую защитную обертку над другими типами.
Почему — как раз можно, и это наиболее понятный и утилитарно-обоснованный способ.
Почему обязательно пытаться притягивать категории, функторы?
Это просто хаскеллисты притягивают, поскольку многие концепции и подходы к структурированию задач в Хаскеле могут быть описаны в рамках теории категорий, т.е. математически. А это очень подкупает.
И кстати, я случаем не пропустил в статье сказочное утверждение, что в хаскеле попав в монаду, из нее нельзя выйти?
Например из монады IO выйти нельзя — «распаковать» значение без доступного конструктора или спецфункции компилятора не получится.
Правильная ссылка на книжку здесь. А то версия 1.2.0 уже устарела…
А зачем все это? Зачем так все усложнять? Функциональное программирование хорошо в меру. Зачем из него делать целый язык?
Мне Haskell переусложненным не кажется, он просто функциональный и все. Уложить его в голову требует некоторого ментального усилия(а у людей с математической подготовкой и не требует), но далее все просто и понятно. Мне кажется переусложненным например scala, где функционального в меру, процедурного в меру, ООП в меру, присутствуют и наследование и композиция и символические преобразования и последовательные вычисления, с миру по нитке. Это все дается ценой запутанного синтаксиса и невнятной идиоматики. То же Rust — то ли Haskell для бедных, то ли переHaskell. Haskell на мой взгляд яснее чем мультипарадигменные гибриды, проще понять что ты пишешь и прочитать написанное другими.
Такое впечатление, что Haskell придумали специально, чтобы с большим трудом решать простые задачи. Язык не надо укладывать в голову. Есть три задачи языка программирования: 1) быстро написать правильную программу 2) легко прочитать программу 3) быстро изменить программу не добавляя ошибок. Другие языки гораздо лучше для этих целей.
UFO just landed and posted this here
То, что по третьему пункту хаскель сияет — это широко распространенное заблуждение. Ключевое слово там — быстро.
UFO just landed and posted this here
UFO just landed and posted this here

После семи лет ежедневной и плотной работы в Wolfram Mathematica, мне её показалось мало и в моём инструментарии появился Lisp. Ещё через пять лет мне стало тесно и в нём, так я пришёл к Хаскелю, Axiom, Agda, поскольку в них математическая мысль формулируется точнее и строже. И всё это прекрасно сочетается в работе и не мешает писать для развлечения на JavaScript :) Или вы предлагаете отменить все прочие языки, оставив лишь те, что нравятся вам?

Нет, конечно. Вам нравится одно, мне — другое. Мы обменялись мнением. Никто ничего не предлагал отменять. Просто я уверен, что все о чем написано в этой статье можно сделать намного проще в других языках.

Не всё. Монады не про ввод/вывод, а про композицию в контексте. Одним из контекстов может быть "внешний мир", но их гораздо больше: неоднозначные вычисления, вероятностные вычисления, параллельность и корутины… При этом синтаксически одна программа может выаолняться в разных контекстах. Это как ортогональные криволинейные координаты: один раз выразил дифференциальные операторы в терминах чисел Ламе и получаешь один и тот же дифур, но в разных контестах.

Зачем выращивать рис, когда есть макароны? Зачем снимать кино, когда есть книги? Зачем переусложнять и говорить по-китайски, когда есть понятный и простой румынский? Ответ прост: потому что можно. Так развивается человечество.

Да, человечество развивается именно так: попробовал, признал негодным, забыл

Или так: попробовал, понял область применения и применяешь, если нужно. Многим нужно.

Спасибо, давно скачано на волне интереса, но руки/глаза не доходят прочитать.
В превью на гитхабе мат. символы рендерятся в иероглифы (скачанный вариант ок)
А вы бы не могли улучшить русскую статью вики по теории категорий? Какими проблемами вызвано появление теории и решила ли она их? Интересные результаты? Из статьи как-то получается что это теория без теорем.

П.С. В английской написано " the goal of understanding the processes that preserve mathematical structure" и я этого не могу понять однозначно.
Вики мне улучшать не хочется, но теория категорий в первую очередь задумывалась как новый язык математики вместо теоретико-множественного. Маклейн ведь учился логике в Германии у Гильберта. На категорном языке удобно давать общематематические определения (типа «декартово произведение»). Некоторые разделы математики без этого языка невозможны (алгебраическая геометрия и топология в первую очередь). Интересные примеры тоже возникли, в первую очередь это топосы (категории, похожие на категорию множеств, но с неклассической логикой). Неожиданно оказалось, что они естественно возникают везде. Я в учебнике на этих примерах всё и объясняю.
В каком смысле алгебраическая геометрия невозможна без «теории категорий»? Помню на хабре была статья про элиптические уравнения и доказательство Вайлса ВТФ в пересказе «перечень шагов» я смотрел — не было там ни термнинов теории категорий (функторов, композиций) ни алгебраической записи характерной для нее. Что я упускаю?
За доказательство Вайлса не ручаюсь (не моя область), а вообще алгебраическая геометрия — это пучки, а пучки это функторы с определёнными свойствами. Диаграммы там рабочий язык.

Категория из одного объекта не обязана иметь только морфизм id .

Так это нигде и не утверждается… Если тот же тип Bool взять как единственный объект и функции Bool -> Bool как морфизмы, то их 4 штуки показано. Мне просто не хотелось для каждой стрелочку рисовать, чтобы рисунок не загромождать.

В вашем тексте есть утверждение, что функтор в категорию из одного объекта переводит все морфизмы в id. В общем случае это неверно.

А, поняла, про что вы. Там имеется в виду, что результирующая категория, взятая как пример крайнего случая, состоит из одного объекта и одного морфизма. Т.е. это не происходит потому, что функтор так действует, а потому что мы изначально такую захотели рассматривать. Попробую переформулировать чётче эту часть, спасибо.

Не за что. Пишите ещё. Например про комонады или про монадические трансформеры.

UFO just landed and posted this here
Я страшно далек от функционального программирования. Я хоть убейте, не могу понять, как в функциональном стили написать простую программу.
Программа выводит на консоль «Напечатайте фразу»
И ждет вода пользователя
Пользователь вводит что-то
Программ после ввода просит повторить ввод
И если повторный ввод совпадает, программ в консоль выводит «Ok»
Если нет, то «Ошибка»
Тут явно нужно как-то хранить состояния ввода и делать сравнения

Можно так:


io1 = do
  putStrLn "введите текст"
  x <- getLine
  putStrLn "повторите ввод"
  y <- getLine
  if x == y
    then putStrLn "Ok"
    else putStrLn "Ошибка"

Как видите, программа вполне соответствует тому, что она делает (хоть и монады).
Но если бизнес-логика становится сложнее, её лучше выделить в чистую функцию, а ввод-вывод скомпоновать отдельным блоком. Например, как-то так:


io2 = process
      <$> (putStrLn "введите текст" >> getLine)
      <*> (putStrLn "повторите ввод" >> getLine)
      >>= putStrLn
  where
    process x y = if x == y then "Ok" else "Ошибка"
Спасибо! Тут для меня две магические функции putStrLn, getLine. За которыми скрыто очень много. Например, за getLine скрыта обработка прерываний от клавиатуры. За putStrLn в общем-то глобальное хранение состояния видео памяти.
Для меня одним из декларируемых свойств функционального программирование — это то, что выполнение функции не зависит от состояния. А тут явная зависимость вывода функции getLine от состояния вычислительной машины. Она же может никогда не отработать. Функция всегда должна при одинаковых параметрах возвращать одинаковый результат. А тут getLine всегда получает пустой параметр, а возвращает разные результаты.
Для меня, вот эти две функции, выглядят как костыли в языке, Haskell. Которые возможны только по воле создателя языка. А вот если абстрагироваться от реализации? А оперировать абстрактной вычислительной машиной.
Или в общем-то мы все равно придем к глобальному хранению состояний машины? Или программа на функциональном языке — это всегда функция? В чистом виде, без костылей. И при вызове программы мы всегда должны передать ей входные параметры, а она при одинаковых входных параметрах всегда должна возвращать одинаковый результат.
Типа $ plus 1 1
2
И в чистом виде, она никогда не должна вернуть, например 3. Хотя с функцией getLine, такое сделать можно в легкую.
То есть вопрос не про Haskell, а про философию функционального программирования.

Можно подходить к этому так. Программа с IO это чистая функция — рецепт того, что делать при конкретных входах. После чего мы подаём ей на вход разные варианты внешнего мира так же, как в интерпретаторе функции plus подаём разные числа. При этом, концептуально, одинаковым мирам всегда соответствует одинаковый ответ. getLine, в мире, в котором клавиатура всегда возвращает "123", всегда вернёт "123". миров много, но и у функции plus возможно много входных параметров.
Не воспринимайте IO-функции как нечто инородное в чистом ФП, а монады требуются не для обеспечения чистоты, с этим проблем нет, а для обеспечения порядка вычислений в ленивом языке.

UFO just landed and posted this here

Абсолютно согласен. Мне хотелось показать, что ничего "магического", "неправильного" или чуждого идее ФП в функциях типа getLine нет. И вообще, разницу между императивным и функциональным подходом хотелось бы демистифицировать. Можно писать чистые функции на С, и даже выгоды от этого получать (в FORTRAN есть ключевое слово pure, помогающее компилятору трансляции чистых функций), но это акт доброй воли. Можно в Haskell запихать всё в IO, но компилятор из помощника превратится в послушного толмача. А можно балдеть от оптимизаций gcc и ghc, подавая им правильно приготовленные программы, расширяя горизонты. Кайф!

Сергей, а можно вас также попросить представить пример тоже простейшей программы на Хаскел эквивалентной следующей на С:
#include <stdio.h>

#define N 10000u

const char* compare(unsigned x, unsigned y)
{
  static const char LT[] = "LT";
  static const char EQ[] = "EQ";
  static const char GT[] = "GT";
  return (x < y ? LT : (x > y ? GT : EQ));
}

int main()
{
  for (unsigned x = 1; x <= N; ++x)
    for (unsigned y = 1; y <= N; ++y)
      printf ("%u <%s> %u\n", x, compare(x ,y), y);
}
  
именно «отделив бизнес-логику в чистую функцию».

Хороший пример, спасибо!
Идиоматичное решение здесь может быть таким: создадим чистую полиморфную функцию, которая соответствует вложенному циклу:


mkComps :: PrintfType a => Int -> [a]
mkComps n = [printf "%u <%s> %u\n" x (show (compare x y)) y
            | x <- [0..n]
            , y <- [0..n]] 

где compare :: Ord a => a -> a -> Ordering — это чистая библиотечная функция, соответствующая вашей.


Эту функцию можно вызвать в чистом контексте:


> concat (mkComps 3) :: String
"0 <EQ> 0\n0 <LT> 1\n0 <LT> 2\n0 <LT> 3\n1 <GT> 0\n1 <EQ> 1\n1 <LT> 2\n1 <LT> 3\n2 <GT> 0\n2 <GT> 1\n2 <EQ> 2\n2 <LT> 3\n3 <GT> 0\n3 <GT> 1\n3 <GT> 2\n3 <EQ> 3\n"

или в IO


main = sequence_ (mkComps 3)

> main
0 <EQ> 0
0 <LT> 1
0 <LT> 2
0 <LT> 3
1 <GT> 0
1 <EQ> 1
1 <LT> 2
1 <LT> 3
2 <GT> 0
2 <GT> 1
2 <EQ> 2
2 <LT> 3
3 <GT> 0
3 <GT> 1
3 <GT> 2
3 <EQ> 3

это был вывод в консоль.


Здесь mkComps выполняет всю работу чистым образом, "производя" данные, а в main они превращается в вывод на печать. Благодаря параметрическому полиморфизму функция printf может возвращать как "чистую" строку, так и побочное действие. А благодаря ленивости языка никакого списка в памяти при этом не создаётся, хотя выглядит он подозрительно. На моём ноутбуке откомпилированный бинарник отправил в /dev/null 10000*10000 записей за 10 секунд.

Здорово. Мой вариант для задачи, сведённой до этого упрощенного примера был таким:
mkOut :: Int -> [String]
mkOut n = [ shows x $
            showString " <" $
            shows (compare x y) $
            showString "> " $
            shows y "\n"
          | x <- xy, y <- xy ]
  where xy = [1..n]

main = mapM_ putStr $ mkOut 10000
И он меня очень не порадовал — память заканчивается, система начинает подвисать.

Такая версия — сделать список из IO-действий — не является по сути отделением логики от операции вывода:
mkOut :: Int -> [IO ()]
mkOut n = [ putStr $
            shows x $
            showString " <" $
            shows (compare x y) $
            showString "> " $
            shows y "\n"
          | x <- xy, y <- xy ]
  where xy = [1..n]

main = sequence_ $ mkOut 10000

Насколько я понял решение можно сделать таким, используя полиморфную функцию-префикс:
{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE FlexibleInstances #-}

class Out t where  out :: String -> t

instance Out String where  out = id
instance Out (IO ()) where  out = putStr

mkOut :: Out t => Int -> [t]
mkOut n = [ out $
            shows x $
            showString " <" $
            shows (compare x y) $
            showString "> " $
            shows y "\n"
          | x <- xy, y <- xy ]
  where xy = [1..n]

test :: String
test = concat $ mkOut 10000

main :: IO ()
main = sequence_ $ (mkOut 10000 :: [IO ()])
(Какие-то нюансы не знаю, что мешают, чтобы не нужно было специфицировать тип результата mkOut при вызове.)
Я правильно понял с инженерной точки зрения? Что монада нужна для того, чтобы чисто функциональный язык, типа Haskell мог взаимодействовать с машиной, которая управляется через ее состояния. В языках, которые построены на управлении состояниями, и где внедряются и рекламируются функциональный подход (C#, JavaScrip, python и т.д.), большой необходимости в монадах нет. Там прозрачней использовать родные средства, а функциональный подход использовать для организации логики на чистых функциях.

Думаю, вполне правильно. Для управления состояниями строгие и императивные языки в монадах не нуждаются. Но монады и в этих языках позволяют организовывать поток вычислений с весьма изощрённой логикой. Когда-то я определил для себя, что монады позволяют мне перегрузить операторы := и ; определив в них свою семантику. Это случилось во времена Лиспа — не чистого и строгого языка.

Некоторые функциональщики как собаки: глаза умненькие, понимающие, а объяснить не могут (но задорно пытаются).
Это черта многих математиков в целом.

Грубовато вышло… вызывает желание ответить тем же.

«Первоначально он отнёсся ко мне неприязненно и даже оскорблял меня, то есть думал, что оскорбляет, называя меня собакой, — тут арестант усмехнулся, — я лично не вижу ничего дурного в этом звере, чтобы обижаться на это слово…»
Ну не знаю, я люблю собак.
ИМХО, основная беда теории категорий не в сложности, а в разболтанности и неоднозначности её языка. Тебе кажется, что ты легко воспринял вводную, но не обратил внимания на нюанс, который выстрелит в ногу сильно позже.
Категории очень легко и естественно визуализируются как ориентированные графы. В принципе, любой ориентированный граф можно достроить до категории, добавив композиции морфизмов и тождественные морфизмы, если необходимо.
«если необходимо»? То есть, можно назначать композиции уже имеющимся морфизмам? (Иногда можно) А вот попробуйте решить задачку, которую я сам себе в своё время придумал, а решая просветлился.
Постройте для произвольного графа категорию, где стрелки — всевозможные маршруты в данном графе без повторяющихся вершин, то есть почти свободная категория, но со стянутыми в id циклами, всё просто, riiight?
Внезапно, такие приколы есть и в «строгом» хаскелле, за примером далеко ходить не надо:
Существует целый класс типов, который так и называется, Functor.
Эта фраза меня просто убивает, именно потому что так говорят все, а это неверно. Тип не может быть функтором, функтором может быть конструктор типа. [a] — не тип, тип — [Int]. Это дико мешает начинающему воспринять концепцию параметрических типов, ибо тех поначалу за одинаковым синтаксисом не замечаешь. В С++ тут будет шаблон и его, по крайней мере, сразу видно.

Тяжёлое наследие сортов. [a] это тип, просто сорт у него не , а ->*.

Тип это то, что принадлежит категории Hask, то, экземпляр чего можно создать, (кроме Void).
UFO just landed and posted this here
Заголовок статьи установил контекст «с точки зрения программистов»

Компилятор считает по-другому. Для совмещения теории с практикой нужна формулировка что объекты категории Hask это типы сорта *.

UFO just landed and posted this here
UFO just landed and posted this here
Я имел в виду схожесть
class Functor f
и
class Show a
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
2 вывода по статье.
* Автор явно не Фейнман. На кого рассчитана статья не понятно.
* Отличный пример, что использовать монады можно понимая ничего в теоркате, а значит и углубляться в него нужно только если действительно хочется.

Монады может быть, но есть ещё профункторы, естественные преобразования, комонады,T-алгебры и коалгебры, монадические трансформеры, линзы…
Можно интегралы считать как площадь под графиком трафаретом, по клеточкам, и ныть что высшая математика это сложно и никому не нужно, потому что есть трафарет а самые частые случаи уже посчитали (это я про паттерны проектирования).

Ничего из этого не требует теорката для использования (про трансформеры вообще странное упоминание). Это выводилось с помощью теорката и можно так понять базис и более глубокую интуицую получить, но это по желанию. И теоркат это сложно. То что большинство в лучшем случае почитали/посмотрели Бартоша не делает их знающими теоркат.

Чтобы мосты строить тоже снипы есть, но, почему-то, строители учат и математический анализ и механику и сопротивление

«И математика, и физика — экспериментальные науки» академик Арнольд. www.mccme.ru/edu/index.php?ikey=viarn_burbaki теория категорий, теория множеств не основы математики, не язык математики, а просто лабаротория для умтсвенного поиска особого рода. Для вычисления интегралов другие эксперименты в другой лабартории нужны, и практически, другие люди.

Оставьте суждения о математике математикам.

А какие вопросы к Арнольду? Что касается меня — то два образования одно из них университетский бакалавр в прикладной математике хотя математиком не стал, да.
UFO just landed and posted this here
Бесконечное количество аксиоматик и утверждений. А практически, я бы лично хотел чтобы дифуры и интегральные уравнения и другая аналитика шли с первого курса (у меня было не так). Не все интересно.
UFO just landed and posted this here
Тогда мне было это интересней, чем передоказательство всего начиная с теории множетсв. Приведенную цитату Арнольда, я понимаю и так «в математике можно работать и с интуитивными понятиями (а на практике и с утверждениями в которые „веришь“ — они были доказаны когда-то, но сам ты это не проверял) потому что в итоге результаты просто работают на практике». И еще в итоге, мне кажется, все равно все всё «в основах» забывают — ресурс головы не безграничен — а для практика ценится будет умение посторить модель. Так лучше уж сразу и держаться моделей.

Я думал это я тугодум, но уже на магитсратуре софтинженерии — пришел на кафедру прикладников — там по каким-то дням решили публично передоказать что-то модное, прозвучавшее (уже не помню что) и раздавали доцентам по страничке подготовить перед студентами и профессурой (многодневное доказательство, странчек на 20-40, но в конкретный день — по 2-3 проходили). Это было мучительно. Каждое утверждение куда-то проваливалось и это было бесконечным углублением. И я понял почему передоказывают вообще «по кафедрам» так мало — ну никому не надо, не интересно, как использовать не ясно, а работа тяжелая. Так вот я вполне могу сказать верю я ваши бурбакизмы и быстрей вернуться к своим моделям.
UFO just landed and posted this here
урматы

Можно, для тех, у кого математика была только два курса, расшифровать эту аббревиатуру? Чтобы знать, что гуглить.
уравнения математической физики.

Т.е. это просто математический инструментарий физика. При этом физика формально не особо-то колышет почему оно «работает», главное — как и где это применять.
UFO just landed and posted this here
Ну, да, поэтому наш курс во многом выродился в «для уравнений такого-то вида надо искать решения в такой-то форме».

Да ладно, это даже часто для курса диффуров верно, чо уж там про урматы. Да и "ищем в нужной форме" это ладно еще (и хорошая, кстати, задачка — получить тот же самый результат, но естественным образом, без подгонки), вот "из естественных физических соображений бадумц!" — вот это да! :)

UFO just landed and posted this here
Ну это всё вопрос интересов, как вы в самых первых пяти словах и написали.


И тут приходят профессора, всех строят под свои интересы, утверждают, вон как свидетельствует Арнольд: «ноль это положительное число… и вообще теория множеств — язык математики». А если им аккуратно сказать «я так не думаю»… влепляют минус в карму.
UFO just landed and posted this here
В западной математической школе ноль «по-умолчанию» включают в множество натуральных чисел. Это можно понять как «положительность нуля»
UFO just landed and posted this here

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

UFO just landed and posted this here

Арнольду из указанной ссылки принадлежит высказывание про экспериментальность.
В остальном, это древний холивар про нужна ли математика подставить_профессию_по_желанию.

Список — тоже монада.
concat :: [[a]] -> [a]
concat = join
Автору спасибо за попытку, но, к сожалению, все-таки не для программиста(= В отличие от этой статьи: после десятка подобных вашей наконец-то пришло понимание. А все потому, что ничего лишнего в объяснении — только код.

Есть хороший критерий для самопроверки. Вы должны потерять способность объяснять что такое монады.

А я попробую объяснить "для программистов", по-проще.


И так, начальные условия. Мы что-то там начитались про функциональщину и теперь взяли строгий принцип: только чистые функции, только хардкор!


Берём чрезвычайно программистский язык С (не волнуйтесь, кода не будет). В том числе и потому что там можно всё писать чисто на чистых функциях. Ну там main получает указатель на массив строк аргументов и выходит с интовым кодом возврата (т.е. это (**char -> int) ), а вся грязь только от нас.


Отлично. Пока полёт нормальный. Хотим написать утилитку, которая берёт из аргумента имя и выводит на экран "Привет, имярек!" (чуть более сложный Hello, world!).
Тут у нас случается "упс". В **char -> int ничего нет про экран. Т.е. хотим грязного! Как работать с грязью? Правильно — абстрагироваться.


И так есть экран, чьё состояние изменится, но мы хотим оставить наши руки чистыми. Мы говорим себе — нет экран на самом деле не изменится. Точнее, что он там себе изменится внутри — нас, чистюль, не колышет, для нас он как был так и останется сам собой — экраном. Точнее, сохраняет свой тип (а конкретное значение меняется).


Какие бы мы преобразования над экраном не делали, они дадут нам экран. Другой экран уже с начерканными там словесами, но экран. А раз так, то мы — чисты, мы ничего не меняли! Это как 2+2=4 сложить. Только мы берём экран, берём операцию написания слов, применяем, получаем другой экран. С точки зрения нашего кода и нашего абстрагирования от экрана — никаких побочных эффектов.(Чтоб программа main как функция была не тривиальной ещё возвращаем из неё 1, если экран вычислился какой-то ошибочный, и 0 если нет)


Как не сложно догадаться такое понимание экрана — и есть монада.


Т.е. мы все грязные изменения состояния превращаем в абстракции над вычислениями (ой это уже сложно зашло).

UFO just landed and posted this here
А зачем вектор в монаду засовывать? Там где гуляют монады — вектора имютабельны должны быть. Иначе — костыли городить. Нехорошо :)

С опшиналом так же как и с экраном. _Считаем_ внутренние состояния (в данном случае null — не null) просто за различные константные значения, изменения — операциями (блин, так и подмывает «функторы» и «морфизмы» писать, но я пока держусь).
Тем более там операции-то простые null с чем угодно даёт null, а что угодно с не null даёт туже операцию.

Моё описание имеет некоторую общность.

Вот монаду Promise так просто уже тяжелее будет объяснить в таких упрощённых терминах, потому и не буду.
UFO just landed and posted this here
Автору спасибо за попытку, но, к сожалению, все-таки не для программиста(= В отличие от этой статьи: после десятка подобных вашей наконец-то пришло понимание. А все потому, что ничего лишнего в объяснении — только код.

Первый пример — сильно обрезанный экземпляр монады Writer с единственной возможной функциональностью которую можно реализовать в данном случае — конкатенация вывода лог-сообщений. Третий пример (когда функции класса Employee могут возвращать None вместо значения) можно интерпретировать как монаду Maybe. Но как было уже сказано: "Монада — это не maybe. Наоборот maybe — это монада"

Во втором примере монады как таковой нет. Да, это effectful-вычисления, но ни в каком виде не монада. Что-то типа:
data Backtrace a = Backtrace {
                     getResult :: a,
                     getBacktrace :: [a]
                   }

class IdFunctor f where
  ($$) :: (a -> a) -> f a -> f a
  infixr 0 $$

instance IdFunctor Backtrace where
  ($$) f (Backtrace x b) = Backtrace (f x) (x : b)

withBacktrace :: a -> Backtrace a
withBacktrace x = Backtrace x []


f1 x = x + 1
f2 x = x + 2
f3 x = x + 3

-- обычное вычисление
result1 = f3 $ f2 $ f1 $ 0

-- вычисление с обратной трассировкой
resultWithBacktrace = f3 $$ f2 $$ f1 $$ withBacktrace 0

result2 = getResult resultWithBacktrace
backtrace = getBacktrace resultWithBacktrace
Sign up to leave a comment.

Articles