LINUX.ORG.RU

Чистый код и монад трансформеры

 


0

2

Хаскель рекламируется как язык в котором чистые функции отделены от IO actions. При этом, как я это понимаю, функции работающие в остальных монадах все равно остаются частью чистого кода. И если мы возьмем монад трансформер Q, вычисления типа Q a m b где m монада которая не инстанс MonadIO, то мы тоже легко можем запускать на этом коде QuickCheck и доказывать о нем разнообразные факты.

Но если для монады определен liftIO, то что же получается, опять все в кучу? Дискасс.


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

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

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

Да вообще, фундаментальный вопрос. Я решил что хочу возить с собой повсюду стэйт (пусть будет строка). Но вдруг я захочу сделать IO? Конечно, я использую StateT!

my_clunky_function :: StateT String IO ()
my_clunky_function = do state <- get
                        result <- lift $ my_io_action
                        -- do something else
                        put (state `modifyWith` result)

и все испорчено! Я уже не могу reason about this code.

nokachi
() автор топика
Ответ на: комментарий от s9gf4ult

you are missing the point. Был чистый код, добавил монаду в MonadIO — и чистым он быть потенциально перестал.

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

Не очень понятно, что тебя путает. Мне очень помог код xmonad. Там в wiki всё доступно написано, что смотреть в какой последовательности.

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

ну как что. была у нас монада m, ну просто параметризованный тип с двумя определенными операциями. Функции типа m a — обычные чистые функции, ничего дурного от них ожидать нельзя. Внезапно мы для m определяем liftIO — и наша хорошая монада становится ничуть не лучше IO, любая функция с типом m a может внутри себя молча слать данные в сеть или читать диск. Выглядит как хак.

nokachi
() автор топика

Хаскель рекламируется как язык в котором чистые функции отделены от IO actions.

Чистые функции отделены от «нечистых». «Нечистые» функции, тобишь функции, имеющие сайд-эффекты/зависящие от окружения, не обязательно IO.

При этом, как я это понимаю, функции работающие в остальных монадах все равно остаются частью чистого кода

См. выше

И если мы возьмем монад трансформер Q, вычисления типа Q a m b где m монада которая не инстанс MonadIO, то мы тоже легко можем запускать на этом коде QuickCheck

Суть в том, что некоторые монады можно «запускать» из чистого кода.

Prelude Control.Monad.State Control.Monad.Reader> :t runReader
runReader :: Reader r a -> r -> a
Prelude Control.Monad.State Control.Monad.Reader> :t runState
runState :: State s a -> s -> (a, s)

-> a, -> (a, s), никаких m a или m (a, s) на конце.

Но если для монады определен liftIO, то что же получается, опять все в кучу?

Не понимаю, зачем ты приплёл это к предыдущему абзацу.

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

Чистые функции отделены от «нечистых». «Нечистые» функции, тобишь функции, имеющие сайд-эффекты/зависящие от окружения, не обязательно IO. <...> некоторые монады можно «запускать» из чистого кода.

и если из некоторых монад нельзя запускать IO actions, то это все чистый код — монады ли там, не монады.

Чистый код и монад трансформеры (комментарий)

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

Функции типа m a — обычные чистые функции

Лол! Повтори определение чистоты функции, потом, возможно, до тебя самого дойдёт, что к чему.

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

Все что не может делать IO можно запускать из чистого кода.

У тебя какая-то каша в голове.

Давай по существу.

Открываешь заветную страничку, читаешь определение, сравниваешь со свойствами foo, делаешь вывод.

{-# LANGUAGE FlexibleContexts #-}

import Control.Monad.Reader
import Control.Monad.Reader.Class

foo :: MonadReader Int m => m Int
foo = liftM (+1) ask

bar :: Int
bar = runReader foo 1

baz :: Int
baz = runReader foo 2

И никакого IO!

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

У тебя какая-то каша в голове.

Нет это у тебя каша в голове. Функция

filter :: (a -> Bool) -> [a] -> [a]
чистая или нет? А если вспомнить что лист это монада?

То что ты привел — это чистый код. Разве Read инстансе MonadIO?

nokachi
() автор топика

Хаскель рекламируется как язык в котором чистые функции отделены от IO actions.

Шо, опять по новой? :)

IO actions возвращаются чистыми функциями. Например, функция putStrLn - чистая, и она не имеет побочных эффектов. Но вычисление IO, которое возвращает эта функция, является императивным и действительно производит побочный эффект при своем применении. Чувствуешь разницу в подходе?

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

меня не совсем это интересует. Когда мы делаем

z <- liftIO getLine

мы IO action не возвращаем а именно исполняем. И делаем это в монаде отличной от IO. Я не сомневаюсь что type system нигде не нарушается, я спрашиваю не теряется ли рекламируемое достоинство хаскелля когда мы из двух сущностей — чистых и IO, делаем такой гибрид.

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

Что такое «Read»?

а что такое норм?

MonadIO m => MonadIO (ReaderT r m)

на всякий случай поясню что имелась в виду Reader monad, defined as

type Reader r = ReaderT r Data.Functor.Identity.Identity
nokachi
() автор топика
Ответ на: комментарий от nokachi

а что такое норм?

Что идиосинкразия пока(?) на пустом месте.

в виду Reader monad, defined as

Может вы и Бебеля с Бабелем путаете? И, что, для Reader есть liftIO?

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

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

А если вспомнить что лист это монада?

Лист - это такая довольно хитрая монада.

yoghurt ★★★★★
()

А что такое чистая функция?

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

Всё нормально, объявляя функцию

my_clunky_function :: StateT String IO ()
ты говоришь, что она делает IO и не является чистой. Ты не можешь взять и вызвать её из чистой функции, т.к. у runStateT будет сигнатура
runStateT :: String -> IO ((), String)
, а execStateT и evalStateT будут в IO.

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

мы IO action не возвращаем а именно исполняем. И делаем это в монаде отличной от IO. Я не сомневаюсь что type system нигде не нарушается, я спрашиваю не теряется ли рекламируемое достоинство хаскелля когда мы из двух сущностей — чистых и IO, делаем такой гибрид.

Склонен рассматривать термин «IO action» как педагогическую ошибку, ибо пудрит мозги.

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

Тут простой момент. Пока ты остаешься в рамках хаскеля, вычисление IO ничем не отличается от других типов данных - только это некий черный ящик для тебя. Оно начинает работать через main - и это забота main произвести все запланированные побочные эффекты. А поскольку язык ленивый, то процесс применения main сильно оптимизирован, и начинается сразу.

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

с точки зрения типов мне все и так понятно. Вопрос-то в модульности: если определен liftIO то уже не получается разнести чистый и нечистый код в разные места.

nokachi
() автор топика

Если функция возвращает значение в монаде IO, но не использует прямо или косвенно unsafePerformIO и подобные, она по прежнему остается чистой. Значения в IO можно складывать в списки/мапы/массивы, мемоизировать, прятать в экзистенциалы и т.п., они сохраняют все свойства «чистого» кода.
Всем несогласным курить «tackling the awkward squad».

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

мы IO action не возвращаем а именно исполняем.

Сразу не заметил. Это - грубая ошибка так думать. Видимо, в этом причина возникновения этого топика.

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

Я в общих чертах знаю как работает хаскелевский компилятор и рантайм. Я понимаю что исполняется все на самом деле в IO, и exceptions ловятся только в IO. Причина возникновения этого топика в том что, как правильно понял unC0Rr, у нас вместо новой монады получается опять IO но с новой фичей. И код не разделяется на две части — чистый и IO, а вместо этого лапша из того и другого.

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

Тебе никто не мешает взять состояние из StateT и перейти в монаду State, когда понадобится чистота, всё ровно так же, как в случае, когда ты вызываешь чистые функции в main.

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

Это мне понятно, но как раз это все вещи где нет liftIO.

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

И код не разделяется на две части — чистый и IO, а вместо этого лапша из того и другого.

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

Чистота в рамках хаскеля буквально подразумевает, что все побочные эффекты, производимые функцией, прописаны явно в ее сигнатуре. Так что, никакого противоречия с IO нет. Другое дело, что под чистотой еще часто понимают код, незавязанный на IO.

Получается два значения у одного термина. Первое связано с фундаментальным свойством языка. Второе - больше обиходное среди хаскелистов. Да, эта двойственность здорово путает людей. Раньше я тоже путался :)

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

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

Что остается неясным, так это то что хаскеллисты постоянно подчеркивают это железобетонное разделение IO от всего остального, а на самом деле с точки зрения написания программ это довольно слабая граница — класс MonadIO дает возможность compose everything with IO и искать инварианты для тестирования такого кода становится труднее.

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

Ну, дык. Если во втором смысле термина, то IO, конечно, «портит» чистоту монадного трансформера, ибо начинает требовать уже IO для окончательного запуска вычисления. Вне сомнения. Это же тоже тривиально :)

В общем, тут начинается уже игра терминов.

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

Наличие монадических функций, не использующих побочные эффекты («foo = return 3»), ещё не говорит об отсутствии монадических функций с побочными эффектами. Да, тут выше уже пришли к согласию.

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

нет, тут дело не просто в типах. Включение IO в чистый код нарушает modularity. Например, если мы вызываем через liftIO что-то через FFI, то мы должны теперь смотреть где мы нашу монаду используем, что это обязательно должен быть bounded thread. То есть те проблемы, которые type system doesn't capture.

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

Читать могучие мысли местных хачкельных экспертов мне лениво, но причины в общем две: неудобно комбинировать 2 стекоговна и добавлять эффекты в стек.

Все ждут юзабельную систему эффектов.

anonymous
()

Офигеть. Что я только что прочитал?

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

Да вообще, фундаментальный вопрос.

фундаментальным этот вопрос может стать только если ты разберёшься с кашей в голове и разложишь всё по полочкам.

. Я решил что хочу возить с собой повсюду стэйт (пусть будет строка). Но вдруг я захочу сделать IO? Конечно, я использую StateT!

StateT говорит о том, что у тебя в монадическом стеке есть уровень соответствующий State. Это значит, что в этом уровне bind соответсвует state монаде, со всеми соответсвующими выводами. Ты знаешь, что ты можешь подняться с любого более низкого уровня m на уровень State и не можешь никак подняться выше. И тебе всё равно, что у тебя на более низком уровне. Не всё равно тебе только в top-level, когда стек уже будет конкретным, а не полиморфным.

my_clunky_function

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

и все испорчено!

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

Я уже не могу reason about this code.

что ты можешь сказать о:

foo :: (StateMonad String m) => m ()

?

P.S. почему к этому вопросу был приплетён QuickCheck?

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

еще один про кашу. Давай ты сначала разберешься со своими комплексами а потом придешь сюда и будешь нормально вести обсуждение.

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

Я, вообще, не очень понимаю, почему многие так боятся этой монады IO, особенно среди преподавателей и их студентов (и те, и другие не являются практикующими программистами). На мой взгляд, написать что-то реальное без IO невозможно. Ну, пусть она там что-то нарушает в их стройной выдуманной системе. Если даже это происходит, то это лишь значит, что реальный мир несколько сложнее, чем строго детерминированная иллюзия.

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

Так никто не говорит чтобы без IO — но нынешняя имплементация IO имеет «особенности», из-за которых хотелось держать ее преимущественно внизу. И пример с liftIO выглядит как беспомощный workaround — хотелось бы собрать конструкцию из чистого кода, поставить на IO и запустить, а приходится и в середине стэка какие-то реальные действия выполнять. То ли реальность неидеальна, то ли programming model.

nokachi
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.