From a2288cd395a48a8b18b322c508d8522d863b1aa3 Mon Sep 17 00:00:00 2001 From: Evgeny Pogrebnyak Date: Fri, 25 Oct 2019 12:01:45 +0300 Subject: [PATCH 01/12] Update and rename 2018-01-18-effects-haskell.md to 2018-01-18-effects-haskell.eng.md --- ...ell.md => 2018-01-18-effects-haskell.eng.md} | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) rename posts/theory/{2018-01-18-effects-haskell.md => 2018-01-18-effects-haskell.eng.md} (99%) diff --git a/posts/theory/2018-01-18-effects-haskell.md b/posts/theory/2018-01-18-effects-haskell.eng.md similarity index 99% rename from posts/theory/2018-01-18-effects-haskell.md rename to posts/theory/2018-01-18-effects-haskell.eng.md index 49d165cd..d70e8f19 100644 --- a/posts/theory/2018-01-18-effects-haskell.md +++ b/posts/theory/2018-01-18-effects-haskell.eng.md @@ -2,11 +2,11 @@ author: Юрий Сыровецкий title: Эффекты в Haskell tags: эффекты -description: Реализация эффектов в Haskell. +description: Effects implementation in Haskell. --- -В [предыдущей статье](../10/effects.html) мы познакомились с основными видами -эффектов в математике и программировании. +Draft translation into English. + Сегодня мы докажем, что для процедур, то есть «функций с эффектами» не нужна особая поддержка со стороны языка программирования, @@ -18,6 +18,17 @@ OCaml, JavaScript и прочих, нет никаких встроенных э Однако, средствами этого языка можно построить чистые управляемые эффекты, и не хуже, чем «нечистые». +--- + +Make a footnote: + +--- + +В [предыдущей статье](../10/effects.html) мы познакомились с основными видами +эффектов в математике и программировании. + +--- + _Замечание о терминах._ Я позволю себе называть параметрические типы с опущенными параметрами просто _типами_, From bb553c8f82bb46a63c4934f40661f44e18f4e94e Mon Sep 17 00:00:00 2001 From: E Pogrebnyak Date: Mon, 28 Oct 2019 18:50:31 +0300 Subject: [PATCH 02/12] post in Russian restored, translation in drafts --- .../2018-01-18-effects-haskell.eng.md | 0 posts/theory/2018-01-18-effects-haskell.md | 506 ++++++++++++++++++ 2 files changed, 506 insertions(+) rename {posts/theory => drafts}/2018-01-18-effects-haskell.eng.md (100%) create mode 100644 posts/theory/2018-01-18-effects-haskell.md diff --git a/posts/theory/2018-01-18-effects-haskell.eng.md b/drafts/2018-01-18-effects-haskell.eng.md similarity index 100% rename from posts/theory/2018-01-18-effects-haskell.eng.md rename to drafts/2018-01-18-effects-haskell.eng.md diff --git a/posts/theory/2018-01-18-effects-haskell.md b/posts/theory/2018-01-18-effects-haskell.md new file mode 100644 index 00000000..49d165cd --- /dev/null +++ b/posts/theory/2018-01-18-effects-haskell.md @@ -0,0 +1,506 @@ +--- +author: Юрий Сыровецкий +title: Эффекты в Haskell +tags: эффекты +description: Реализация эффектов в Haskell. +--- + +В [предыдущей статье](../10/effects.html) мы познакомились с основными видами +эффектов в математике и программировании. +Сегодня мы докажем, что для процедур, +то есть «функций с эффектами» не нужна особая поддержка со стороны языка +программирования, +достаточно реализации обычных «чистых» функций. + +Для примера возьмём Haskell — чистый язык, в котором, в отличие от «нечистых» C, +OCaml, JavaScript и прочих, нет никаких встроенных эффектов. +(Ну, почти нет.) +Однако, средствами этого языка можно построить чистые управляемые эффекты, +и не хуже, чем «нечистые». + +_Замечание о терминах._ +Я позволю себе называть параметрические типы с опущенными параметрами просто +_типами_, +хотя строгий хаскелит употребил бы термин _конструктор типов_. +Конструктор типов сам типом не является, но принадлежит языку типов. +В разговорной речи и не очень строгих статьях, как эта, +такая вольность вполне допустима, чтобы речь звучала не так тяжело +(«ехал конструктор через конструктор...»), +благо, ни к каким противоречиям при программировании это не приводит. +Если параметр не указан, то это конструктор типа, +а если по контексту подразумевается всё-таки конкретный тип, +значит, речь идёт о конструкторе с произвольными значениями параметров. + +## 0. Отсутствие эффектов + +Представим чистую функцию `f :: a -> b` в виде чёрного ящика: + +
![](../../../../../files/posts/2018-01-18/pure.svg)
+ +## 1. Эффект частичности + +
![](../../../../../files/posts/2018-01-18/partial.svg)
+ +Частичная функция либо возвращает результат, либо не возвращает. + +Такая вариативность легко моделируется с помощью типа-суммы. + +```haskell +data Maybe a = Nothing | Just a +``` + +Значение типа `Maybe a` либо содержит значение типа `a`, либо нет. + +Мы можем описать частичную процедуру, иногда возвращающую `b`, как функцию, +всегда возвращающую `Maybe b`. + +
![](../../../../../files/posts/2018-01-18/partial-pure.svg)
+ +```haskell +p :: a -> Maybe b +``` + +Пример. + +```haskell +headM :: [a] -> Maybe a +headM [] = Nothing -- невозможно взять голову пустого списка +headM (x:_) = Just x -- голова непустого списка — вот она! +``` + +Обратите внимание, что тип `Maybe` принадлежит `Functor`, `Applicative`, +`Monad` и многим другим интересным и полезным классам. + +На практике также применяется типы `Either` и `Except`, +реализующие тот же эффект, но позволяющий добавить информацию о том, +почему вычисление не может быть завершено. + +Пример. + +```haskell +data Either a b + = Left a -- условно ошибка + | Right b -- условно успех + +data MyError = EmptyList + +headE :: [a] -> Either MyError a +headE [] = Left EmptyList +headE (x:_) = Right x +``` + +Можно комбинировать частичность с другими эффектами: + +```haskell +headE + :: MonadError MyError m -- 'm' поддерживает эффект "ошибки" + => [a] -> m a +headE (x:_) = pure x +headE [] = + throwError EmptyList -- эффект ошибки, прекращение дальнейших вычислений +``` + +На самом деле частичность — единственный неуправляемый эффект, +доступный в Хаскеле непосредственно. +Любая функция может оказаться частичной, +то есть зависнуть или выбросить исключение, +по ошибке или из-за несовершенства реального мира. + +```haskell +-- простейший бесконечный цикл +x = x -- вычисление 'x' приводит к вычислению 'x', снова и снова + +-- не столь тривиальный пример зависания +n = length $ takeWhile (< 10) [2, 1 ..] +``` + +К сожалению, в полных по Тьюрингу языках этого невозможно избежать, +полнота влечёт возможность реализации бесконечного цикла, +то есть незавершения программы. +Существуют языки, не полные по Тьюрингу, +и на них тоже можно писать сколь угодно сложные программы без эффекта +частичности, +то есть гарантированно тотальные, +но такое требование существенно усложняет язык. + +К счастью, в Хаскеле доступна управляемая частичность, +и иметь дело с неуправляемой частичностью приходится редко. + +## 2. Эффекты недетерменированности (неопределённости) + +### 2.1. Эффект нестабильности + +
![](../../../../../files/posts/2018-01-18/unstable.svg)
+ +Если процедура для одного и того же значения аргумента может вернуть от раза +к разу разные результаты, +это значит, что на самом деле результат зависит от чего-то ещё. + +Даже генератор случайных чисел (настоящий, аппаратный) — это «чистая» функция, +зависящая от состояния источника энтропии. + +Чтобы представить этот эффект чистой функцией, +надо всего лишь неявную зависимость сделать явной. + +
![](../../../../../files/posts/2018-01-18/unstable-pure.svg)
+ +```haskell +p :: a -> r -> b +``` + +Пример. + +```haskell +getDataDir :: Config -> FilePath +getDataDir Config{dataDir} = dataDir +``` + +Для удобства рассуждений об эффектах удобно ввести синоним + +```haskell +type Reader r b = r -> b +p :: a -> Reader r b +``` + +```haskell +-- процедура, читающая неявное значение и возвращающая его +ask :: Reader r r +ask = id + +getDataDir :: Reader Config FilePath +getDataDir = do + Config{dataDir} <- ask -- читаем конфиг, переданный неявно + pure dataDir +``` + +Можно комбинировать неявную зависимость с другими эффектами: + +```haskell +data MyError = DataDirNotSpecified + +getDataDir + :: ( MonadError MyError m + , MonadReader Config m + -- 'm' поддерживает эффект неявной зависимости от 'Config' + ) + => m FilePath +getDataDir = do + Config{mDataDir} <- ask -- читаем конфиг, переданный неявно + case mDataDir of + Nothing -> throwError DataDirNotSpecified + Just dataDir -> pure dataDir +``` + +Обратите внимание, что тип `Reader r` +(конструктор типа `Reader`, частично применённый к одному аргументу) +принадлежит `Functor`, `Applicative`, +`Monad` и многим другим интересным и полезным классам. + +### 2.2. Эффект множественности + +
![](../../../../../files/posts/2018-01-18/many.svg)
+ +Здесь всё просто и очевидно. +Функция, дающая много ответов сразу — это функция, +имеющая единственный ответ-множество. + +В Хаскеле есть тип `Set` для множеств, +но для моделирования эффекта множественности оказывается более удобным список — +`[]`. + +
![](../../../../../files/posts/2018-01-18/many-pure.svg)
+ +```haskell +p :: a -> [b] +``` + +Пример. + +```haskell +rollADie :: Int -> [Int] +rollADie n = [1..n] + +rollTwoDiceAndSum :: Int -> [Int] +rollTwoDiceAndSum n = do + a <- rollADie n + b <- rollADie n + pure $ a + b +``` + +Обратите внимание, что тип `[]` (конструктор типа списка) +принадлежит `Functor`, `Applicative`, +`Monad` и многим другим интересным и полезным классам. + +Посколько множество результатов может быть и пустое, +то множественность можно рассматривать как частный случай случай частичности, — +функция, возвращающая множество ответов, +может для некоторых значений аргумента вернуть пустое множество, +то есть не вернуть ни одного ответа. + +Таким образом, тип `[]` реализует и эффект частичности. + +## 3. Побочный эффект + +
![](../../../../../files/posts/2018-01-18/side.svg)
+ +Побочный эффект — это просто неявный результат. Сделаем же неявное явным! + +
![](../../../../../files/posts/2018-01-18/side-pure.svg)
+ +```haskell +p :: a -> (b, s) +``` + +Для удобства рассуждений об эффектах удобно ввести обёртку + +```haskell +newtype Putter s b = Putter (b, s) +p :: a -> Putter s b +``` + +Обратите внимание, что тип `Putter s` +(конструктор типа `Putter`, частично применённый к одному аргументу) +принадлежит `Functor`, `Applicative`, +`Monad` и многим другим интересным и полезным классам. + +На практике чаще применяется тип `Writer`, структурно идентичный, +но с более полезными свойствами: все побочные эффекты собираются в моноид. + +Пример. + +```haskell +-- процедура, откладывающая побочное значение +tell :: w -> Writer w () +tell w = Writer ((), w) + +-- процедура с побочным эффектом журналирования +sumWithLog :: Int -> Int -> Writer String Int +sumWithLog x y = do + tell $ "sum: x = " ++ show x ++ "\n" -- запишем в лог аргументы процедуры + tell $ "sum: y = " ++ show y ++ "\n" + let result = x + y + tell $ "sum: result = " ++ show result ++ "\n" -- и результат запишем + pure r + +-- функция склейки процедур, спрятанная в do-синтаксисе +-- тут-то и происходит сборка моноида +(>>) :: Monoid w => Writer w a -> Writer w b -> Writer w b +Writer (_, w1) >> Writer (b, w2) = Writer (b, w1 <> w2) +``` + +Можно комбинировать! + +```haskell +data MyError = DataDirNotSpecified +type AccessCounter = Sum Int + +-- процедура с побочным эффектом подсчёта количества вызовов +getDataDir + :: ( MonadError MyError m + , MonadReader Config m + , MonadWriter AccessCounter m + -- 'm' поддерживает побочный эффект счётчика + ) + => m FilePath +getDataDir = do + tell 1 -- добавить 1 ко счётчику обращений + Config{mDataDir} <- ask + case mDataDir of + Nothing -> throwError DataDirNotSpecified + Just dataDir -> pure dataDir +``` + +## 2 + 3. Эффект состояния + +
![](../../../../../files/posts/2018-01-18/state.svg)
+ +Если соединить результат побочного эффекта и источник нестабильности, +из их комбинации (композиции) получается эффект состояния — процедура, +которая может и зависеть от текущего состояния «переменной», +и задавать ей новое состояние. + +Проведя рассуждения, аналогичные случаям `Reader` и `Putter`, получим + +
![](../../../../../files/posts/2018-01-18/state-pure.svg)
+ +```haskell +p :: a -> s -> (b, s) +``` + +Для удобства рассуждений об эффектах удобно ввести обёртку + +```haskell +newtype State s b = State (s -> (b, s)) +p :: a -> State s b +``` + +Пример. + +```haskell +-- получить значение внутренней переменной +get :: State a a +get = State $ \s -> -- старое значение + ( s -- результат + , s -- новое значение переменной совпадает со старым + ) + +-- присвоить значение внутренней переменной +put :: a -> State a () +put s = State $ \_ -> -- старое значение игнорируем + ( () -- результат + , s -- новое значение переменной + ) + +-- изменить значение внутренней переменной +modify :: (a -> a) -> State a () +modify f = State $ \s -> -- старое значение + ( () -- результат + , f s -- новое значение переменной + ) + +-- процедура-генератор псевдослучайных чисел по простейшей формуле +prng + :: State + Int -- тип внутренней переменной + Int -- тип результата +prng = do + modify $ \s -> s * 23 + 97 + get +``` + +Комбинируем с предыдущими эффектами. + +```haskell +type Storage = Map FilePath Value -- имитация файловой системы для демонстрации + +-- процедура с побочным эффектом подсчёта количества вызовов +putData + :: ( MonadError MyError m + , MonadReader Config m + , MonadWriter AccessCounter m + , MonadState Storage m -- 'm' поддерживает эффект изменения 'Storage' + ) + => m FilePath +putData key value = do + dataDir <- getDataDir + modify $ Map.insert (dataDir key) value -- вносим изменения в Storage +``` + +Не будет сюрпризом, что тип `State s` +(конструктор типа `State`, частично применённый к одному аргументу) +принадлежит `Functor`, `Applicative`, +`Monad` и многим другим интересным и полезным классам. + +## 0. Отсутствие эффектов (продолжение) + +Рассмотрим тип + +```haskell +newtype Identity a = Identity a +``` + +Тип `Identity a` полностью аналогичен типу `a`. +То есть это своего рода функция `id`, только на уровне типов. + +Тип `Identity` не может выражать никаких эффектов. +С другой стороны, можно сказать, что он выражает отсутствие эффектов. + +Конечно же, конструктор типа `Identity` принадлежит `Functor`, `Applicative`, +`Monad` и многим другим интересным и полезным классам. + +## Эффекты! Эффекты повсюду! + +В Хаскеле есть специальный тип `IO`, реализующий сразу все возможные эффекты. +В нём можно прерывать программу, обмениваться данными с ресурсами, +не указанными явно в аргументах и возвращаемом значении. +В `IO` нет ограничений, доступен на чтение и запись весь мир, +в том числе ядерные ракеты. +Как если бы у нас был в программе объект типа `RealWorld` и мы могли бы изменять +его, как переменную под `State`. + +Реализация `IO` не определена в спецификации языка. +Если вы заглянете в исходники стандартной библиотеки, скорее всего, +действительно увидите что-то подобное `State RealWorld`, +но эта реализация нужна только для внутренних нужд компилятора и пропадает при +компиляции, +так что верить ей не стоит. + +Вы уже догадались, что конструктор типа `IO` принадлежит `Functor`, +`Applicative`, `Monad` и многим другим интересным и полезным классам. + +Выше я утверждал, что в Хаскеле никаких побочных эффектов нет, +а сейчас рассказываю, что в `IO` всё можно — только руку протяни. +Этот парадокс разрешается просто: запускает ракеты не сам язык, а тип `IO`, +который в некотором смысле отделён от языка и подчиняется ему. +Хаскель управляет `IO`, а не наоборот. +Нет возможности запустить `IO` в произвольном месте программы +(ну ладно, есть пара грязных хаков, но честных способов нет), +только в специально отведённых. + +Иными словами, `IO` даёт доступ к неуправляемым эффектам, +но доступ к самому `IO` управляемый. + +## Процедуры и чистые функции + +Вернёмся к нашей дихотомии. + +С одной стороны, функция — это просто процедура без эффектов. + +С другой стороны, нам удалось все процедуры выразить в типах вида + +```haskell +p :: a -> f b +``` + +где _b_ — тип результата, он может быть любым, _f_ — тип, реализующий эффект. + +Иными словами, процедура — это чистая функция, вычисляющая эффект. + +Обратите внимание, что все упомянутые типы эффектов принадлежат `Functor`, +`Applicative`, `Monad` и многим другим интересным и полезным классам +(разве что `Writer` с некоторыми ограничениями). +Эти классы предоставляют общий механизм для работы с эффектами. +Подробнее можно почитать в +[www.staff.city.ac.uk/~ross/papers/Applicative](http://www.staff.city.ac.uk/~ross/papers/Applicative). + +Существуют и другие типы, реализующие эти эффекты иными, +более сложными и полезными способами. +Они всегда являются аппликативными функторами и почти всегда монадами. + +## Управляемые эффекты + +Что даёт возможность управлять эффектами? +Переводя процедуры с эффектами в пространство чистых функций, мы, +не теряя выразительности, получаем возможности + +- рассуждать о процедурах и их эффектах как о сущностях внутри программы, + - в частности, буквально читать в типе функции эффекты, возможные в ней, +- проверять их корректность системой типов компилятора, +- в каждой функции ограничивать пространство эффектов только необходимыми для + решения задачи, +- создавать столь же управляемые комбинации эффектов + (это уже тема для отдельной статьи или даже книги). + +## Заключение + +Соберём в таблицу аспекты чистоты, эффекты, нарушающие эти аспекты, и типы, +моделирующие эти эффекты. + +| Свойство чистой функции | Эффект | Тип | +|---------------------------|----------------------|---------------------------| +| Все | Нет | `Identity` | +| Тотальность | Частичность | `Maybe`, `Either e`, `[]` | +| Детерминированность | Нестабильность | `Reader r` | +| Детерминированность | Множественность | `[]` | +| Нет побочных эффектов | Побочный | `Putter s`, `Writer w` | +| Детерминированность и нет побочных | Состояние | `State s` | +| Любое | Любой | `IO` | + +Все упомянутые типы встроены в язык или легко находятся в стандартной библиотеке +(пакеты `base`, `mtl`), кроме `Putter`. +`Putter` я придумал только для иллюстрации, +но его легко представить как `State`, ограниченный до операции `put`. + +Если будет интерес читателей, +можно будет раскрыть подробности реализации эффектов данными типами в будущих +статьях. From 49136774db14192d9daa1dfce1daf12c6e7c5318 Mon Sep 17 00:00:00 2001 From: E Pogrebnyak Date: Mon, 28 Oct 2019 20:31:08 +0300 Subject: [PATCH 03/12] rename to *.en.md --- ...18-effects-haskell.eng.md => 2018-01-18-effects-haskell.en.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename drafts/{2018-01-18-effects-haskell.eng.md => 2018-01-18-effects-haskell.en.md} (100%) diff --git a/drafts/2018-01-18-effects-haskell.eng.md b/drafts/2018-01-18-effects-haskell.en.md similarity index 100% rename from drafts/2018-01-18-effects-haskell.eng.md rename to drafts/2018-01-18-effects-haskell.en.md From 6751364b367af882a0720d53da5ef1a004f2e399 Mon Sep 17 00:00:00 2001 From: E Pogrebnyak Date: Mon, 28 Oct 2019 21:13:17 +0300 Subject: [PATCH 04/12] start of translation --- drafts/2018-01-18-effects-haskell.en.md | 35 +++++++++++++------------ drafts/eff.hs | 2 ++ 2 files changed, 20 insertions(+), 17 deletions(-) create mode 100644 drafts/eff.hs diff --git a/drafts/2018-01-18-effects-haskell.en.md b/drafts/2018-01-18-effects-haskell.en.md index d70e8f19..187f4bc7 100644 --- a/drafts/2018-01-18-effects-haskell.en.md +++ b/drafts/2018-01-18-effects-haskell.en.md @@ -42,7 +42,7 @@ _типами_, а если по контексту подразумевается всё-таки конкретный тип, значит, речь идёт о конструкторе с произвольными значениями параметров. -## 0. Отсутствие эффектов +## 0. No effects -- Отсутствие эффектов Представим чистую функцию `f :: a -> b` в виде чёрного ящика: @@ -52,18 +52,18 @@ _типами_,
![](../../../../../files/posts/2018-01-18/partial.svg)
-Частичная функция либо возвращает результат, либо не возвращает. - -Такая вариативность легко моделируется с помощью типа-суммы. +A partial fucntion either returns a result or it doesn't. +A sum data type well captures this behaviour. ```haskell data Maybe a = Nothing | Just a ``` -Значение типа `Maybe a` либо содержит значение типа `a`, либо нет. +Value of type `Maybe a` either contains a value +of type `a`, or there is no such value. -Мы можем описать частичную процедуру, иногда возвращающую `b`, как функцию, -всегда возвращающую `Maybe b`. +We can describe a partial procedure that optionally +returns type `b`, as a function that always returns `Maybe b`.
![](../../../../../files/posts/2018-01-18/partial-pure.svg)
@@ -71,27 +71,28 @@ data Maybe a = Nothing | Just a p :: a -> Maybe b ``` -Пример. +Here is an example. ```haskell headM :: [a] -> Maybe a -headM [] = Nothing -- невозможно взять голову пустого списка -headM (x:_) = Just x -- голова непустого списка — вот она! +headM [] = Nothing -- cannot take a head of empty list +headM (x:_) = Just x -- head of non-mepty list - here it is! ``` -Обратите внимание, что тип `Maybe` принадлежит `Functor`, `Applicative`, -`Monad` и многим другим интересным и полезным классам. +Please note, that `Maybe` type belongs to `Functor`, `Applicative`, +`Monad` and other interesting and useful typeclasses. -На практике также применяется типы `Either` и `Except`, -реализующие тот же эффект, но позволяющий добавить информацию о том, -почему вычисление не может быть завершено. +In practice we can also use `Either` and `Except`, +implementing similar effect as `Maybe`, but +allows to add additional information why +a computation was not completed. Пример. ```haskell data Either a b - = Left a -- условно ошибка - | Right b -- условно успех + = Left a -- considered an error + | Right b -- considered success data MyError = EmptyList diff --git a/drafts/eff.hs b/drafts/eff.hs new file mode 100644 index 00000000..f99c9ff3 --- /dev/null +++ b/drafts/eff.hs @@ -0,0 +1,2 @@ +getDataDir :: Config -> FilePath +getDataDir Config{dataDir} = dataDir From ef5d9f63e671bbbb10f5e1e3d7d31301a0dd7489 Mon Sep 17 00:00:00 2001 From: E Pogrebnyak Date: Mon, 28 Oct 2019 21:20:45 +0300 Subject: [PATCH 05/12] explaination needed What is the type of Config? Where is it defined? How can one use it? --- drafts/2018-01-18-effects-haskell.en.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/drafts/2018-01-18-effects-haskell.en.md b/drafts/2018-01-18-effects-haskell.en.md index 187f4bc7..5202fec4 100644 --- a/drafts/2018-01-18-effects-haskell.en.md +++ b/drafts/2018-01-18-effects-haskell.en.md @@ -162,6 +162,22 @@ p :: a -> r -> b Пример. +<-- +EP: желательно определить, что такое `Config`, откуда он +берется и что значит запись `Config{dataDir}` - она стандартная +или через какое-то расширение доступна. + +eff.hs:2:12: error: + Illegal use of punning for field `dataDir' + Use NamedFieldPuns to permit this + | +2 | getDataDir Config{dataDir} = dataDir + | ^^^^^^^^^^^^^^^ + + +--> + + ```haskell getDataDir :: Config -> FilePath getDataDir Config{dataDir} = dataDir From e5bb954a7ce9380d055b30ced9f7c46b9ff23d25 Mon Sep 17 00:00:00 2001 From: E Pogrebnyak Date: Mon, 28 Oct 2019 21:35:30 +0300 Subject: [PATCH 06/12] extension about the Reader --- drafts/2018-01-18-effects-haskell.en.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/drafts/2018-01-18-effects-haskell.en.md b/drafts/2018-01-18-effects-haskell.en.md index 5202fec4..ec80cf27 100644 --- a/drafts/2018-01-18-effects-haskell.en.md +++ b/drafts/2018-01-18-effects-haskell.en.md @@ -174,6 +174,10 @@ eff.hs:2:12: error: 2 | getDataDir Config{dataDir} = dataDir | ^^^^^^^^^^^^^^^ +Можно ли обойтись примером без использования расширений? + +getDataDir выглядит какой-то искуственной - просто +выдает аттрибут из какой-то структуры данных? --> @@ -185,13 +189,19 @@ getDataDir Config{dataDir} = dataDir Для удобства рассуждений об эффектах удобно ввести синоним +EP (дополнение, для редактирования): ...синоним для типа `r -> b`. Мы назовем этот новый тип `Reader`. Этот тип задается с помощью двух параметров типов `r` и `b`. Типу `Reader`, +согласно нашему опрделению, будут соответстовать функции, аргументом +которых является значение типа `r`, а результатом функции является значение типа `b`. + + ```haskell type Reader r b = r -> b -p :: a -> Reader r b +p :: a -> Reader r b -- EP: это эквивалент a -> r -> b ? ``` ```haskell -- процедура, читающая неявное значение и возвращающая его +-- EP: почему значения являются неявным? ask :: Reader r r ask = id From 3a093995ed002b63c144b1458f1ace9dd8690786 Mon Sep 17 00:00:00 2001 From: E Pogrebnyak Date: Mon, 28 Oct 2019 21:48:18 +0300 Subject: [PATCH 07/12] code example --- drafts/2018-01-18-effects-haskell.en.md | 16 +++++++--------- drafts/{eff.hs => effects.hs} | 0 2 files changed, 7 insertions(+), 9 deletions(-) rename drafts/{eff.hs => effects.hs} (100%) diff --git a/drafts/2018-01-18-effects-haskell.en.md b/drafts/2018-01-18-effects-haskell.en.md index ec80cf27..1f1aef63 100644 --- a/drafts/2018-01-18-effects-haskell.en.md +++ b/drafts/2018-01-18-effects-haskell.en.md @@ -174,11 +174,10 @@ eff.hs:2:12: error: 2 | getDataDir Config{dataDir} = dataDir | ^^^^^^^^^^^^^^^ -Можно ли обойтись примером без использования расширений? - getDataDir выглядит какой-то искуственной - просто выдает аттрибут из какой-то структуры данных? +Можно ли обойтись примером без использования расширений? --> @@ -534,11 +533,10 @@ p :: a -> f b | Детерминированность и нет побочных | Состояние | `State s` | | Любое | Любой | `IO` | -Все упомянутые типы встроены в язык или легко находятся в стандартной библиотеке -(пакеты `base`, `mtl`), кроме `Putter`. -`Putter` я придумал только для иллюстрации, -но его легко представить как `State`, ограниченный до операции `put`. +All above mentioned types are embedded into the language +and can be found in easily in standard packages like `base` and `mtl`, except `Putter`. I created `Putter` as an illustration, but one can envisage it as `State`, contrained to operation `put` only. + +To run the examples in this arctile I attach their code in [effects.hs](effects.hs). -Если будет интерес читателей, -можно будет раскрыть подробности реализации эффектов данными типами в будущих -статьях. +We can make a follow-up on effects implementation through types +in our further publications, based on reader interest. \ No newline at end of file diff --git a/drafts/eff.hs b/drafts/effects.hs similarity index 100% rename from drafts/eff.hs rename to drafts/effects.hs From 98628a3809c7be958423cd9244eaed232fa05d71 Mon Sep 17 00:00:00 2001 From: E Pogrebnyak Date: Mon, 28 Oct 2019 21:53:43 +0300 Subject: [PATCH 08/12] MonadError appearance questioned --- drafts/2018-01-18-effects-haskell.en.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/drafts/2018-01-18-effects-haskell.en.md b/drafts/2018-01-18-effects-haskell.en.md index 1f1aef63..e7e10d07 100644 --- a/drafts/2018-01-18-effects-haskell.en.md +++ b/drafts/2018-01-18-effects-haskell.en.md @@ -87,20 +87,27 @@ implementing similar effect as `Maybe`, but allows to add additional information why a computation was not completed. -Пример. + + +In example below we introduce an error type +`MyError` with a sole value `EmptyList`. +`MyError` will be used in other code examples below. ```haskell data Either a b = Left a -- considered an error | Right b -- considered success -data MyError = EmptyList +data MyError = EmptyList -- denotes an custom error name + headE :: [a] -> Either MyError a headE [] = Left EmptyList headE (x:_) = Right x ``` + + Можно комбинировать частичность с другими эффектами: ```haskell From bc45aa2df4f7a9f11646089f64c5c480ac1df538 Mon Sep 17 00:00:00 2001 From: E Pogrebnyak Date: Mon, 28 Oct 2019 22:04:14 +0300 Subject: [PATCH 09/12] partiality statement needs rephrasing --- drafts/2018-01-18-effects-haskell.en.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/drafts/2018-01-18-effects-haskell.en.md b/drafts/2018-01-18-effects-haskell.en.md index e7e10d07..191c626d 100644 --- a/drafts/2018-01-18-effects-haskell.en.md +++ b/drafts/2018-01-18-effects-haskell.en.md @@ -121,10 +121,27 @@ headE [] = На самом деле частичность — единственный неуправляемый эффект, доступный в Хаскеле непосредственно. + + + Любая функция может оказаться частичной, то есть зависнуть или выбросить исключение, по ошибке или из-за несовершенства реального мира. + + + + ```haskell -- простейший бесконечный цикл x = x -- вычисление 'x' приводит к вычислению 'x', снова и снова From d0cd08625320b8a8812440a346ec98fed17a7fa1 Mon Sep 17 00:00:00 2001 From: E Pogrebnyak Date: Mon, 28 Oct 2019 22:05:43 +0300 Subject: [PATCH 10/12] comment formatting --- drafts/2018-01-18-effects-haskell.en.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drafts/2018-01-18-effects-haskell.en.md b/drafts/2018-01-18-effects-haskell.en.md index 191c626d..df93bcea 100644 --- a/drafts/2018-01-18-effects-haskell.en.md +++ b/drafts/2018-01-18-effects-haskell.en.md @@ -128,7 +128,7 @@ headE [] = ...который может быть возникнуть непосредственно из-за использования самих конструкций языка Haskell, -а не явлений внешнего мираю +а не явлений внешнего мира. --> @@ -136,7 +136,7 @@ headE [] = то есть зависнуть или выбросить исключение, по ошибке или из-за несовершенства реального мира. - @@ -186,7 +186,7 @@ p :: a -> r -> b Пример. -<-- + + + На практике чаще применяется тип `Writer`, структурно идентичный, но с более полезными свойствами: все побочные эффекты собираются в моноид. + + + + +... , который позволяет "склеивать" побочные эффекты в длинную колбасу, + например, в список сообщений логгирования +или сообщений об ошибке. + Пример. ```haskell @@ -341,7 +358,7 @@ sumWithLog x y = do tell $ "sum: y = " ++ show y ++ "\n" let result = x + y tell $ "sum: result = " ++ show result ++ "\n" -- и результат запишем - pure r + pure r -- *** должно быть result ? *** -- функция склейки процедур, спрятанная в do-синтаксисе -- тут-то и происходит сборка моноида From 7087336432222c950ed1ac354085e33d6e267474 Mon Sep 17 00:00:00 2001 From: E Pogrebnyak Date: Wed, 30 Oct 2019 10:41:30 +0300 Subject: [PATCH 12/12] Sections 0 and 1 translated, comments in --- drafts/2018-01-18-effects-haskell.en.md | 131 +++++++++++++----------- 1 file changed, 73 insertions(+), 58 deletions(-) diff --git a/drafts/2018-01-18-effects-haskell.en.md b/drafts/2018-01-18-effects-haskell.en.md index e1d6e847..d51d0675 100644 --- a/drafts/2018-01-18-effects-haskell.en.md +++ b/drafts/2018-01-18-effects-haskell.en.md @@ -1,33 +1,29 @@ --- author: Юрий Сыровецкий -title: Эффекты в Haskell -tags: эффекты +title: Effects in Haskell +tags: effects, purity description: Effects implementation in Haskell. --- -Draft translation into English. +In this article we will demonstrate that procedures (functions with effects) +do not require special support from the programming language and that +regular "pure" functions can be used to handle these procedures with effects. -Сегодня мы докажем, что для процедур, -то есть «функций с эффектами» не нужна особая поддержка со стороны языка -программирования, -достаточно реализации обычных «чистых» функций. +As an example we will use Haskell, a pure functions language, which does not have the embedded effects in a way "impure" C, OCaml, JavaScript do. +However, we can build pure, controllable effects in Haskell, which +serve our goals quite similarly as "impure" effects. -Для примера возьмём Haskell — чистый язык, в котором, в отличие от «нечистых» C, -OCaml, JavaScript и прочих, нет никаких встроенных эффектов. -(Ну, почти нет.) -Однако, средствами этого языка можно построить чистые управляемые эффекты, -и не хуже, чем «нечистые». ---- + -В [предыдущей статье](../10/effects.html) мы познакомились с основными видами -эффектов в математике и программировании. ---- + _Замечание о терминах._ Я позволю себе называть параметрические типы с опущенными параметрами просто @@ -42,13 +38,13 @@ _типами_, а если по контексту подразумевается всё-таки конкретный тип, значит, речь идёт о конструкторе с произвольными значениями параметров. -## 0. No effects -- Отсутствие эффектов +## 0. No effects -Представим чистую функцию `f :: a -> b` в виде чёрного ящика: +We can represent a pure funciton `f :: a -> b` as a black box that takes in a value of type `a` and returns a value of type `b`:
![](../../../../../files/posts/2018-01-18/pure.svg)
-## 1. Эффект частичности +## 1. Partiality effect
![](../../../../../files/posts/2018-01-18/partial.svg)
@@ -60,10 +56,10 @@ data Maybe a = Nothing | Just a ``` Value of type `Maybe a` either contains a value -of type `a`, or there is no such value. +of type `a`, or there is no value. We can describe a partial procedure that optionally -returns type `b`, as a function that always returns `Maybe b`. +returns type `b` as a function that always returns `Maybe b` type.
![](../../../../../files/posts/2018-01-18/partial-pure.svg)
@@ -79,17 +75,14 @@ headM [] = Nothing -- cannot take a head of empty list headM (x:_) = Just x -- head of non-mepty list - here it is! ``` -Please note, that `Maybe` type belongs to `Functor`, `Applicative`, +Please note that `Maybe` type belongs to `Functor`, `Applicative`, `Monad` and other interesting and useful typeclasses. -In practice we can also use `Either` and `Except`, -implementing similar effect as `Maybe`, but -allows to add additional information why -a computation was not completed. +In practice we can also use `Either` and `Except` types. They +implement similar effect as `Maybe`, but +also add additional information why a computation was not completed. - - -In example below we introduce an error type +In example below we introduce an error data type `MyError` with a sole value `EmptyList`. `MyError` will be used in other code examples below. @@ -100,71 +93,94 @@ data Either a b data MyError = EmptyList -- denotes an custom error name - headE :: [a] -> Either MyError a headE [] = Left EmptyList headE (x:_) = Right x ``` - + -Можно комбинировать частичность с другими эффектами: +Partiality can be combined with other effects. Below we use +`MonadError` from [mtl](https://hackage.haskell.org/package/mtl-2.2.2/docs/Control-Monad-Except.html) package for exception processing: ```haskell headE - :: MonadError MyError m -- 'm' поддерживает эффект "ошибки" + :: MonadError MyError m -- 'm' type constructor supports "error" effects => [a] -> m a headE (x:_) = pure x headE [] = - throwError EmptyList -- эффект ошибки, прекращение дальнейших вычислений + throwError EmptyList -- we add an error effect, it terminates further computations ``` +Partiallity is the only effect that can arise from inside Haskell. +The rest of the effects require some act of the outside world, while +partiality situation can be constructed by means of Haskell itself +as we show in examples below. + + + + +Going into an infinte loop is one of partiality effect: ```haskell --- простейший бесконечный цикл -x = x -- вычисление 'x' приводит к вычислению 'x', снова и снова - --- не столь тривиальный пример зависания +-- a simplistic infinite loop +x = x -- computation of 'x' requires 'x' again and again + +-- a slightly longer example: +-- takeWhile (< 10) will require a value 10 or above +-- to stop, there will be no such value in +-- [2, 1 ..], so length of infinite list +-- will never be computed n = length $ takeWhile (< 10) [2, 1 ..] ``` -К сожалению, в полных по Тьюрингу языках этого невозможно избежать, -полнота влечёт возможность реализации бесконечного цикла, -то есть незавершения программы. -Существуют языки, не полные по Тьюрингу, -и на них тоже можно писать сколь угодно сложные программы без эффекта -частичности, -то есть гарантированно тотальные, -но такое требование существенно усложняет язык. -К счастью, в Хаскеле доступна управляемая частичность, -и иметь дело с неуправляемой частичностью приходится редко. + + +In Turing-complete languages the possibility of an infinite loop +cannot be avoided. There are non-Turing-complete programming +languages, where the programs will avoid partiality effect +and the program 'totality' is be garanteed, but the drawback +is an increase in langauge complexity. [This link](https://stackoverflow.com/questions/315340/practical-non-turing-complete-languages) provides a discussion of several non-Turing-complete languages +with respect to program termination guarantee. + +Luckily in Haskell we can use controlled partiallity and +cases of uncontrolled partiallity are rather rare. -## 2. Эффекты недетерменированности (неопределённости) +## 2. Indeterminism (uncertainty) effect -### 2.1. Эффект нестабильности +### 2.1. Instability effect
![](../../../../../files/posts/2018-01-18/unstable.svg)
@@ -172,8 +188,7 @@ n = length $ takeWhile (< 10) [2, 1 ..] к разу разные результаты, это значит, что на самом деле результат зависит от чего-то ещё. -Даже генератор случайных чисел (настоящий, аппаратный) — это «чистая» функция, -зависящая от состояния источника энтропии. +Даже генератор случайных чисел (настоящий, аппаратный) — это «чистая» функция, зависящая от состояния источника энтропии. Чтобы представить этот эффект чистой функцией, надо всего лишь неявную зависимость сделать явной.