如何从不纯的方法返回纯值

Ber*_*ian 2 monads haskell functor

我知道它听起来一定是微不足道的,但我想知道如何从仿函数中展开一个值并将其作为纯值返回?

我试过了:

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

f= >>=
Run Code Online (Sandbox Code Playgroud)

我应该在右侧放置什么?我不能使用,return因为它会再次包裹它.

Mar*_*ann 10

这是一个经常被问到的问题:如何从我的monad中提取'the'值,不仅在Haskell中,而且在其他语言中.我有一个关于为什么这个问题不断涌现的理论,所以我会试着回答这个问题; 我希望它有所帮助.

单值容器

您可以将仿函数(也因此也是monad)视为值的容器.这是(冗余)Identity仿函数最明显的:

Prelude Control.Monad.Identity> Identity 42
Identity 42
Run Code Online (Sandbox Code Playgroud)

在这种情况下,这只是一个值的包装器42.对于此特定容器,您可以提取值,因为它保证在那里:

Prelude Control.Monad.Identity> runIdentity $ Identity 42
42
Run Code Online (Sandbox Code Playgroud)

虽然Identity看起来相当无用,但您可以找到其他似乎包含单个值的仿函数.例如,在F#中,您经常会遇到像Async<'a>or Lazy<'a>这样的容器,它们用于表示异步或延迟计算(Haskell不需要后者,因为默认情况下它是惰性的).

你会发现很多在Haskell其他单值的容器,如Sum,Product,Last,First,Max,Min,等对所有那些是他们换一个值,这意味着你可以提取价值.

我认为当人们第一次遇到仿函数和monad时,他们倾向于以这种方式思考数据容器的概念:作为单个值的容器.

可选值的容器

不幸的是,Haskell中的一些常见的monad似乎支持这个想法.例如,Maybe也是一个数据容器,但可以包含零个或一个值.不幸的是,如果存在价值,你仍然可以提取价值:

Prelude Data.Maybe> fromJust $ Just 42
42
Run Code Online (Sandbox Code Playgroud)

这个问题fromJust是不完全的,所以如果用一个Nothing值调用它会崩溃:

Prelude Data.Maybe> fromJust Nothing
*** Exception: Maybe.fromJust: Nothing
Run Code Online (Sandbox Code Playgroud)

你可以看到同样的问题Either.虽然我不知道提取Right值的内置部分函数,但您可以轻松地使用模式匹配编写一个(如果忽略编译器警告):

extractRight :: Either l r -> r
extractRight (Right x) = x
Run Code Online (Sandbox Code Playgroud)

同样,它适用于"快乐路径"场景,但也可以轻松崩溃:

Prelude> extractRight $ Right 42
42
Prelude> extractRight $ Left "foo"
*** Exception: <interactive>:12:1-26: Non-exhaustive patterns in function extractRight
Run Code Online (Sandbox Code Playgroud)

尽管如此,由于fromJust存在这样的函数,我认为它会让人们对仿函数和monad的概念产生新的想法,将它们视为数据容器,您可以从中提取值.

当你IO Int第一次遇到这样的事情的时候,我就能明白为什么你会把它想象成一个单一价值的容器.从某种意义上说,它是,但在另一种意义上,它不是.

多个容器的容器

即使使用列表,您也可以(尝试)从列表中提取"值":

Prelude> head [42..1337]
42
Run Code Online (Sandbox Code Playgroud)

它仍然可能失败:

Prelude> head []
*** Exception: Prelude.head: empty list
Run Code Online (Sandbox Code Playgroud)

然而,在这一点上,应该清楚的是,尝试从任意任意函子中提取"the"值是无稽之谈.列表是一个仿函数,但它包含任意数量的值,包括零和无限多个.

你有什么可以永远做,虽然是写的是采取"包含"值作为输入功能并返回另一个值作为输出.这是这样一个函数的任意例子:

countAndMultiply :: Foldable t => (t a, Int) -> Int
countAndMultiply (xs, factor) = length xs * factor
Run Code Online (Sandbox Code Playgroud)

虽然你不能"提取值"出来的清单,你可以申请你的函数每个在列表中的值:

Prelude> fmap countAndMultiply [("foo", 2), ("bar", 3), ("corge", 2)]
[6,9,10]
Run Code Online (Sandbox Code Playgroud)

既然IO是一个仿函数,你也可以用它来做同样的事情:

Prelude> foo = return ("foo", 2) :: IO (String, Int)
Prelude> :t foo
foo :: IO (String, Int)
Prelude> fmap countAndMultiply foo
6
Run Code Online (Sandbox Code Playgroud)

关键是你没有从仿函数中提取值,你进入仿函数.

单子

有时,应用于仿函数的函数会返回已包含在同一数据容器中的值.例如,您可能有一个将字符串拆分为特定字符的函数.为了简单起见,让我们看一下words将字符串拆分为单词的内置函数:

Prelude> words "foo bar"
["foo","bar"]
Run Code Online (Sandbox Code Playgroud)

如果您有一个字符串列表,并应用于words每个字符串,您将获得一个嵌套列表:

Prelude> fmap words ["foo bar", "baz qux"]
[["foo","bar"],["baz","qux"]]
Run Code Online (Sandbox Code Playgroud)

结果是一个嵌套的数据容器,在本例中是一个列表列表.您可以使用以下方法展平它join:

Prelude Control.Monad> join $ fmap words ["foo bar", "baz qux"]
["foo","bar","baz","qux"]
Run Code Online (Sandbox Code Playgroud)

这是monad的原始定义:它是一个可以展平的仿函数.在现代Haskell中,Monadbind(>>=)定义,可以从中派生出来join,但也可以从中派生>>=出来join.

IO作为所有值

此时,您可能想知道:这与此有什么关系IO不是IO a该类型的单个值的容器a

并不是的.一个解释IO是,它是保存类型的任意值的容器a.根据这种解释,它类似于多世界对量子力学的解释.IO a是所有可能的类型值的叠加a.

在薛定谔最初的思想实验中,盒子里的猫既活着也死了,直到被观察到.这是叠加的两种可能状态.如果我们考虑一个变量叫catIsAlive,这将是相当于叠加TrueFalse.因此,您可以将其IO Bool视为一组可能的值{True, False},这些值在观察时只会折叠为单个值.

同样,IO Word8可以解释为所有可能Word8值的集合的叠加,即{0, 1, 2,.. 255},IO Int作为所有可能Int值的叠加,IO String作为所有可能的String值(即,无限集),等等.

那么你如何观察价值呢?

您不提取它,您数据容器中工作.你可以,如上图所示,fmapjoin在其上.所以,你可以写你的应用程序作为纯函数,你再与不纯的值与组合fmap,>>=,join,等等.

  • @BercoviciAdrian C#的`await`关键字只是语法糖,类似于`do`符号中Haskell的`<-`绑定.他们非常接近,虽然`Task <T>`不是monad.进入仿函数*类比的步骤最多是不精确的; 这是为了使概念更容易理解.如果我没有成功,那就不要出汗了.但是,我的意思是你可以使用`fmap`,`>> =`等将你的纯函数"注入"你的`IO`值.这种方式允许您在容器内执行工作,而无需提取数据.HTH. (2认同)