Вы конечно можете придумывать себе какую угодно математику с обоснованием «я так вижу», «смотря что под этим понимать», «смотря как считать». Но в общепринятом математическом определении: если элемент уже принадлежит некоторому множеству, то добавление его ещё раз в это же самое множество, не меняет это множество.
Автору спасибо за попытку, но, к сожалению, все-таки не для программиста(= В отличие от этой статьи: после десятка подобных вашей наконец-то пришло понимание. А все потому, что ничего лишнего в объяснении — только код.
Первый пример — сильно обрезанный экземпляр монады 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
Здорово. Мой вариант для задачи, сведённой до этого упрощенного примера был таким:
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 при вызове.)
Прелесть монад (и управления эффектами с их помощью) не в том, что если у меня функция живёт в какой-нибудь MonadWriter LogTy или State Foo, то я знаю, что она может писать логи типа LogTy или ковырять состояние типа Foo. Прелесть в том, что если она в этих монадах не живёт, то логи она точно не пишет и состояние не ковыряет. Чистое ФП нужно для того, чтобы функциям ничего лишнего не разрешать, а монады в нём позволяют разрешать только то, что нужно.
Да, я понял что вы имели ввиду когда говорили об ограничениях эффектов только теми местами, где они нужны.
То, что вы перечислили в качестве эффектов — это не более чем элементы программной функциональности. И в качестве таких «эффектов» можно указать много чего — в зависимости от того, что требуется программисту. И в этом смысле это конечно очень широкое понятие.
Суть в том, что в императивных языках привнесение их в программу это не удел монад, в этих языках имеются куда более естественные и идиоматические средства их создания и паттерны проектирования. Поэтому когда в контексте императивного ООП языка упоминают про монады, то это по-моему только лишь для красного словца.
Кстати стоит упомянуть, что самый главный эффект, который достигается с любой из перечисленных вами монад — задание последовательности вычислений (например функция может что-то вычислить, залогировать это, и потом еще что-то довычислить, залогировать и вернуть результат) в императивных языках имеется из коробки (этот привычный эффект и поэтому всегда пропускается, но в ФП его можно добиться только зависимостью по данным).
И в Хаскеле программирование с эффектами тоже не удел лишь монад, для этого можно использовать и аппликативы (или вообще использовать какую-то свою шайтан-конструкцию). Но я уверен, что при желании можно и аппликатив начать демонстрировать как он выглядит и объяснять что это такое например на С++ или Питоне, просто не нашлось ещё желающих просветить программистскую общественность на это счёт. :)
Монады в функциональном программировании используются для выполнения лишь одной роли — эмуляция эффектов характерных для императивного программирования. Непосредственно эмуляция выполняется в функции 'bind' (оператор >>= в Хаскеле). Никаких других мистических качеств у монад нет.
Если мы уже находимся в императивном окружении (ООП в общем его предполагает), то зачем там нужно как-то имитировать/реализовывать монады?
Все что нужно — инкапсуляция, возможности для сайдэффектов в любой ф-ции с параметрами по ссылке, возможность создания последовательности вычислений (с их прерыванием или протаскиванием состояния или генерирования исключения и т.п.) — все что угодно, для чего используются монады в ФП можно реализовать в ООП на императивном языке идиоматично для него вообще не прибегая даже к такому понятию.
Или я не прав? :)
Мне всегда интересно было, почему, например, Монаду нельзя описать как некую хрень с сайдэффектом или как некую защитную обертку над другими типами.
Почему — как раз можно, и это наиболее понятный и утилитарно-обоснованный способ.
Почему обязательно пытаться притягивать категории, функторы?
Это просто хаскеллисты притягивают, поскольку многие концепции и подходы к структурированию задач в Хаскеле могут быть описаны в рамках теории категорий, т.е. математически. А это очень подкупает.
И кстати, я случаем не пропустил в статье сказочное утверждение, что в хаскеле попав в монаду, из нее нельзя выйти?
Например из монады IO выйти нельзя — «распаковать» значение без доступного конструктора или спецфункции компилятора не получится.
Обычный «нормальный» функтор F переводит морфизмы (a -> b) в (F a -> F b). Используя «однобокий правый» функтор K для перевода из морфизмов (a -> b) в (a -> K b) можно построить категорию Клейсли. Что получается при использовании «однобокого левого» функтора U из (a -> b) в (U a -> b)?
:)
Может в ленте по подписке показывать только новости из подписанных хабов?
(И соответственно чтобы линк «все новости» под блоком с пятью последними новостями на ленте публикаций по подписке переводил на ленту новостей, фильтрованную по подписанным хабам.)
Ну вот так всегда: попросишь человека ответить за свои слова, а у него сразу же после этого находится множество куда более важных и интересных для него других тем.
Strict aliasig rule определяет, какие два различных указателя могут ссылаться на один и тот же объект, а какие — в нормальном случае нет. Когда компилятор видит в обрабатываемом участке кода использование двух различных указателей, для которых согласно правилу допустимо, что они могут указывать на один и тот же участок памяти, то он действует аккуратно, без оптимизаций; в остальных случаях считается, что производится работа с различными объектами и выполняется соответствующая оптимизация генерируемого кода.
На этом собственно основная содержательная история со strict aliasing заканчивается, и к взаимному кастингу указателей она имеет лишь опосредованное отношение.
При этом компилятор действует достаточно умно: например если он видит что программист приводит указатель являющийся входным параметром функции к другому типу, то он переносит этот новый тип на сам входной параметр и компилирует всю функцию как если бы она имела соответствующую сигнатуру. Например если в функции
int foo( float *f, int *i ) {
*i = 1 ;
*f = 0.f ;
return *i ;
}
то компилятор будет обрабатывать код как если бы сигнатура была int foo( float *f, char *i ) Т.е. согласно strict aliasing rule эти два указателя — входных параметра могут ссылаться на один и тот же объект.
Есть также нюанс с перекрытием объектов одного и того же типа при расположении в памяти — частичное перекрытие позволяет с помощью разных указателей (одного и того же типа) по разным относительным смещениям (например с помощью обращения к разным полям структуры) обратиться к одному и тому же участку памяти. В этом случае несмотря на то, что эти два указателя одинакового типа очевидно могут ссылаться на один и тот же объект, и поэтому оптимизация для работающего с ними кода должна быть выключена, факт обращения к разным полям структуры определяется как доступ к строго различным участкам памяти и компилятор оптимизирует данный случай, что приводит к UB.
Если вы понимаете о чем говорите, расскажите пожалуйста по-подробнее о «централизованной точке управления» российским интенетом. Материально она в чём будет выражаться, поясните? Используя формулировки прямо из ваших комментариев: «только пожалуйста с технической точки зрения и с подробностями, а не очередной булшит».
Вы, «как человек--разработчик сетевого ПО с опытом работы в телекоме, и хорошо представляющий, как это все устроено и работает», объясните пожалуйста.
А двенадцатая монета куда подевалась?
Первый пример — сильно обрезанный экземпляр монады Writer с единственной возможной функциональностью которую можно реализовать в данном случае — конкатенация вывода лог-сообщений. Третий пример (когда функции класса
Employee
могут возвращатьNone
вместо значения) можно интерпретировать как монаду Maybe. Но как было уже сказано: "Монада — это не maybe. Наоборот maybe — это монада"Во втором примере монады как таковой нет. Да, это effectful-вычисления, но ни в каком виде не монада. Что-то типа:
Такая версия — сделать список из IO-действий — не является по сути отделением логики от операции вывода:
Насколько я понял решение можно сделать таким, используя полиморфную функцию-префикс:
(Какие-то нюансы не знаю, что мешают, чтобы не нужно было специфицировать тип результата
mkOut
при вызове.)Интересно, спасибо.
Суть в том, что в императивных языках привнесение их в программу это не удел монад, в этих языках имеются куда более естественные и идиоматические средства их создания и паттерны проектирования. Поэтому когда в контексте императивного ООП языка упоминают про монады, то это по-моему только лишь для красного словца.
Кстати стоит упомянуть, что самый главный эффект, который достигается с любой из перечисленных вами монад — задание последовательности вычислений (например функция может что-то вычислить, залогировать это, и потом еще что-то довычислить, залогировать и вернуть результат) в императивных языках имеется из коробки (этот привычный эффект и поэтому всегда пропускается, но в ФП его можно добиться только зависимостью по данным).
И в Хаскеле программирование с эффектами тоже не удел лишь монад, для этого можно использовать и аппликативы (или вообще использовать какую-то свою шайтан-конструкцию). Но я уверен, что при желании можно и аппликатив начать демонстрировать как он выглядит и объяснять что это такое например на С++ или Питоне, просто не нашлось ещё желающих просветить программистскую общественность на это счёт. :)
Если мы уже находимся в императивном окружении (ООП в общем его предполагает), то зачем там нужно как-то имитировать/реализовывать монады?
Все что нужно — инкапсуляция, возможности для сайдэффектов в любой ф-ции с параметрами по ссылке, возможность создания последовательности вычислений (с их прерыванием или протаскиванием состояния или генерирования исключения и т.п.) — все что угодно, для чего используются монады в ФП можно реализовать в ООП на императивном языке идиоматично для него вообще не прибегая даже к такому понятию.
Или я не прав? :)
Это просто хаскеллисты притягивают, поскольку многие концепции и подходы к структурированию задач в Хаскеле могут быть описаны в рамках теории категорий, т.е. математически. А это очень подкупает.
Например из монады IO выйти нельзя — «распаковать» значение без доступного конструктора или спецфункции компилятора не получится.
(a -> b)
в(F a -> F b)
. Используя «однобокий правый» функтор K для перевода из морфизмов(a -> b)
в(a -> K b)
можно построить категорию Клейсли. Что получается при использовании «однобокого левого» функтора U из(a -> b)
в(U a -> b)
?:)
(И соответственно чтобы линк «все новости» под блоком с пятью последними новостями на ленте публикаций по подписке переводил на ленту новостей, фильтрованную по подписанным хабам.)
На этом собственно основная содержательная история со strict aliasing заканчивается, и к взаимному кастингу указателей она имеет лишь опосредованное отношение.
При этом компилятор действует достаточно умно: например если он видит что программист приводит указатель являющийся входным параметром функции к другому типу, то он переносит этот новый тип на сам входной параметр и компилирует всю функцию как если бы она имела соответствующую сигнатуру. Например если в функции
добавить то компилятор будет обрабатывать код как если бы сигнатура была
int foo( float *f, char *i )
Т.е. согласно strict aliasing rule эти два указателя — входных параметра могут ссылаться на один и тот же объект.Ещё не всегда очевидный пример такого же рода:
Есть также нюанс с перекрытием объектов одного и того же типа при расположении в памяти — частичное перекрытие позволяет с помощью разных указателей (одного и того же типа) по разным относительным смещениям (например с помощью обращения к разным полям структуры) обратиться к одному и тому же участку памяти. В этом случае несмотря на то, что эти два указателя одинакового типа очевидно могут ссылаться на один и тот же объект, и поэтому оптимизация для работающего с ними кода должна быть выключена, факт обращения к разным полям структуры определяется как доступ к строго различным участкам памяти и компилятор оптимизирует данный случай, что приводит к UB.
Вы, «как человек--разработчик сетевого ПО с опытом работы в телекоме, и хорошо представляющий, как это все устроено и работает», объясните пожалуйста.