LINUX.ORG.RU

Монады vs макросы vs фекспры

 , , ,


3

4

Эти все вещи имеют нечто общее — контроль над порядком вычислений. Однако, почему то, я никогда не видел сравнения их с этой точки зрения. Постараюсь восполнить этот пробел, а вы дополните, или возразите. Я привожу градацию в пороядке «от сильного к слабому»

1) fexprs. Имеют полный контроль над вычислениями.

2) macros. Имеют контроль над вычислениями, ограниченный временем компиляции.

3) monads. То же самое, что п. 2, за исключением того, что в теле функции невозможно получить само выражение аргумент, «как он есть», а лишь его вычисленное значение.

Возможно я ошибаюсь, поэтому дополняйте и исправляйте.

Ответ на: комментарий от qnikst

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

Miguel ★★★★★
()
Ответ на: комментарий от qnikst

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

То есть имелись ввиду не все монады, а «имеющие эффекты» и только они, всякие cont, maybe, list, reader и т.п. к ним не относятся, ок. Но что значит «имеющие эффекты монады»? А то мне так кажется, что у тебя выходит тавтология - «детерменируют эффекты те монады, которые детерменируют эффекты».

anonymous
()
Ответ на: комментарий от Miguel

Во-первых, Maybe - это монада вне зависимости от меня лично.

Maybe это много чего ещё, но пока ты не используешь это «много чего ещё» данное знание абсолютно не важно.

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

не любая, а та, которой нету в более слабых структурах.

И?

пока в список попало все, что имеет observable effect..

qnikst ★★★★★
()
Ответ на: комментарий от qnikst

Maybe это много чего ещё, но пока ты не используешь это «много чего ещё» данное знание абсолютно не важно.

Что не отменяет его существования.

не любая, а та, которой нету в более слабых структурах.

Почему это?

пока в список попало все, что имеет observable effect.

И? Вон, тебе привели пример, что эффекты в State, скажем, могут в совсем другом порядке выполняться, чем они написаны.

Miguel ★★★★★
()
Ответ на: комментарий от qnikst

Кстати, можно написать монаду ИО так, что она не будет задавать последовательность вычислений.

anonymous
()
Ответ на: комментарий от Miguel

И? Вон, тебе привели пример, что эффекты в State, скажем, могут в совсем другом порядке выполняться, чем они написаны.

конкретно тот пример не показателен и не доказывает мою неправоту, но его легко можно модифицировать до варианта, который оспаривает:

import Debug.Trace
import Control.Applicative
import Control.Monad.State.Lazy

data Foo a b = Foo { a :: a, b :: b }

main = do
  let (r, s) = (flip runState) (Foo 1 1) $ do
          trace "effect1" (modify (\x -> x { a = trace "Should be 1st, right?" $ 2}))
          trace "effect2" (modify (\x -> x { b = trace "Should be 2nd, right?" $ 1}))
  print $ b s
  print $ a s
effect2
effect1
Should be 2nd, right?
1
Should be 1st, right?
2

Почему не оспаривает предыдущий - т.к. эффект это имеено modify.

qnikst ★★★★★
()
Ответ на: комментарий от qnikst
#lang lazy

(define (return x)
  `',x)

(define (>>= x f)
  (f (eval x)))

(define readln '(read))

(define main (readln . >>= . (λ (x) (return 'huy))))

> (eval main)
'huy
> 

вот тебе ИО-монада, readln не сработало.

anonymous
()
Ответ на: комментарий от qnikst

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

import Debug.Trace
import Control.Applicative
import Control.Monad.State.Lazy

data Foo a b = Foo { a :: a, b :: b }

main = do
  let (r, s) = (flip runState) (Foo 1 1) $ do
          trace "effect1" (modify (\x -> x { a = trace ("Expecting 1, but got "++show (b x)) $ 2}))
	  trace "effect2" (modify (\x -> x { b = trace ("Expecting 2, but got "++show (a x)) $ 2}))
  print $ b s
  print $ a s

мы получаем почти ожидаемый результат:

effect2
effect1
Expecting 1, but got 1
Expecting 2, but got 2
2
2

Собственно при вычислении effect2 мы поднимаем все эффекты от которых мы зависим и это именно то, что дает монадический интерфейс по сравнению с аппликативным функтором.

qnikst ★★★★★
()
Ответ на: комментарий от qnikst

Кстати за исключением лени оно в точности повторяет ИО из хаскеля. Чтобы было совсем как в хаскеле надо так:

(define (>>= x f)
  (define res (! (eval x)))
  (f res))
В этом случае абсолютно точное соответствие в семантике.

Собственно, именно этот момент (форс аргумента в бинде) и приводит к тому, что ИО в хаскеле «детерменирует» чего-то там - ведь любое вычисление эта цепочка строгих по биндов, контрол-флоу разворачивается в a `seq` b `seq` c ...

anonymous
()
Ответ на: комментарий от qnikst

и это именно то, что дает монадический интерфейс по сравнению с аппликативным функтором.

И опять же, контрпример остаётся контрпримером, вне зависимости от того, нравится он тебе, или нет.

Miguel ★★★★★
()
Ответ на: комментарий от Miguel

вопрос в том выполнится ли эффект modify до или нет, а не про вычисления внутри modify. Пример fmdw говорит о том втором, никак не оспаривая первое. Модифицированный пример говорит показывает то, что в мои слова нужно внести уточнения.

Смотри у нас есть do modify f >> modify g мы получаем S1, и S2,

в итоге мы имеем (g (f x)), при форсировании вычислении g вычислятся f, таким образом мы получаем:

1. запустили эффект g (увидели trace) 2. запустили эффект f (увидели trace) 3. эффект f выполнен 4. эффект g выполнен

Итого неожиданным образом эффект f выполнен до g, как и должно быть. Существуют ли примеры где это не так?

qnikst ★★★★★
()
Ответ на: комментарий от Miguel

И опять же, контрпример остаётся контрпримером, вне зависимости от того, нравится он тебе, или нет.

я думаю если ты сядешь и минут 15 подумаешь над тем, что я написал, то до тебя дойдет, ты все таки не глупый человек.

qnikst ★★★★★
()
Ответ на: комментарий от anonymous

не... вроде аналог return undefined >>= const return 5 попортишь, в haskell результат не форсится, а цепочка благодаря тасканию RealWorld.

qnikst ★★★★★
()
Ответ на: комментарий от qnikst

в итоге мы имеем (g (f x)), при форсировании вычислении g вычислятся f,

Э... не обязательно. Например, если g = const что-то.

Miguel ★★★★★
()
Ответ на: комментарий от Miguel

Если g==const, то неожиданно, но это аппликативный функтор! Таким образом ты не используешь то, что это монада. Если ты говоришь, что все у чего есть инстанс monad это монада вне зависимости от использования и если ты говоришь, что если мы используем return и bind даже если тут можно использовать более простой интерфейс - то это монада, то спор можно прекращать в виду того, что ты не понял то что я говорю.

qnikst ★★★★★
()
Ответ на: комментарий от fmdw

Ты путаешь entry point исполняемого файла и функцию, запускающую IO

Был вопрос про выражения верхнего уровня в лиспе. Они как раз являются последовательностью точек входа (entry point). Для Haskell, так же как и для Си точка входа (она же top level) одна — main.

Дёрни эту функцию из REPL.

REPL запускает функции, а не программы на Haskell. Из REPL я могу и просто «putStr „ok“» дёрнуть.

monk ★★★★★
()
Ответ на: комментарий от qnikst

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

Это одно и то же, чтобы протащить цепочку надо форсировать результат. undefined же имеет тип a, не IO a, то есть

(define undefined (delay (error "undefined")))

(define (>> x y)
  (x . >>= . (λ (x) y)))

(define main ((return undefined) . >>= . (λ (x) (return 'huy))))
(define main2 ((writeln 1) . >> . (writeln 2)))

->
> (eval main) ;не форсится
'huy
> (eval main2) ;форсится
1
2
> (eval undefined) ;семантика undefined соблюдена
. . ..\..\..\..\Program Files\Racket\collects\racket\private\promise.rkt:53:8: undefined
> 
anonymous
()
Ответ на: комментарий от qnikst

Если g==const, то неожиданно, но это аппликативный функтор!

Это вообще бессмысленная фраза.

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

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

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

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

Miguel ★★★★★
()
Ответ на: комментарий от monk

Репл в хаскеле запускает ио-термы, фактически так же как репл в лиспе окружает все евалом, репл в хаскеле окружает все через unsafePerformIO

То есть когда ты вводишь чтото в репл в хаскеле, то делается: unsafePerformIO . eval . read, где eval - евал кода на хаскеле, unsafePerformIO - евал ио-термов. В лиспе: eval . expand . read.

Они как раз являются последовательностью точек входа (entry point). Для Haskell, так же как и для Си точка входа (она же top level) одна — main.

Разница в том, что в лиспе значение main - это кусок кода, который идет на евал, а в лиспе значение формы - это уже сразу результат работы программы, который никуда не идет (ну на печать разве что).

anonymous
()
Ответ на: комментарий от anonymous
> (haskell-putStr "yoba")
.#<syntax:4:6 (displayln "yoba")>

ghci:
Prelude> putStr "yoba"
yobaPrelude>

Как мне в Haskell получить .#<syntax...>, если ты говоришь, что там все функции возвращают синтаксис? Пока я вижу, что и в ghci и в main работа функции putStr аналогична lisp-putStr

monk ★★★★★
()
Ответ на: комментарий от qnikst

Я утверждаю, что слова «использовать, то, что это монада» означают ровно «использовать функции, определённый в классе Monad».

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

Наконец, я утверждаю, что это ты должен говорить то, что имеешь в виду, а не другие - догадываться.

Miguel ★★★★★
()
Ответ на: комментарий от qnikst

Значит я понял верно - твоя очередь думать.

О чём?

Miguel ★★★★★
()
Ответ на: комментарий от anonymous

а в лиспе значение формы - это уже сразу результат работы программы

Да ну? Значение формы идёт на (eval (expand (read форма))). И уже результат eval будет результатом работы программы. А без этого форма просто кусок кода (ничем не отличается от Haskell).

monk ★★★★★
()
Ответ на: комментарий от qnikst

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

anonymous
()
Ответ на: комментарий от monk

Да ну? Значение формы идёт на (eval (expand (read форма))).

Туда идет форма, а не значение формы. Значение формы - это результат (eval (expand (read форма))).

В хаскеле «форма» - это код на хаскеле, «значение формы» - ИО-терм, чтобы получить ИО-терм, надо заевалить форму. А потом этот ИО-терм надо заевалить, чтобы произвести сайд-эффекты.

anonymous
()
Ответ на: комментарий от Miguel

Я утверждаю, что слова «использовать, то, что это монада» означают ровно «использовать функции, определённый в классе Monad».

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

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

Наконец, я утверждаю, что это ты должен говорить то, что имеешь в виду, а не другие - догадываться.

взаимно.

О чём?

я недвусмысленно об этом написал выше.

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

qnikst ★★★★★
()
Ответ на: комментарий от monk

Они как раз являются последовательностью точек входа (entry point).

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

fmdw
()
Ответ на: комментарий от monk

Как мне в Haskell получить .#<syntax...>, если ты говоришь, что там все функции возвращают синтаксис?

В репле хаскеля когда возвращается ИО, то оно форcится через unsafePerformIO, то есть это то же самое что в ракетке сделать дополнительный евал (в хаскеле два разных евала, т.к. языки разные, в ракетке два раза применяем один и тот же, т.к. один язык).

Как мне в Haskell получить .#<syntax...>, если ты говоришь, что там все функции возвращают синтаксис?

Хаскель его не умеет выводить, но если ты как-то сделаешь для IO a инстанс show, то пожалуйста.

anonymous
()
Ответ на: комментарий от qnikst

взаимно.

Как ты сам сказал, ты меня вполне понимаешь. Так что к чему это было?

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

Оно от этого не стало относиться к делу.

Можешь ли ты внятно ответить, чем отличается Monad от Applicative и что оно вносит по сравнению с первым.

Да, разумеется. Monad - подкласс Applicative; строгий подкласс, ибо существуют инстансы второго, не являющиеся (и не могущие быть сделанными) инстансами класса Monad. Принципиальное отличие Monad от Applicative состоит в том, что первый, комбинируя функцию и её аргумент, позволяет сделать «эффекты» (в кавычках, чтобы не думать про IO) этой функции зависящими от значения аргумента. Applicative этого не позволяет.

Собственно, всё.

Miguel ★★★★★
()
Последнее исправление: Miguel (всего исправлений: 1)
Ответ на: комментарий от fmdw

последовательностью точек входа

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

Они последовательность, а не неупорядоченное множество. А надо это, чтобы main не писать. Наткнулись на любое выражение при чтении файла, выполнили. После этого читаем следующее выражение (или не читаем, если при выполнении произошла ошибка). Эдакая смесь плюсов интерпретатора (выражения выполняются сразу после чтения) и компилятора (каждое выражение компилируется).

monk ★★★★★
()
Ответ на: комментарий от anonymous

в хаскеле два разных евала, т.к. языки разные, в ракетке два раза применяем один и тот же, т.к. один язык

В ракетке тоже два разных. Тот который даёт значение в монаде IO, в ракетке называется expand, а тот который в ракетке eval, в хаскеле будет unsafePerformIO.

monk ★★★★★
()
Ответ на: комментарий от Miguel

Как ты сам сказал, ты меня вполне понимаешь. Так что к чему это было?

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

Да, разумеется. Monad - подкласс Applicative; строгий подкласс, ибо существуют инстансы второго, не являющиеся (и не могущие быть сделанными) инстансами класса Monad. Принципиальное отличие Monad от Applicative состоит в том, что первый, комбинируя функцию и её аргумент, позволяет сделать «эффекты» (в кавычках, чтобы не думать про IO) этой функции зависящими от значения аргумента. Applicative этого не позволяет.

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

а то получается чушь какая-то

..первый комбинируя функцию (:: a -> m b) и её аргумент (?) (аргумент функции это a, а аргумент у bind m a, что мы с чем комбинируем?) позволяет сделать «эффекты» этой функции зависящими от аргумента.

а то (#) :: a -> (a -> m a) -> m a тоже попадает под твоё определение.

qnikst ★★★★★
()
Последнее исправление: qnikst (всего исправлений: 1)
Ответ на: комментарий от anonymous

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

qnikst ★★★★★
()
Ответ на: комментарий от monk

В ракетке тоже два разных. Тот который даёт значение в монаде IO, в ракетке называется expand, а тот который в ракетке eval, в хаскеле будет unsafePerformIO.

Да, экспанд и евал хаскеля во много похожи (и то и то, фактически, формирует вычисление, которое потом будет запущено), отличия два.

Во-первых экспанд ракетки возвращает код на ракетке, экспанд хаскеля - возвращает некое _вычисление_, которое, в реальности, неизвестно как представлено (да, это может быть код на каком-то ЯП, может быть лямбда, может быть то или иное представление cfg - здесь нету никакой спецификации, для анализа мы можем выбрать любой из эквивалентных способов), но оно явно не представлено кодом на хаскеле (потому что код на хаскеле не умеет ввод/вывод, он умеет только в генерацию вычислений, которые делают ввод/вывод).

Во-вторых - они существенно отличаются алгоритмически. То что происходит в хаскеле в ракетке будет выглядеть так:

#lang racket

(begin-for-syntax
  *code*
  
  (define main *code*))

(define-syntax (main stx) main)
main

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

anonymous
()
Ответ на: комментарий от qnikst

#lang lazy

(require racket/promise)

(define (return x)
  #`'#,x)

(define (>>= x f)
  (define res (! (eval x)))
  (f res))

(define (>> x y)
  (x . >>= . (λ (x) y)))

(define readln #'(read))
(define (writeln x)
  #`(displayln #,x))

(define undefined (delay (error "undefined")))

(define main ((return undefined) . >>= . (λ (x) (return 'huy))))
(define main2 ((writeln 1) . >> . (writeln 2)))

потом в репле руками евалишь main'ы писать их в репле не надо, т.к. там будет лишний форс.

и ты учти, что на самом деле это костыльно - для некостыльной реализации надо уметь делать ленивый syntax (т.к. cfg может быть с циклами), а его в ракетке нет, мой вариант через lazy - work-around, но я уверен, что неполноценный, и бинд из-за этого тоже сделан немного не так, как _должен_. Но существенные моменты остаются те же.

anonymous
()
Ответ на: комментарий от Miguel

Принципиальное отличие Monad от Applicative состоит в том, что первый, комбинируя функцию и её аргумент, позволяет сделать «эффекты» (в кавычках, чтобы не думать про IO) этой функции зависящими от значения аргумента. Applicative этого не позволяет.

fmap позволяет поднимать ф-ю в функтор «с двух сторон», то есть погружать в монаду любой код вида f ((t g) (h (x))), аппликатив позволяет поднимать ф-и многих аргументов, то есть любой код вида f (x (g (p h) y) z).

return позволяет поднимать ф-ю «справа» (делает m a -> m b из m a -> b), bind позволяет поднимать ф-ю «слева» (m a -> m b из a -> m b), другими словами монада позволяет «доподнять» код, который наполовину поднят, наполовину - нет.

anonymous
()
Ответ на: комментарий от qnikst

Просто в файл и run, хз. Не забыть поставить язык #lang lazy и сделать «выбор языка в файле»

anonymous
()
Ответ на: комментарий от anonymous

То есть аппликатив поднимает _любой_ код, но полностью. Монада доподнимает _любой код_ (если с аппликативом или код того же вида что в случае с fmap если не пользуемся аппликативом), не важно частично или полностью.

anonymous
()
Ответ на: комментарий от qnikst

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

И не могу. Удалят.

тоже попадает под твоё определение.

Я не приводил определения. Определение - это декларация class Monad (соотв. class Applicative). Я описал всего лишь, что нужно иметь в виду, когда с этим работаешь. Всё.

Miguel ★★★★★
()
Ответ на: комментарий от anonymous

Во-первых экспанд ракетки возвращает код на ракетке

Ладно, дпя полной аналогии пусть будет (eval (compile form)).

потому что код на хаскеле не умеет ввод/вывод, он умеет только в генерацию вычислений, которые делают ввод/вывод

В ракетке, положим, ввод/вывод тоже является foreign. В смысле. написан на Си.

Во-вторых - они существенно отличаются алгоритмически.

Интересно, #lang lazy ты тоже так воспринимаешь? Если нет, то чем ленивые f x y = x + y в Haskell отличается от (define ((f x) y) (+ x y)) в Lazy Racket?

monk ★★★★★
()
Ответ на: комментарий от monk

Ладно, дпя полной аналогии пусть будет (eval (compile form)).

Опять нет. Ты понимаешь, чем процесс компиляции отличается от процесса исполнения?

В ракетке, положим, ввод/вывод тоже является foreign. В смысле. написан на Си.

Смысл в том что в ракетке есть ф-и, которые в соответствии со спецификацией что-то выводят/читают и т.п.

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

А вот в хаскеле таких функций нет. Там есть ф-и, которые в соответствии со спецификацией возвращают вычисление. Но нет ф-й, которые читают/пишут.

Интересно, #lang lazy ты тоже так воспринимаешь? Если нет, то чем ленивые f x y = x + y в Haskell отличается от (define ((f x) y) (+ x y)) в Lazy Racket?

Ничем, это же чистые функции. Они одинаковы. Отличаются ф-и, которые возвращают IO a - в ракетке они выполняют непосредственно какие-то эффекты (запускаются автоматом), а в хаскеле - это вычисления, котоыре еще нужно запустить.

anonymous
()
Ответ на: комментарий от Miguel

И не могу. Удалят.

печально.

Я не приводил определения. Определение - это декларация class Monad (соотв. class Applicative). Я описал всего лишь, что нужно иметь в виду, когда с этим работаешь. Всё.

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

qnikst ★★★★★
()
Последнее исправление: qnikst (всего исправлений: 1)
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.