Да, именно так, через функторы разжёвано. Это ссылка на середину книги, её надо читать с начала.
А можно ничего не читать, можно просто пользоваться монадами как flatMap, большинство программистов так делают. Это менее интересно, но тоже достаточно продуктивно.
А что такое "тип с параметром"?
Тип данных, зависящий от другого типа данных. Например, во статически типизированных языках вектор сам по себе не тип данных, но вектор строк или вектор целых — это тип данных. Значит, вектор — это такая штука, которой нужно дать параметр, чтобы она стала настоящим типом. Мы называем это параметрическим типом.
Если хотите, могу дать примеры на вашем любимом языке. Можем продолжить беседу в личке.
А разве композиция (функций) не лежит в основе самой парадигмы ФП?
Да, а монадная композиция позволяет соединять не только обычные функции-вычисления, но и «обогащённые», то есть разнообразные действия, например, ввод-вывод.
Есть ещё много других видов композиции для разных видов сущностей.
Чем тогда монады отличаются от других "структур"?
Именно этим и отличается. Монады — это такие "структуры", которые можно связывать, выход одного со входом другого. Ну и там несколько математических требований к этому связыванию. Грубо говоря, оно должно вести себя предсказуемо.
можно ли все грязные побочные действия самому выполнять в виде вызовов сишных примитивов?
В Хаскеле по умолчанию нет примитивов, есть только много разных IO-операций в стандартной библиотеке.
Как тогда самому определить, какое действие является побочным? Можно ли по формальным признакам отделить монаду от чистого типа, например, от типа Int -> Int?
Да, это строгий термин с формальными признаками. Они хорошо описаны в документации и разжёваны в учебниках вроде этого.
Грубо говоря, это должен быть тип с параметром, должна быть функция для связывания, и связывание должно вести себя предсказуемо, по правилам.
Интересно, что тип чистой функции вроде Int -> Int (точнее, Int -> a) с формальной точки зрения тоже будет монадой, но это математический трюк, это знание не особо часто может пригодиться в работе.
Или взять вот такую штуку (по мотивам библиотеки wreq):
get :: String -> IO Response
Формально, это функция, принимающая строку (урл) и вычисляющая операцию ввода-вывода, которая, будучи исполнена, сходит в сеть по заданному урлу и принесёт какие-то данные.
Можно ровно то же самое сказать иными словами: это операция ввода-вывода (сходить в сеть и принести ответ), параметризованная строкой (урлом). Тоже формально.
Неформально на эту функцию с операцией можно посмотреть как на «функцию, обогащённую вводом-выводом», у которой на входе строка (урл), на выходе ответ (от сервера), а в побочных эффектах операции ввода-вывода (хождение по сети и возможное исключение, если не удастся достучаться до сервера).
А сами монады — это типы, объекты которых можно монадически соединять.
Например, в Хаскеле есть объект getLine :: IO String, кодирующий ввод строки из потока ввода. Это операция ввода-вывода, как можно предположить по типу IO. Изнутри языка нельзя посмотреть, что там внутри (если интересно, внутри там вызов сишных примитивов, но это не важно). Но можно передать эту операцию на исполнение вместе с какой-то функцией-продолжением, операция исполнится, и в вашу функцию придёт строка из потока ввода.
Или вот есть функция putStr :: String -> IO (), она сама ничего не печатает, она только по строке вычисляет операцию вывода. Эту операцию можно исполнить или передать кому-то, кто умеет принимать операции ввода-вывода.
Функциональное программирование требует отсутствия побочных эффектов (side effects)… побочные эффекты это то, ради чего мы программируем.
Вы, конечно, правы, но на самом деле всё наоборот.
Это популярное заблуждение, что в чистых языках или в ФП нет побочных эффектов.
В «грязных» языках побочные эффекты разрешены в любом месте и ими нельзя управлять. В чистых языках побочные эффекты живут только в специальных объектах-загончиках, только там, где они нужны, и ими можно управлять.
Если посмотреть на динамическую типизацию с точки зрения статической, можно считать все динамические типы одним статическим. Так что противоречия нет, можно внутри статически типизированной программы иметь динамический код.
В RON конфликты избегаются так же, как и в остальных CRDT — конфликтов просто нет.
Шучу, это непросто. Суть бесконфликтности в том, чтобы разрешить только такие операции, которые не вызывают конфликтов (при доставке в произвольном порядке). Придумывать такие структуры тяжело. Зато их можно легко комбинировать, вкладывать друг в друга, объединять в структуры.
Можно ли рассматривать сам репозиторий (только не git, а какой-нибудь патчевой СКВ, где конфликтное состояние является возможным состоянием) как CRDT относительно операции pull/push?
Гит можно, но только относительно fetch и fast-forward.
Функторы в C++ являются сокращением от «функциональные объекты».
Что-то я не вижу, как тут сокращение получилось.
Насколько я помню, всё было немного иначе.
До С++ функторы появились в С, и там это было сокращение-гармошка (portmanteau) от «FUNCtion poinTER» (с поправкой на e/o, но это хотя бы какой-то смысл имеет, в отличие от «object»). Потом в С++ это понятие расширили на любое значение, которое можно вызывать как функцию.
Всё отлично читается и по кусочкам. Я думаю, вы привыкли к Алгол-С-подобному синтаксису, а к ISWYM-подобному ещё не привыкли. Если поработаете с Хаскелем некоторое время, научитесь и ему.
Правильно, лучше переводить деньги через закрытый Свифт. Нет денег — нет проблем с легализацией.
Да, именно так, через функторы разжёвано. Это ссылка на середину книги, её надо читать с начала.
А можно ничего не читать, можно просто пользоваться монадами как flatMap, большинство программистов так делают. Это менее интересно, но тоже достаточно продуктивно.
Тип данных, зависящий от другого типа данных. Например, во статически типизированных языках вектор сам по себе не тип данных, но вектор строк или вектор целых — это тип данных. Значит, вектор — это такая штука, которой нужно дать параметр, чтобы она стала настоящим типом. Мы называем это параметрическим типом.
Если хотите, могу дать примеры на вашем любимом языке. Можем продолжить беседу в личке.
Да, а монадная композиция позволяет соединять не только обычные функции-вычисления, но и «обогащённые», то есть разнообразные действия, например, ввод-вывод.
Есть ещё много других видов композиции для разных видов сущностей.
Именно этим и отличается. Монады — это такие "структуры", которые можно связывать, выход одного со входом другого. Ну и там несколько математических требований к этому связыванию. Грубо говоря, оно должно вести себя предсказуемо.
В Хаскеле по умолчанию нет примитивов, есть только много разных IO-операций в стандартной библиотеке.
Да, это строгий термин с формальными признаками. Они хорошо описаны в документации и разжёваны в учебниках вроде этого.
Грубо говоря, это должен быть тип с параметром, должна быть функция для связывания, и связывание должно вести себя предсказуемо, по правилам.
Интересно, что тип чистой функции вроде
Int -> Int
(точнее,Int -> a
) с формальной точки зрения тоже будет монадой, но это математический трюк, это знание не особо часто может пригодиться в работе.А читать QR из файла всё так же неудобно без второго телефона.
Непонятная база — это https://ru.wikipedia.org/wiki/Федеральная_информационная_адресная_система
Или взять вот такую штуку (по мотивам библиотеки wreq):
Формально, это функция, принимающая строку (урл) и вычисляющая операцию ввода-вывода, которая, будучи исполнена, сходит в сеть по заданному урлу и принесёт какие-то данные.
Можно ровно то же самое сказать иными словами: это операция ввода-вывода (сходить в сеть и принести ответ), параметризованная строкой (урлом). Тоже формально.
Неформально на эту функцию с операцией можно посмотреть как на «функцию, обогащённую вводом-выводом», у которой на входе строка (урл), на выходе ответ (от сервера), а в побочных эффектах операции ввода-вывода (хождение по сети и возможное исключение, если не удастся достучаться до сервера).
Здесь тип IO — монада, потому что можно операции соединять между собой, например,
при этом при соединении не производится ввод или вывод, а только вычисляется новая операция.
Принцип монад — это способ композиции.
А сами монады — это типы, объекты которых можно монадически соединять.
Например, в Хаскеле есть объект
getLine :: IO String
, кодирующий ввод строки из потока ввода. Это операция ввода-вывода, как можно предположить по типуIO
. Изнутри языка нельзя посмотреть, что там внутри (если интересно, внутри там вызов сишных примитивов, но это не важно). Но можно передать эту операцию на исполнение вместе с какой-то функцией-продолжением, операция исполнится, и в вашу функцию придёт строка из потока ввода.Или вот есть функция
putStr :: String -> IO ()
, она сама ничего не печатает, она только по строке вычисляет операцию вывода. Эту операцию можно исполнить или передать кому-то, кто умеет принимать операции ввода-вывода.Нет. Монадой может быть тип такого объекта (например, Writer). А может и не быть (например, ввод-вывод в старом Хаскеле был просто списком действий).
К чему именно относится второй вопрос, я не понял. Переформулируйте, пожалуйста.
Вот тут противоречие. Какая же это доступность, если взаимодействовать нельзя?
А дело в том, что для availability достаточно локального взаимодействия, и локально с системой взаимодействовать можно!
Вы, конечно, правы, но на самом деле всё наоборот.
Это популярное заблуждение, что в чистых языках или в ФП нет побочных эффектов.
В «грязных» языках побочные эффекты разрешены в любом месте и ими нельзя управлять. В чистых языках побочные эффекты живут только в специальных объектах-загончиках, только там, где они нужны, и ими можно управлять.
CvRDT = полурешётка,
CmRDT — лог операций.
Потом ещё придумали Mergeable, Delta-state, и вот этот RON в статье можно считать отдельным видом CRDT.
Шучу, это непросто. Суть бесконфликтности в том, чтобы разрешить только такие операции, которые не вызывают конфликтов (при доставке в произвольном порядке). Придумывать такие структуры тяжело. Зато их можно легко комбинировать, вкладывать друг в друга, объединять в структуры.
Полурешётка — один из видов CRDT, есть и другие.
Гит можно, но только относительно fetch и fast-forward.
Насколько я помню, всё было немного иначе.
До С++ функторы появились в С, и там это было сокращение-гармошка (portmanteau) от «FUNCtion poinTER» (с поправкой на e/o, но это хотя бы какой-то смысл имеет, в отличие от «object»). Потом в С++ это понятие расширили на любое значение, которое можно вызывать как функцию.