LINUX.ORG.RU

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

 , , ,


3

4

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

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

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

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

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

Ответ на: комментарий от terminator-101

Все энергично и предсказуемо вычислено. Какие проблемы? Что ты тут собирался имитировать?

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

вычислено.

((lambda()(write 'side-effect)))

Не вычислено, в случае test2

Все энергично и предсказуемо

Да, все энергично и предсказуемо, точно также как в call-by-need стратегии вычислений, все нормально и предсказуемо. В соответствии с нормальным порядком редукций.

terminator-101
() автор топика
Ответ на: комментарий от terminator-101

Лямбды и функции вообще по определению в нормальной форме, как литерал 5, например, так как они относятся к значениям (термам в нормальной форме которые дальше уже не редуцируются) — их можно передавать, возвращать, сохранять в переменных и т.п. как значения без вычисления тела. Аппликация лямбды или функции — уже нет, требует вычисления тела, выполнения эффектов и т.п., так как не значение в нормальной форме, а терм который нужно редуцировать.

Вообще, такая лямбда это же просто функция:

void test() {
  puts("nothing");
}

int main() {
  auto foo = test; // не печатает, ёпта :)
  test(); // ок
}

её нормальной формой будет её адрес (у замыкания / функтора ещё и ассоциированные данные) — если его передавать / возвращать / сохранять в переменной, то естественно, что нечего происходить не будет, редукция / вычисление / вызов по адресу / вызов функтора происходит... при вызове :) то есть при аппликации.

motto
()
Ответ на: комментарий от terminator-101

Вот именно в этом смысле она не вычисляется.

Все аргументы абсолютно всегда вычисляются до нормальной формы. Если считаешь иначе - приведи пример, где это не так.

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

((lambda()(write 'side-effect)))
Не вычислено, в случае test2

Такого терма там нет, вот он и не вычислен.

Да, все энергично и предсказуемо, точно также как в call-by-need стратегии вычислений

Еще раз - в call-by-need аргумент может быть вычислен когда угодно. Или вовсе не быть вычислен. Где здесь предсказуемость? В энергичной модели аргумент всегда вычисляется сразу. Без исключений.

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

в (lambda () side-effect) ничего редуцировать не нужно, это УЖЕ нормальная форма, которая УЖЕ вычислена. Ее некуда дальше вычислять.

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

Все аргументы абсолютно всегда вычисляются до нормальной формы

У терма если есть нормальная форма, она всегда может быть получена при помощи нормальной стратегии. А о чем ты говоришь, я хз. Попробуй вычислить до нормальной формы вот такой терм ((lambda(x) y)(lambda(x) (x x))(lambda(x) (x x))) c помощью аппликативных вычислений.

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

Еще раз - в call-by-need аргумент может быть вычислен когда угодно.

Еще раз, в call-by-need аргумент вычисляется не когда угодно, а в соответствии с порядком редукции call-by-need.

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

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

terminator-101
() автор топика
Ответ на: комментарий от terminator-101

А о чем ты говоришь, я хз.

Я говорю о том, что при энергичной стратегии терм ВСЕГДА вычисляется до нормальной формы (когда она есть) до вызова ф-и. При нормальной стратегии терм может быть вычислен когда угодно или вообще не вычислен - у тебя нет никакого способа предсказать поведение.

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

Еще раз, в call-by-need аргумент вычисляется не когда угодно, а в соответствии с порядком редукции call-by-need.

Да. Но предсказать когда эта редукция произойдет (и произойдет ли вообще) - ты не можешь.

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

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

Все знают как выразить нормальную стратегию в аппликативной. Но нормальная стратегия так и остается непредсказуемой, а энергичная - предсказуемой.

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

Но нормальная стратегия так и остается непредсказуемой

(g f), где f функция, содержащая сайд-эффект. Будет ли произведен сайд-эффект?

terminator-101
() автор топика
Ответ на: комментарий от terminator-101

lambda это та же спец. форма (как if) которая вне зависимости от порядка есть просто готовое значение в любом языке. При нестрогой стратегии это всё равно значение (например, тогда как вызов — THUNK -> BLACKHOLE -> WHNF/NF -> ... по мере вычислений, алсо).

В принципе мы либо имеем строгий порядок и опциональную нестрогость (что само по себе оставляет порядок на откуп реализации — лень, оптимистик, ...), либо наоборот, чего достаточно для большинства случаев, так что необходимости как-то тюнить порядок нет (либо ты напишешь интерпретатор). К монадам это не имеет отношения — в их коде ты выбираешь стратегию так же, как и в остальных местах (разные по строгости версии монадических контейнеров / трансформеров, IO c unsafeInterleaveIO, ...).

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

Но любая программа на Racket также возвращает байткод для JIT'а

представь, что у тебя есть полностью аппаратная реализация лиспа. Лисп-машина. Необязательно возвращать код для JITа, оно всё исполнится «само». Сам код на лиспе - это уже инструкции процессора.

А в случае с Хаскелем пришлось бы писать не одну аппаратную Хаскель-машину, а целых две: Хаскель-машину и IO-машину, и вот только обе вместе они достигли бы результата. Печально, что получается 2 разных машины. Печально что на Хаскель-машину будет строгий стандарт, а на IO-машину - уже нет.

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

Плюсую. Я уже говорил об этом тут раза 2, никто не понял. Лисп является виртульной машиной сам для себя:)

terminator-101
() автор топика
Ответ на: комментарий от stevejobs

Печально, что получается 2 разных машины. Печально что на Хаскель-машину будет строгий стандарт, а на IO-машину - уже нет.

Тут, кстати, печальней всего то, что хаскель не может быть реализован в рамках семантики самого хаскеля. Это, как бы, неполноценный язык, получается, ему нужна подпорка. Хрен знает, еще, можно ли считать его тьюринг полным.

terminator-101
() автор топика
Ответ на: комментарий от qnikst

Определения - это то, что написано в коде. А не то, что ты думаешь об этих определениях, основываясь на своих знаниях предметной области. У себя в голове ты можешь иметь высокоуровневые инструкции типа «если преимущества конструкции не используются, то нужно использовать другую инструкцию». Но этот язык звучит только у тебя в голове!

Пример из другой области: у меня в голове есть голос, который вещает, что нужно писать на Java с использованием паттернов проектирования и прочих хороших практик. Но в самом языке Java никаких паттернов проектирования нет, из языка их наличие не выводится никак. Нельзя сказать, что раз код написан в нарушение вообще всех хороших практик и «здравого смысла», что этот код перестал быть кодом на Java.

stevejobs ★★★★☆
()
Ответ на: комментарий от terminator-101

(g f), где f функция, содержащая сайд-эффект. Будет ли произведен сайд-эффект?

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

В данном случае f совершенно точно будет вычислено. Но оно и так в нормальной форме, то есть вычислять его куда-то дальше не нужно (да и невозможно).

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

В случае нормального порядка - хрен его знает.

В десятый раз: не хрен его знает, а в соответствии с правилами редукции. Может вычислитель тоже не знает, как и когда вычислять твои выражения?

В данном случае f совершенно точно будет вычислено

Вопрос был не в этом. Я спрашиваю будет ли побочный эффект. А «точно вычислено», я уже писал, что да, в call-by-need вызов функции тоже будет совершенно точно вычислен в санк, только это не о чем. Ты писал, о якобы принципиальном различии, что якобы, если у нас нормальный порядок редукци, то порядка нет (парадокс, да? порядок==беспорядок), а при аппликативном порядке тебе все ясно. Ну если ясно, значит тебе не трудно будет ответить на поставленный вопрос.

terminator-101
() автор топика
Ответ на: комментарий от stevejobs

А в случае с Хаскелем пришлось бы писать не одну аппаратную Хаскель-машину, а целых две: Хаскель-машину и IO-машину

type Addr = Int
type Word = Int
type Mem = Addr -> Word
type IO a = Mem -> (a, Mem)

ret :: a -> IO a
ret = (,)

bind :: IO a -> (a -> IO b) -> IO b
bind io f = uncurry f . io

next :: IO a -> IO b -> IO b
next io io' = io `bind` const io'

run :: IO a -> Mem -> a
run io = fst . io

put :: Addr -> Word -> IO ()
put addr val mem = ((), \addr' -> if addr == addr' then val else mem addr')

get :: Addr -> IO Word
get addr mem = (mem addr, mem)

t = run (put 1 2 `next` put 2 3 `next` get 1 `bind` \x -> get 2 `bind` \y -> ret $ x + y) $ const 0
-- => 5

^ достаточно (unsafe) mock-нуть Mem и подставлять run перед main.

motto
()
Ответ на: комментарий от terminator-101

Терминахтер, ну ты хоть погугли что такое thunk. Зачем бредом несвежим на людях обмазываться?

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

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

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

terminator-101
() автор топика
Ответ на: комментарий от terminator-101

В десятый раз: не хрен его знает, а в соответствии с правилами редукции.

Передавай санк в функцию которая его вычислит после того как решит очередное диофантово уравнение прочитанное из файла тогда :)

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

Я не совсем понял, но при семантике by-need, санк передается неявно в функцию, на уровне исполнителя. Санк, в данном случае, это подковерный объект, скрытый от явных манипуляций им. То есть, любой аргумент оборачивается в санк.

terminator-101
() автор топика
Ответ на: комментарий от terminator-101

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

ты не стесняйся, источники предъявляй, застенчивый ты наш

shty ★★★★★
()
Последнее исправление: shty (всего исправлений: 1)
Ответ на: комментарий от terminator-101

То есть, любой аргумент оборачивается в санк.

иногда лучше жевать

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

по автору можно догадаться, это или Википедия, или SICP, или высказывания создателей языка, или что-то такое

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

или Википедия

Да нет, я не считаю что Википедия так уж заслуживает доверия:) Это SICP

terminator-101
() автор топика
Ответ на: комментарий от terminator-101

ну что ты привязался, я - глупый, мне можно всякое говорить

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

shty ★★★★★
()
Ответ на: комментарий от terminator-101

мне неинтересно тупняк разводить

смелое заявление, энергичное

shty ★★★★★
()
Последнее исправление: shty (всего исправлений: 1)
Ответ на: комментарий от terminator-101

Это к тому, что, например

q x = gt 4 where
  gt n = if np 2 then force x else gt $ n + 2 where
    np a | a > n `div` 2 = True
         | isPrime a && isPrime (n - a) = False
         | otherwise = np $ a + 1

довольно проблематично сказать, когда будет зафорсен санк x. Тогда как при строгости ясно — ровно до вызова q.

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

Справедливое замечание, я на него ответить не смогу, врочем в статьях по монадам в ФП, приводится и такое определение монад. Так же join и bind связаны как join m = m >>= id, m >>= g = join (fmap g m), ну и в общем-то я не о порядке вычислений, а о порядке применения эффектов.. В общем как я уже сказал, для того, чтобы определить точно мне нужно как следует все сформулировать и возможно ослабить утверждение, мне это делать лень, особенно в присутсвие всяких неадекватных личностей типа Мигеля..

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

достаточно (unsafe) mock-нуть Mem и подставлять run перед main.

И каким образом ты собрался копировать вселенную?

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

В десятый раз: не хрен его знает, а в соответствии с правилами редукции.

Но из правил редукции нельзя это вывести. Если у тебя выражение f ( (g x), то ты не знаешь когда вычислитcя g x (и на сколько оно вычислится).

Вопрос был не в этом.

Вопрос был в этом. Энергичный порядок гарантирует вычисления в заданный момент времени - по-этому он предсказуем. Нормальный порядок ничего не гарантирует - аргумент может быть вычислен когда угодно и на сколько угодно.

Ты писал, о якобы принципиальном различии, что якобы, если у нас нормальный порядок редукци, то порядка нет (парадокс, да? порядок==беспорядок), а при аппликативном порядке тебе все ясно.

Именно так. Еще раз, у тебя форма (g x), ты знаешь, когда и насколько будет вычислено х? Да. В ленивом порядке? Нет. Вот и все.

Ну если ясно, значит тебе не трудно будет ответить на поставленный вопрос.

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

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

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

Так а кто тебе мешает написать бинд, который будет совершать эффекты параллельно? Допустим у нас есть:

let f = (printLn "yo!") >> readLn
let g x = (printLn "ya!") >> (printLn x)
main = f >>= g x

понятно, что чтобы вывести х надо считать информацию, но вот вывод «yo!» и «ya!» может быть сделан в произвольном порядке. В частности, мы можем написать бинд, который будет делать это параллельно - и это будет корректная монада с корректным биндом. Тот факт, что в стандартном IO «yo!» выводится раньше - заслуга конкретной реализации этог обинда, а не самой монадической структуры.

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

Напиши пожалуйста такой бинд - вот такой аппликативный функтор я написал, моё утверждение - такой бинд написать невозможно, тут фишка в том, что ты использовал (>>), который на самом деле операция из более слабой структуры (*>) далее читаем то, что я 100500 раз уже написал в этом треде.

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

тут фишка в том, что ты использовал (>>)

выражается через >>=, так что тут я использовал только и исключительно >>=.

который на самом деле операция из более слабой структуры (*>) далее читаем то, что я 100500 раз уже написал в этом треде.

Так мы же рассматриваем случай где аппликатива недостаточно. Мой код нельзя написать на аппликативе - т.к. существенно используется >>=. То есть это именно монада, а не аппликатив. При этом можешь считать, что >> там не используется вовсе - т.к. его можно выразить через бинд.

Напиши пожалуйста такой бинд

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

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

Там используется 'return wtf', в общем то ИО в хацкеле достаточно ленив, чтобы было тоже самое (если мы про пример с undefined), что уже не прокатит с эффектами как выше.так что инчтанс написанный на Haskell меня бы убедил.

С другой стороны такие монады как identity и reader желаемыми мне свойствами не обладают. В принципе можно ослабить моё утверждение и ввести смешные определения чтобы уточнить мою т.з., но большого смысла в этом нет.

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

Если Mem это вся физическая память, то копировать нечего, Mem ~ (), если это страница / массив / ссылка, то передаём адрес, Mem ~ Addr (разумеется, операции put/get используют строгие unpure примитивы работы с памятью (соответствующие вполне стандартизированные для данной машины инструкции, например), один фиг оно за монадой будет скрыто, в чём весь трюк — в GHC работая с IO напрямую тоже можно вся чистоту сломать).

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

Немного погуглил — http://www.cs.cornell.edu/~ross/publications/productors/productors-tate-popl1..., там effectful называется в т.ч. Maybe, в смысле того, что приплюсованный Nothing это «эффект» этого типа (для partiality), тогда (аппликативный) функтор занимается распространением (propagating) чистого кода (относительно «эффекта» в широком смысле) в effectful коде, а необходимость строить цепочки (sequencing, тоже в широком смысле) из effectful кода эквивалентна возникновению вложенных типов, то есть необходимости в join, т.е. в монаде.

Таким образом получается вырожденный identity который ничего не меняет, maybe для partiality (в тотальном языке даже немного более буквально), error для более различимой partiality, list/other containers для ranged/structured/non-deterministic computations, reader для окружения, writer для построений чего-либо, cont, ... Ну а эффекты State & co тем более очевидны.

Порядок же может быть какой нужен (как тест — строгость или нет sequence).

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

Спасибо за ссылку, прочитаю, может ещё что глупое скажу.

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

Судя уже по:

Just as we restrict our scope to producer effects, we also re-strict it to sequential composition of computations. There are many other forms of composition, such as multiplicative composition (for parallelism and for adjacent subexpressions), additive composition (for branching), and coinductive composition (for recursion and for loops). We choose sequential composition because that is where existing research has focused its efforts. We focus on just the one form of composition so that we may address its challenges in full. We believe many of the insights and techniques in this paper can be adapted to other forms of composition, though there is still plenty of interesting work to be done there.

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

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