Монады
От: reductor  
Дата: 01.12.05 01:14
Оценка: 287 (21)
Вот, что смог. Сумбурно.
----
Здесь я попытаюсь рассмотреть проблему ввода-вывода и механизм монад применительно к ленивому языку без побочных эффектов с достаточно мощной для их реализации системой типов. Такому как Haskell. Монады как таковые вовсе не являются специфичными для Haskell, вся их поддержка в нем заключена лишь в минимальной и необязательной доработке кодогенератора и парсера, а сами они могут быть реализованы на любом языке. Реализация некоторых монад так же входит в поставку языка Clean
Осознавая, что читать это будут люди с разной подготовкой, я постараюсь избежать лишних подробностей и аналогий непонятных никому кроме специалистов в какой-то области. Заранее прошу прощения за возможные ошибки и неточности.

Введение в проблему.

Имея в качестве данности чисто функциональный, а точнее нужно сказать – язык без возможности иметь какие-либо побочные эффекты вопрос об организации ввода-вывода стоит особенно остро. Что вообще такое отсутствие побочных эффектов?
Это требование, которое происходит из предположения о том, что если у нас есть функция f(x) = y, то для любого x она всегда и при любых обстоятельствах вернет один и тот же y. Если f(2) = 4, то всегда f(2) будет оставаться =4. Очевидно, что для полной уверенности в этом мы должны полностью исключить возможность функции влиять на окружение и, соответственно, на результаты других функций. Следовательно, исчезает любая возможность чтения чего-то извне или сохранения результатов в переменные для дальнейшего чтения.
При кажущейся бесполезности, как требования, так и следствия, в результате оно дает нам гораздо больше, чем требует, но об этом позже.

А как вообще тогда...?
Понятно, что в общем это ограничение запросто может не касаться основной функции программы, которая может получать необходимые данные при вызове и возвращать нужный результат. Например, так:
-- ПСЕВДОХАСКЕЛЬ
main input = calcSomethingStupid (read input :: Int)


Уже что-то, но мало пригодно для чего-то, кроме вычислительных задач.
Следующей идеей, которая может прийти в голову, будет внешняя программа написанная на менее привередливом языке, которая читает с STDIN какой-то заранее определенный набор команд, выполняет их и направляет их результат туда, куда скажут. Так как это делает UNIX shell:
dd bs=10 count=1 if=/dev/random > random.value
# Но можно и:
dd bs=10 count=1 if=/dev/random | ./myprogram

То есть если мы запустим нашу программу как:
`./myprogram start`

то она сможет давать шеллу команду сделать что-то полезное вместе с указанием вызвать себя же с этим полезным в качестве входа.
после чего вернуть следующую команду и по новой. Еще лучше, если мы напишем свою обертку, которая сможет избежать ненужного оверхеда и будет уметь вызывать не просто нашу программу, а конкретные ее функции. Например так:
-- ПСЕВДОХАСКЕЛЬ
-- функции main просто возвращает пару значений, 
-- где первый - это что сделать, а второй - куда его подать
main _ = (readLine "Enter your name", hello)
hello name = (writeLine name, ())
-- функция hello принимает на вход строку и возвращает команду напечатать
-- и пустое значение (), означающее, что пора заканчивать.
-- readLine и writeLine просто представляют из себя функции,
-- возвращающие закодированное сообщение в нужном виде для внешнего цикла.

Уже гораздо лучше, хотя до идеального удобства далеко, да и выглядит это не очень все элегантно (не теряйте терпения, до финиша нам остался один шаг.).
Тем не менее, даже в таком варианте у нас есть фактически полноценный ввод-вывод и интуитивно можно прийти к очень важным выводам вообще для программирования:
1. Мы получили очень надежный ввод-вывод, который никак не задевает нашу программу
2. Мы уверены, что сохранили ссылочную прозрачность, при этом если вернуться и посмотреть на код, то можно заметить, что он визуально похож на императивный, хотя таковым не является.
3. Мы пришли к пониманию, что сама семантика ввода-вывода необязательно должна быть императивной, в вполне может определяться как соответствие между _значением_ входа и выхода. То есть мы вполне можем читать программу вслух не как: <прочитать байт, прибавить единицу, записать байт>, а {функция для прочитанного байта будет равна записи этого байта плюс единица}. Причем, интуитивно нет оснований полагать, что такая интерпретация выглядит менее естественной, чем императивная.
Еще один пример на всякий случай:
-- ПСЕВДОХАСКЕЛЬ
main _ = (readByte, plus 1)
plus x y = (writeLine result, readName)
    where result = "result: " ++ show add
          add = x + y
readName = (readString "enter your name", processName)
processName name = 
    let out = "Hello, " ++ name
    in (writeLine out, ())


А теперь, пристегните ремни, пришло время для самого главного, того чего все больше всего боятся, но чего бояться совершенно не стоит, а наоборот, стоит любить.

Монады.

Несмотря на то, что в 92 году решения подобные вышеописанному кое как работали (чуть сложнее конечно, я упростил некоторые не самые важные для понимания идеи подробности), научный поиск (как свой так и чужой) привел гениального Фила Уодлера к публикации документа о монадах (их придумал не он, они уже существовали в математике — в теории категорий).
Что такое Монады вообще такое?
Это объект, точнее сказать, в хаскеле — это тип высокого порядка, параметризующийся другим типом (кто еще не понял как устроена система типов в хаскеле, могут вспомнить шаблоны в С++ или generics в Java).
У этого типа есть две определяющие его операции — unit и bind
unit принимает на вход значение произвольного типа |a| и возвращает значение типа |m a|, где m — это конкретная реализация какой-нибудь монады. А конкретнее: unit :: a -> m a
То есть unit — это конструктор.

bind принимает на вход 2 параметра, первое — это собственно запакованная монада и второе — это _функция_ которая принимает на вход значение того типа, которым параметризовано данное монадическое значение и должна вернуть опять значение монадического типа.
конкретнее:
bind :: m a -> (a -> m b) -> m b
то есть функция должна явным образом вызывать конструктор unit.
Непосредственно в хаскеле unit и bind называются немного по другому. unit — это return, а bind — это инфиксный оператор (>>=)
И все это определено как обычный класс типов:
class Monad m where
    return :: a -> m a
    (>>=)  :: m a -> (a -> m b) -> m b
    (>>)   :: m a -> m b -> m b -- специальный bind для пустого типа: ()
-- И IO определяется как:
instance Monad IO where
    -- здесь нечто, что скрывает в себе непосредственно ввод-вывод

Но что нам это все дает?
Давайте посмотрим сначала как этим можно пользоваться
-- настоящий хаскель, можно компилировать и выполнять
module Main where
import IO

main :: IO ()
main = process
          -- здесь первый оператор >> принимает на вход значение putStr (IO ())
          -- и функцию hFlush, которая возвращает тот же IO ()
          -- которому через >> мы скармливаем функцию readName
    where process :: IO ()
          process = putStr "Enter your name: " >> hFlush stdout >> readName
          -- тут мы к getLine, который является значением IO String
          -- байндим анонимную функцию с параметром name, 
          -- значение функции putStrLn, которое всегда будет IO () 
          readName :: IO ()
          readName = getLine >>= (\name -> putStrLn ("Hello, " ++ name))

То есть, поскольку значение монадического типа мы можем увидеть лишь забайндив к нему через (>>=) другую функцию, которой система типов не дает вернуть ничего, кроме другого монадического значения, мы получаем с одной стороны мощный механизм для описания логики последовательных действий, а с другой — полностью соблюденную прозрачность, т.к в той же функции получаем еще одну, всегда одинаковую монаду, из которой ничего не можем извлечь, а с другой стороны функция, которую мы к монаде байндим и которая оказывается внутри нее не видит самой монады, а видит значение внутри монады. И не может понять изменилось вообще что-то или нет.
Осталась одна маленькая деталь — это все синтаксически не очень красиво выглядит и не всегда хорошо читается. Для этого в хаскель добавили так называемую do-нотацию. В ней перенос строки — это неявный bind и она превращает выражения вроде (x <- mvalue) в (mvalue >>= \x ->) — пример:
main :: IO ()
main = do
    putStr "Enter your name: " -- в конце строки стоит неявное: >> \_ ->
    hFlush stdout
    name <- getLine -- превращается в getLine >>= \name ->
    putStrLn ("Hello, " ++ name)

Вот и вся магия. Под конец пример с минимальной полезной работой:
module Main where
import IO

fact :: Integer -> Integer
fact 0 = 1
fact n = n * fact (n - 1)

main :: IO ()
main = do
    putStr "Enter a number: " >> hFlush stdout
    str <- getLine
    let num = read str
        resnum = fact num
    putStrLn $ "result: " ++ show resnum

Честно говоря получилось рассказать раз в 5 меньше, чем я думал и получилось довольно скомкано, но, надеюсь, хоть что-то будет понятно. Но даже если вы ничего не поняли, запомните главное – все "операции" ввода-вывода — это не операции, а _значение_ этих операций (это особенно верно, что в ленивом языке реальный ввод может произойти в будущем), никакая функция не может вернуть в разное время разное значение и вся программа представляет из себя композицию из функций получающих эти значения ввода-вывода. Как в UNIX shell
А еще монады прекрасный инструмент для очень многих вещей, а не только ввода-вывода.
Про не менее замечательные монады Maybe, State и List как-нибудь потом или можно прочитать здесь (я на самом деле очень рекомендую этот текст тому, кому интересно):

http://www.nomaware.com/monads/html/



01.12.05 20:33: Ветка выделена из темы Монады
Автор: Cyberax
Дата: 28.11.05
— IT
05.12.05 03:01: Перенесено модератором из 'Философия программирования' — Кодт
Re[22]: Монады
От: Костя Ещенко Россия  
Дата: 01.12.05 06:06
Оценка: 4 (1) +1
Здравствуйте, reductor, Вы писали:

R>Вот, что смог. Сумбурно.


[]

Хочется кое-что добавить насчет ввода/вывода. Вот сигнатура главной функции Хаскель-программы:
main :: IO()

Для меня самым большим потрясением в Хаскеле было осознание того, что "значение" типа IO(), возвращаемое этой функцией, и является наблюдаемым поведением программы. Т.е. самой целью существования программы. Тем, что в других языках полуформально определяется как последовательность вызовов внешних функций (и обращений к volatile-переменным, там где они есть).
На самом деле, люди не читают газеты, они принимают их каждое утро, так же как ванну. ©Маршалл Мак-Льюэн
Re[23]: Монады
От: reductor  
Дата: 01.12.05 06:43
Оценка:
Здравствуйте, Костя Ещенко, Вы писали:

КЕ>Для меня самым большим потрясением в Хаскеле было осознание того, что "значение" типа IO(), возвращаемое этой функцией, и является наблюдаемым поведением программы. Т.е. самой целью существования программы. Тем, что в других языках полуформально определяется как последовательность вызовов внешних функций (и обращений к volatile-переменным, там где они есть).


Ну, на самом деле не так все просто. Это значение _наблюдаемым_ как раз не является
Как и любое другое IO-значение, оно не является определенным. Да, формально функция main возвращает программу. Можно сказать систему одновременно со всеми возможными значениями. Но при попытке наблюдения _та_ программа перестает существовать и сводится к единственному значению.

Причем, это далеко не единственная из возможных интерпретаций для такокой мощной абстракции как IO Monad.

Но вообще цель была помочь понять сам принцип работы монад. Я постарался изложить как можно проще. Надеюсь, успешно.
Сигнатуру я там кстати указал.
Re[24]: Монады
От: Костя Ещенко Россия  
Дата: 01.12.05 07:41
Оценка:
Здравствуйте, reductor, Вы писали:

R>Здравствуйте, Костя Ещенко, Вы писали:


КЕ>>Для меня самым большим потрясением в Хаскеле было осознание того, что "значение" типа IO(), возвращаемое этой функцией, и является наблюдаемым поведением программы. Т.е. самой целью существования программы. Тем, что в других языках полуформально определяется как последовательность вызовов внешних функций (и обращений к volatile-переменным, там где они есть).


R>Ну, на самом деле не так все просто. Это значение _наблюдаемым_ как раз не является

R>Как и любое другое IO-значение, оно не является определенным. Да, формально функция main возвращает программу. Можно сказать систему одновременно со всеми возможными значениями. Но при попытке наблюдения _та_ программа перестает существовать и сводится к единственному значению.

Как это, при попытке наблюдения программа перестает существовать? Это что-то на тему принципа непределенности?

Наблюдаемое поведение — это устоявшийся перевод устоявшегося английского термина "observable behavior". Этот термин используется по крайней мере в стандартах C, C++, C#.
Программы запускаются на выполнение именно ради их наблюдаемого поведения — производимых ими побочных эффектов — например, запросить ввод и сохранить результат расчета в файл, отправить пакет в сеть, отобразить и позволить отредактировать текстовый документ. Две программы с одинаковым наблюдаемым поведением эквивалентны для пользователя — они выполняют одну и ту же функцию. Естественно, наблюдаемое поведение программы как правило зависит от состояния внешнего мира, и не обязано повторяться от запуска к запуску.

R>Причем, это далеко не единственная из возможных интерпретаций для такокой мощной абстракции как IO Monad.

R>Но вообще цель была помочь понять сам принцип работы монад. Я постарался изложить как можно проще. Надеюсь, успешно.

Ясно, я просто дополнил. Мне в понимании IO Monad как раз помогла концепция наблюдаемого поведения.

R>Сигнатуру я там кстати указал.
На самом деле, люди не читают газеты, они принимают их каждое утро, так же как ванну. ©Маршалл Мак-Льюэн
Re[25]: Монады
От: reductor  
Дата: 01.12.05 08:10
Оценка:
Здравствуйте, Костя Ещенко, Вы писали:

R>>Ну, на самом деле не так все просто. Это значение _наблюдаемым_ как раз не является

R>>Как и любое другое IO-значение, оно не является определенным. Да, формально функция main возвращает программу. Можно сказать систему одновременно со всеми возможными значениями. Но при попытке наблюдения _та_ программа перестает существовать и сводится к единственному значению.

КЕ>Как это, при попытке наблюдения программа перестает существовать? Это что-то на тему принципа непределенности?


Precisely. Причем, эта связь с квантовым миром не совсем случайна.

КЕ>Наблюдаемое поведение — это устоявшийся перевод устоявшегося английского термина "observable behavior". Этот термин используется по крайней мере в стандартах C, C++, C#.


Я не думаю, что здесь уместна аналогия с этим. В первую очередь потому что любое наблюдение подразумевает изменения во времени, чего здесь категорически нет. В то же время, есть модели и абстракции более точно описывающие то, о чем вы говорите. Те же комонады как монады наоборот, стрелки (arrows) как реакции. Вообще есть целая парадигма под названием Reactive Functional Programming с центром в йейльском университете. Они там роботов программируют.
Ее двигает небезызвестный Paul Hudak. кстати в его The Haskell School of Expression есть порядком про это (даже слишком, я бы сказал).

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


Вот вы делаете здесь ту ошибку (чисто формальную, впрочем), от которой я в тексте два раза предостерег — не совсем правильно рассматривать программу на Haskell как *поведение*. И хотя денотационно ее невозможно полностью описать, поскольку очевидно, что у некоторых функций запросто может быть значение _|_, хотя при этом польза в них есть. У Simon PJ была где-то статья, где он формально определял операционную семантику монад сохраняя при этом полную декларативность. В любом случае, рассматривать программы на хаскеле как автомат будет мало осмысленно. Хотя бы потому что это нам ничего не дает полезного (за что боролись).

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


КЕ>Ясно, я просто дополнил. Мне в понимании IO Monad как раз помогла концепция наблюдаемого поведения.


И спешу сообщить, что пониманий у монад может быть очень много. Главное, что это понимание в каждом конкретном случае можно менять в зависимости от ситуации. В чем и вся соль.
Re[26]: Монады
От: Костя Ещенко Россия  
Дата: 01.12.05 09:20
Оценка: +1
Здравствуйте, reductor, Вы писали:

Ну вот, хотел помочь, а в результате опять спор начинается. В целом пост слишком теоретический для меня, но на высказывания "по делу" постараюсь ответить. Чисто конкретно.

[]

КЕ>>Наблюдаемое поведение — это устоявшийся перевод устоявшегося английского термина "observable behavior". Этот термин используется по крайней мере в стандартах C, C++, C#.


R>Я не думаю, что здесь уместна аналогия с этим. В первую очередь потому что любое наблюдение подразумевает изменения во времени, чего здесь категорически нет.


Уместна и есть, ИМО. В IO существенным является последовательность операций, для формирования которой и используются монады.

R>В то же время, есть модели и абстракции более точно описывающие то, о чем вы говорите. Те же комонады как монады наоборот, стрелки (arrows) как реакции. Вообще есть целая парадигма под названием Reactive Functional Programming с центром в йейльском университете. Они там роботов программируют.

R>Ее двигает небезызвестный Paul Hudak. кстати в его The Haskell School of Expression есть порядком про это (даже слишком, я бы сказал).

Если есть ссылка "популярно для чайников", то с удовольствием просмотрел был.

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


R>Вот вы делаете здесь ту ошибку (чисто формальную, впрочем), от которой я в тексте два раза предостерег — не совсем правильно рассматривать программу на Haskell как *поведение*.


Ее результатом является описание поведения. Если нет, то что по-Вашему является результатом программы?

R>И хотя денотационно ее невозможно полностью описать, поскольку очевидно, что у некоторых функций запросто может быть значение _|_, хотя при этом польза в них есть. У Simon PJ была где-то статья, где он формально определял операционную семантику монад сохраняя при этом полную декларативность. В любом случае, рассматривать программы на хаскеле как автомат будет мало осмысленно. Хотя бы потому что это нам ничего не дает полезного (за что боролись).


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

R>Вся соль в том, что монады позволяют нам вообще не рассматривать программу на хаскеле как что-то единое — их эффекты расслаивают код на множество независимых компонентов каждый из которых является самостоятельной единицей, что само по себе является очень большим достоинством как для формализации, так и для code reuse


R>И спешу сообщить, что пониманий у монад может быть очень много. Главное, что это понимание в каждом конкретном случае можно менять в зависимости от ситуации. В чем и вся соль.


Сложно понимать такие расплывчатые фразы. Но на всякий случай согласшусь.
На самом деле, люди не читают газеты, они принимают их каждое утро, так же как ванну. ©Маршалл Мак-Льюэн
Re: Монады
От: Кодт Россия  
Дата: 05.12.05 00:00
Оценка:
Здравствуйте, reductor, Вы писали:

<>

Кстати, о монадах можно почитать в документе Yet Another Haskell Tutorial (http://www.isi.edu/~hdaume/htut/, http://www.haskell.org/learning.html)
И кстати, do-нотация является не только синтаксическим сахаром, но и имеет формализм.
do
  xxx
  yyy
  zzz

Это как раз стандартный хаскелловский сахар, называемый layout, и он эквивалентен тексту do { xxx ; yyy ; zzz }
точно так же, как и let-in, where, case и т.п.

А вот над do { xxx; yyy; zzz } уже проводятся преобразования...
do {} -- запрещённая конструкция.

-- одиночное действие (монадическая функция)
do action = action
do { action } = action

-- последовательность - ассоциативна
do { statements1 ; statements2 } = do { statements1 } >>= do { statements2 }

-- вводим определение - оно применяется в контексте всех следующий за ним действий
do { let definition ; statements } = (let definition in do { statements })

-- переменная получает значение из действия:
-- нужно не только отдать это значение по конвейеру, но и ввести определение переменной
-- поэтому вводим функцию-обёртку, где var - это имя параметра
do { var <- action ; statements } = action >>= (\var -> do { statements })
  -- это то, о чём ты писал: var <- action [crlf] заменили на action >>= \var -> [crlf]

-- если результат действия сопоставляем с образцом, то функция-обёртка усложняется
do { pattern <- action ; statements }
  let justmatch pattern = do {statements} -- сопоставили и попутно определили переменные образца
      justmatch _       = fail "сопоставление обломилось" -- вернули исключительное значение
  in action >>= justmatch

-- кстати, это значит, что ни let, ни something<- не может быть последним элементом do-списка
Перекуём баги на фичи!
Re[2]: Монады
От: reductor  
Дата: 05.12.05 13:30
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Здравствуйте, reductor, Вы писали:


К><>


К>Кстати, о монадах можно почитать в документе Yet Another Haskell Tutorial (http://www.isi.edu/~hdaume/htut/, http://www.haskell.org/learning.html)

К>И кстати, do-нотация является не только синтаксическим сахаром, но и имеет формализм.

Да понятно, просто это вообще ответ на другое сообщение был.
не знаю как это сюда попало сейчас — здесь, думаю, и так все знают что такое монады и do вместе с list comprehensions и тп, так что получается диковато, действительно.

Я просто попытался объяснить максимально просто в дискуссии там.
Re[3]: Монады
От: Кодт Россия  
Дата: 05.12.05 15:35
Оценка: +2
Здравствуйте, reductor, Вы писали:

R>Да понятно, просто это вообще ответ на другое сообщение был.

R>не знаю как это сюда попало сейчас — здесь, думаю, и так все знают что такое монады и do вместе с list comprehensions и тп, так что получается диковато, действительно.
R>Я просто попытался объяснить максимально просто в дискуссии там.

Модераторы постарались: IT отцепил, а я перенёс сюда.
Потому что:
— Эта информация может быть интересна многим, а не только тем, кто закопался в недра мега-ветки.
— Она представляет самостоятельный интерес и может быть вытащена в изолированный контекст.
— К общефилософии (на мой модераторский взгляд) твой текст имеет меньшее отношение, чем к вопросам подробностей ФЯ (и даже конкретно Haskell).
— Если в философии или ещё где возникнет вопрос про монады — ссылку-то на сообщение всегда можно дать, заодно человек копнёт тематический форум.
— Наконец, не стоит завышать мнение о здешних завсегдатаях (не говоря уже о новичках).
Например, для меня недавно было открытием, что list comprehensions в Haskell является монадическим действием (вплоть до синтаксиса). Как дела с этим обстоят в Python-е — не знаю, нужно подумать, почитать документацию.
Перекуём баги на фичи!
Re[4]: Монады
От: reductor  
Дата: 05.12.05 15:50
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Здравствуйте, reductor, Вы писали:


R>>Да понятно, просто это вообще ответ на другое сообщение был.

R>>не знаю как это сюда попало сейчас — здесь, думаю, и так все знают что такое монады и do вместе с list comprehensions и тп, так что получается диковато, действительно.
R>>Я просто попытался объяснить максимально просто в дискуссии там.

К>Модераторы постарались: IT отцепил, а я перенёс сюда.

К>Например, для меня недавно было открытием, что list comprehensions в Haskell является монадическим действием (вплоть до синтаксиса).
да, это то же самое, что и do, просто для списков, что в общем понятно из-за (<-)
Причем, специальная нотация для списков лично меня нервирует (раньше, кстати, вроде, они были равнозначны с do)

К>Как дела с этим обстоят в Python-е — не знаю, нужно подумать, почитать документацию.

в питоне совершенно ужасно
то есть сначала это был просто наглый хак для парсера и рантайма, потом как-то генерализовали через generator expressions и операцию yield. И это при всем при том, что GvR выкидывает оттуда лямбду и отказался когда-то поддерживать тот же stackless и нормальную раскрутку tail calls (через которые все эти вещи яйца выеденого не стоят).

У меня теперь после всего этого и особенно этого: http://www.artima.com/weblogs/viewpost.jsp?thread=4550 отношение к GvR как к сумасшедшему.
Re[4]: Монады
От: Глеб Алексеев  
Дата: 05.12.05 16:02
Оценка: +1 :)))
Здравствуйте, Кодт, Вы писали:

К>- Наконец, не стоит завышать мнение о здешних завсегдатаях (не говоря уже о новичках).

Вот-вот. В остальных форумах оживление, новички спрашивают, бывалые отвечают, и иногда из вопроса "а что такое указатели" вполне может вырасти интересное обсуждение. А зачем еще форумы?
А здесь — как на совете старейшин индейского племени, собрались гуры и глубокомысленно молчат , всем все ясно. Если новички вроде меня не будут нарушать тишину, форум пропадет (очень ИМХО, своих заслуг на преувеличиваю).
... << RSDN@Home 1.2.0 alpha rev. 619>>
Re[5]: Монады
От: Кодт Россия  
Дата: 05.12.05 16:12
Оценка:
Здравствуйте, reductor, Вы писали:

К>>Например, для меня недавно было открытием, что list comprehensions в Haskell является монадическим действием (вплоть до синтаксиса).


R>да, это то же самое, что и do, просто для списков, что в общем понятно из-за (<-)

R>Причем, специальная нотация для списков лично меня нервирует (раньше, кстати, вроде, они были равнозначны с do)

Не могу сказать, что это "в общем понятно" — раз в одном месте синтаксический сахар насыпали, то и в другом могли.
Например, let-in и let-do — и синтаксически, и по смыслу отличаются, хотя кейворд один и тот же.
Опять же, '|' в кейсах обозначает сопоставления с шаблонами, в функциях — проверки условий, в l.c. — отделяет результат от do-формы. В let/where и do-формах для разделения стейтментов используются ';' а в l.c. — ','
Почему бы не решить, что '<-' значит нечто другое?

Вот это, кстати, меня в Хаскелле и напрягает, и радует одновременно: количество синтаксического сахара.
Радует — потому что язык не страдает пуризмом (как, например, лисп — lots of idiotic silly parenthesis) и поворачивается лицом к кодеру.
Перекуём баги на фичи!
Re[5]: Монады
От: Кодт Россия  
Дата: 05.12.05 16:16
Оценка:
Здравствуйте, reductor, Вы писали:

К>>Например, для меня недавно было открытием, что list comprehensions в Haskell является монадическим действием (вплоть до синтаксиса).

R>да, это то же самое, что и do, просто для списков, что в общем понятно из-за (<-)

Кстати, а как отобразить l.c. в do? Там нужно присобачивать запаковку/распаковку монады, плюс создать цикл (концевую рекурсию)...
Перекуём баги на фичи!
Re[6]: Монады
От: reductor  
Дата: 05.12.05 16:46
Оценка: 24 (1)
Здравствуйте, Кодт, Вы писали:

К>Здравствуйте, reductor, Вы писали:


К>>>Например, для меня недавно было открытием, что list comprehensions в Haskell является монадическим действием (вплоть до синтаксиса).

R>>да, это то же самое, что и do, просто для списков, что в общем понятно из-за (<-)

К>Кстати, а как отобразить l.c. в do? Там нужно присобачивать запаковку/распаковку монады, плюс создать цикл (концевую рекурсию)...


Ага!
Вот почему меня l.c и нервируют так в смысле селективности.
here comes magic
Prelude> do { x <- [1,2,3,4]; return (x + 5) }
[6,7,8,9]


Можно дополнительно посмотреть как определена монада List
Re[6]: Монады
От: reductor  
Дата: 05.12.05 16:55
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Здравствуйте, reductor, Вы писали:


К>>>Например, для меня недавно было открытием, что list comprehensions в Haskell является монадическим действием (вплоть до синтаксиса).


R>>да, это то же самое, что и do, просто для списков, что в общем понятно из-за (<-)

R>>Причем, специальная нотация для списков лично меня нервирует (раньше, кстати, вроде, они были равнозначны с do)

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

К>Например, let-in и let-do — и синтаксически, и по смыслу отличаются, хотя кейворд один и тот же.
К>Опять же, '|' в кейсах обозначает сопоставления с шаблонами, в функциях — проверки условий, в l.c. — отделяет результат от do-формы. В let/where и do-формах для разделения стейтментов используются ';' а в l.c. — ','
К>Почему бы не решить, что '<-' значит нечто другое?

К>Вот это, кстати, меня в Хаскелле и напрягает, и радует одновременно: количество синтаксического сахара.

К>Радует — потому что язык не страдает пуризмом (как, например, лисп — lots of idiotic silly parenthesis) и поворачивается лицом к кодеру.

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

а так вся ж программа на хаскеле — это исключительно \x -> \y -> lots of idiotic silly arrows
И нет ничего через лямбду невыразимого.
ну немножко с сахаром, в лиспе он тоже есть — то же loop macro, например.
не считая огромного количества символьных макросов типа того же '() — (quote)
Re[7]: Монады
От: Кодт Россия  
Дата: 05.12.05 17:43
Оценка:
Здравствуйте, reductor, Вы писали:

R>here comes magic


Белый шаман!

Я так понимаю, List специально сделали монадой (да ещё и монадой+), чтобы можно было в l.c. играть?
Не, ну до чего затейливо и сумрачно!
Особенно прикольно вместо return напрямую конструировать список.
A> do { x<-[1..3]; [x*2,x*3] }
[2,3,4,6,6,9]

-- а может быть, я хотел вот это, да попал в затмение
do { x<-[1..3]; return [x*2,x*3] }
[[2,3],[4,6],[6,9]]

-- ну допустим, типизация нас спасла бы выше... а вот здесь
do { x<-[1..3]; return [] }
[[],[],[]]

do { x<-[1..3]; [] } -- признак 'fail'
[]

Вообще-то, это бардак — когда конструкторы и операторы монад (return, fail, mplus и т.п.) выражаются одним термом (да ещё и встроенным в синтаксис): [x], [], ++
Такое раздолье для ошибок.
Перекуём баги на фичи!
Re[8]: Монады
От: reductor  
Дата: 05.12.05 18:28
Оценка: 1 (1)
Здравствуйте, Кодт, Вы писали:

К>Здравствуйте, reductor, Вы писали:


R>>here comes magic


К>Белый шаман!


К>Я так понимаю, List специально сделали монадой (да ещё и монадой+), чтобы можно было в l.c. играть?

К>Не, ну до чего затейливо и сумрачно!

Да нет, просто монады хорошо формализуют это дело


К>Особенно прикольно вместо return напрямую конструировать список.

К>
К>-- ну допустим, типизация нас спасла бы выше... а вот здесь
К>do { x<-[1..3]; return [] }
К>[[],[],[]]


здесь кстати типизация тоже спасет — не в пустоту же пойдет [a]] вместо [Integer]

No instance for (Num [a])

К>do { x<-[1..3]; [] } -- признак 'fail'
К>[]

А вот и разгадка операции [] :)

К>Вообще-то, это бардак — когда конструкторы и операторы монад (return, fail, mplus и т.п.) выражаются одним термом (да ещё и встроенным в синтаксис): [x], [], ++

К>Такое раздолье для ошибок.

Да нет, как раз ошибки-то в хаскеле сложно совершать с такой формализируемостью.
Но лично мне не нравится, что я должен выделять те же списки из остальных монад.
А вот операторы там и синонимы смущают не очень сильно — как-то двусмысленностей на самом деле нет, иначе бы формально это все не прошло.
Re[9]: Монады
От: Кодт Россия  
Дата: 05.12.05 18:44
Оценка:
Здравствуйте, reductor, Вы писали:

К>>Особенно прикольно вместо return напрямую конструировать список.

К>>
К>>-- ну допустим, типизация нас спасла бы выше... а вот здесь
К>>do { x<-[1..3]; return [] }
К>>[[],[],[]]
R>

R>здесь кстати типизация тоже спасет — не в пустоту же пойдет [ [a] ] вместо [Integer]

А кто сказал, что я там хочу [Integer]. Я, может быть, как раз хотел получить список из трёх пустых списков типа... ну мало ли какого типа. Пусть будет [(Integer,Integer)]].

Потом забыл записать return — и получилось, что я записал fail!
[] :: [a], где a может быть равен [(Integer,Integer)], правда же?
Перекуём баги на фичи!
Re[10]: Монады
От: reductor  
Дата: 05.12.05 19:26
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Здравствуйте, reductor, Вы писали:


К>>>Особенно прикольно вместо return напрямую конструировать список.

К>>>
К>>>-- ну допустим, типизация нас спасла бы выше... а вот здесь
К>>>do { x<-[1..3]; return [] }
К>>>[[],[],[]]
R>>

R>>здесь кстати типизация тоже спасет — не в пустоту же пойдет [ [a] ] вместо [Integer]

К>А кто сказал, что я там хочу [Integer]. Я, может быть, как раз хотел получить список из трёх пустых списков типа... ну мало ли какого типа. Пусть будет [(Integer,Integer)]].


Так просто ни формализм хаскеля ни его систему типов не обмануть. То есть случайным образом не обмануть точно :)

К>Потом забыл записать return — и получилось, что я записал fail!

так это определено:
instance  Monad []  where
    m >>= k          = concat (map k m)
    return x         = [x]
    fail s           = []

К>[] :: [a], где a может быть равен [(Integer,Integer)], правда же?

Да, [a] матчится на [(Integer,Integer)], но [[],[],[]] уже не матчится на [()].

в случае же с [] без return — это явно прописаный fail иначе бы это в свою очередь не совпало с определением списка как
data  [a]  =  [] | a : [a]

где пустая клетка — конец списка

точно так же список записывается просто так:
1 : 2 : 3 : []


Где без явного fail в конце система типов нас грязно обругает
так же:
Prelude> map (:[]) [1,2,3]
[[1],[2],[3]]
Prelude> foldr (:) [] [1,2,3]
[1,2,3]


Вообще такие вещи через пару недель плотного программирования на хаскеле просто ложатся в подкорку и выводятся потом автоматически
А пропустить это невозможно как и в случае с явным определением fail pattern в рекурсивных функция для списков:
f <fail pattern> = []
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.