Как вернуть чистое значение из нечистого метода

Я знаю, что это должно звучать тривиально, но мне было интересно, как вы можете развернуть значение из функтора и вернуть его как чистое значение?

Я пытался:

f::IO a->a
f x=(x>>=) 

f= >>=

Что я должен разместить в правой части? Я не могу использовать return так как это обернет это снова.

2 ответа

Решение

Это часто задаваемый вопрос: как извлечь значение "из" из моей монады не только в Хаскеле, но и в других языках. У меня есть теория о том, почему этот вопрос продолжает возникать, поэтому я постараюсь ответить в соответствии с этим; Я надеюсь, что это помогает.

Контейнеры отдельных значений

Вы можете думать о функторе (и, следовательно, о монаде) как о контейнере значений. Это наиболее ощутимо с (избыточным) Identity функтор:

Prelude Control.Monad.Identity> Identity 42
Identity 42

В данном случае это всего лишь обертка вокруг значения 42, Для этого конкретного контейнера вы можете извлечь значение, потому что оно гарантированно будет там:

Prelude Control.Monad.Identity> runIdentity $ Identity 42
42

В то время как Identity кажется довольно бесполезным, вы можете найти другие функторы, которые, кажется, обернуть одно значение. Например, в F# вы часто встречаете такие контейнеры, как Async<'a> или же Lazy<'a>, которые используются для представления асинхронных или ленивых вычислений (последний не нужен для Haskell, потому что по умолчанию он ленив).

В Haskell вы найдете множество других однозначных контейнеров, таких как Sum, Product, Last, First, Max, Min и т. д. Общим для всех них является то, что они переносят одно значение, что означает, что вы можете извлечь это значение.

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

Контейнеры необязательных значений

К сожалению, некоторые распространенные монады в Хаскеле, кажется, поддерживают эту идею. Например, Maybe также является контейнером данных, но может содержать ноль или одно значение. К сожалению, вы можете извлечь значение, если оно есть:

Prelude Data.Maybe> fromJust $ Just 42
42

Проблема в том, что fromJust не полный, поэтому он потерпит крах, если вы позвоните с Nothing значение:

Prelude Data.Maybe> fromJust Nothing
*** Exception: Maybe.fromJust: Nothing

Вы можете увидеть такую ​​же проблему с Either, Хотя я не знаю о встроенной частичной функции для извлечения Right значение, вы можете легко написать один с сопоставлением с шаблоном (если вы игнорируете предупреждение компилятора):

extractRight :: Either l r -> r
extractRight (Right x) = x

Опять же, он работает в сценарии "счастливого пути", но может так же легко потерпеть крах:

Prelude> extractRight $ Right 42
42
Prelude> extractRight $ Left "foo"
*** Exception: <interactive>:12:1-26: Non-exhaustive patterns in function extractRight

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

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

Контейнеры с несколькими значениями

Даже со списками вы можете (пытаться) извлечь значение 'из' из списка:

Prelude> head [42..1337]
42

Тем не менее, он может потерпеть неудачу:

Prelude> head []
*** Exception: Prelude.head: empty list

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

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

countAndMultiply :: Foldable t => (t a, Int) -> Int
countAndMultiply (xs, factor) = length xs * factor

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

Prelude> fmap countAndMultiply [("foo", 2), ("bar", 3), ("corge", 2)]
[6,9,10]

поскольку IO это функтор, вы можете сделать то же самое с ним:

Prelude> foo = return ("foo", 2) :: IO (String, Int)
Prelude> :t foo
foo :: IO (String, Int)
Prelude> fmap countAndMultiply foo
6

Дело в том, что вы не извлекаете значение из функтора, вы входите в функтор.

монада

Иногда функция, которую вы применяете к функтору, возвращает значение, уже заключенное в тот же контейнер данных. Например, у вас может быть функция, которая разбивает строку на определенный символ. Для простоты давайте просто посмотрим на встроенную функцию words который разбивает строку на слова:

Prelude> words "foo bar"
["foo","bar"]

Если у вас есть список строк и применить words каждому вы получите вложенный список:

Prelude> fmap words ["foo bar", "baz qux"]
[["foo","bar"],["baz","qux"]]

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

Prelude Control.Monad> join $ fmap words ["foo bar", "baz qux"]
["foo","bar","baz","qux"]

Это оригинальное определение монады: это функтор, который вы можете сгладить. В современном Хаскеле Monad определяется связыванием (>>=), из которого можно извлечь join, но также возможно получить >>= от join,

IO как все значения

В этот момент вы можете задаться вопросом: какое это имеет отношение к IO ? не IO a контейнер одного значения типа a?

На самом деле, нет. Одна интерпретация IO является то, что это контейнер, который содержит произвольное значение типа a, Согласно этой интерпретации, она аналогична многомировой интерпретации квантовой механики. IO a это суперпозиция всех возможных значений типа a,

В оригинальном мысленном эксперименте Шредингера кот в коробке и жив, и мертв, пока его не заметят. Это два возможных состояния с наложением. Если мы думаем о переменной с именем catIsAlive, это было бы эквивалентно суперпозиции True а также False, Итак, вы можете думать о IO Bool как набор возможных значений {True, False} это только рухнет в одно значение при наблюдении.

Точно так же, IO Word8 можно интерпретировать как суперпозицию множества всех возможных Word8 значения, т.е. {0, 1, 2,.. 255}, IO Int как суперпозиция всего возможного Int ценности, IO String как все возможно String значения (то есть бесконечное множество) и так далее.

Так как же вы оцениваете ценность?

Вы не извлекаете его, вы работаете в контейнере данных. Вы можете, как показано выше, fmap а также join над ним. Таким образом, вы можете написать свое приложение как чистые функции, которые вы затем создаете с нечистыми значениями fmap, >>=, join, и так далее.

Это тривиально, так что это будет длинный ответ. Короче говоря, проблема заключается в подписи, IO a -> a, это тип, не разрешенный в Haskell. Это действительно меньше связано с IO быть функтором, чем тот факт, что IO особенный.

Для некоторых функторов вы можете восстановить чистое значение. Например, частично примененная пара, (,) aФунктор. Мы разворачиваем значение через snd,

snd :: (a,b) -> b
snd (_,b) = b

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

extract :: Comonad w => w a -> a

любой Comonad будет функтором, для которого вы можете восстановить чистую стоимость.

Многие (некомонадные) функторы имеют, скажем, "оценщики", которые допускают что-то вроде того, что спрашивают. Например, мы можем оценить Maybe с maybe :: a -> Maybe a -> a, Предоставляя значение по умолчанию, maybe a имеет желаемый тип, Maybe a -> a, Еще один полезный пример от государства, evalState :: State s a -> s -> aимеет свои аргументы в обратном порядке, но концепция та же; учитывая монаду, State s aи начальное состояние, sмы разворачиваем чистую стоимость, a,

Наконец, к специфике IO, Нет "оценщика" для IO предоставляется на языке Haskell или в библиотеках. Мы могли бы рассмотреть возможность запуска самой программы в качестве оценщика - во многом в том же духе evalState, Но если это верный концептуальный ход, то это должно только помочь убедить вас в том, что нет разумного способа развернуть IO- любая программа написана только IO a вход в его функцию оценки.

Вместо этого то, что вы вынуждены делать -по замыслу - это работать в IO монада. Например, если у вас есть чистая функция, f :: a -> b, вы применяете это в IO контекст через, fmap f :: IO a -> IO b

TL; DR Вы не можете получить чистую ценность из IO монада. Применять чистые функции в пределах IO контекст, например, fmap

Другие вопросы по тегам