如何从不纯的方法返回纯值 [英] How to return a pure value from a impure method
问题描述
我知道这听起来一定很琐碎,但我想知道如何从函子解开一个值并将其作为纯值返回?
I know it must sound trivial but I was wondering how you can unwrap a value from a functor and return it as pure value?
我试过了:
f::IO a->a
f x=(x>>=)
f= >>=
我应该在右边放什么?我不能使用 return
因为它会再次包装它.
What should I place in the right side? I can't use return
since it will wrap it back again.
推荐答案
这是一个常见问题:我如何从我的 monad 中提取 'the' 值,不仅在 Haskell 中,而且在其他语言也是如此.关于为什么这个问题不断出现,我有一个理论,所以我会尝试根据那个来回答;希望能帮到你.
It's a frequently asked question: How do I extract 'the' value from my monad, not only in Haskell, but in other languages as well. I have a theory about why this question keeps popping up, so I'll try to answer according to that; I hope it helps.
您可以将函子(因此也是monad)视为值的容器.这在(冗余的)Identity
函子中最为明显:
You can think of a functor (and therefore also a monad) as a container of values. This is most palpable with the (redundant) Identity
functor:
Prelude Control.Monad.Identity> Identity 42
Identity 42
这只不过是一个值的包装器,在本例中为 42
.对于这个特定的容器,您可以提取值,因为它保证在那里:
This is nothing but a wrapper around a value, in this case 42
. For this particular container, you can extract the value, because it's guaranteed to be there:
Prelude Control.Monad.Identity> runIdentity $ Identity 42
42
虽然 Identity
看起来相当无用,但您可以找到其他似乎包装单个值的函子.例如,在 F# 中,您经常会遇到诸如 Async<'a>
或 Lazy<'a>
之类的容器,它们用于表示异步或惰性计算(Haskell不需要后者,因为它默认是惰性的).
While Identity
seems fairly useless, you can find other functors that seem to wrap a single value. In F#, for example, you'll often encounter containers like Async<'a>
or Lazy<'a>
, which are used to represent asynchronous or lazy computations (Haskell doesn't need the latter, because it's lazy by default).
您会在 Haskell 中找到许多其他单值容器,例如 Sum
、Product
、Last
、First
、Max
、Min
等.所有这些的共同点是它们包装单个值,这意味着您可以提取值.
You'll find lots of other single-value containers in Haskell, such as Sum
, Product
, Last
, First
, Max
, Min
, etc. Common to all of those is that they wrap a single value, which means that you can extract the value.
我认为,当人们第一次遇到函子和 monad 时,他们倾向于这样认为数据容器的概念:作为单个值的容器.
I think that when people first encounter functors and monads, they tend to think of the concept of a data container in this way: as a container of a single value.
不幸的是,Haskell 中的一些常见 monad 似乎支持这个想法.例如,Maybe
也是一个数据容器,但可以包含零个或一个值.不幸的是,如果值存在,您仍然可以提取该值:
Unfortunately, some common monads in Haskell seem to support that idea. For example, Maybe
is a data container as well, but one that can contain zero or one value. You can, unfortunately, still extract the value if it's there:
Prelude Data.Maybe> fromJust $ Just 42
42
问题在于 fromJust
不是 total,所以如果你用 Nothing
值调用它,它会崩溃:
The problem with this is that fromJust
isn't total, so it'll crash if you call it with a Nothing
value:
Prelude Data.Maybe> fromJust Nothing
*** Exception: Maybe.fromJust: Nothing
您可以在 Either
中看到同样的问题.虽然我不知道提取 Right
值的内置部分函数,但您可以轻松地编写具有模式匹配的函数(如果您忽略编译器警告):
You can see the same sort of problem with Either
. Although I'm not aware of a built-in partial function to extract a Right
value, you can easily write one with pattern matching (if you ignore the compiler warning):
extractRight :: Either l r -> r
extractRight (Right x) = x
同样,它适用于happy path"场景,但也很容易崩溃:
Again, it works in the 'happy path' scenario, but can just as easily crash:
Prelude> extractRight $ Right 42
42
Prelude> extractRight $ Left "foo"
*** Exception: <interactive>:12:1-26: Non-exhaustive patterns in function extractRight
尽管如此,由于像 fromJust
这样的函数存在,我想它会诱使不熟悉函子和 monad 概念的人们将它们视为可以从中提取值的数据容器.
Still, since functions like fromJust
exists, I suppose it tricks people new to the concept of functors and monads into thinking about them as data containers from which you can extract a value.
当您第一次遇到诸如 IO Int
之类的东西时,我可以理解为什么您会倾向于将其视为单个值的容器.从某种意义上说,它是,但在另一种意义上,它不是.
When you encounter something like IO Int
for the first time, then, I can understand why you'd be tempted to think of it as a container of a single value. In a sense, it is, but in another sense, it isn't.
即使使用列表,您也可以(尝试)从列表中提取the"值:
Even with lists, you can (attempt to) extract 'the' value from a list:
Prelude> head [42..1337]
42
不过,它可能会失败:
Prelude> head []
*** Exception: Prelude.head: empty list
然而,此时应该很清楚,试图从任意函子中提取the"值是无稽之谈.列表是一个函子,但它包含任意数量的值,包括零个和无限多个.
At this point, however, it should be clear that attempting to extract 'the' value from any arbitrary functor is nonsense. A list is a functor, but it contains an arbitrary number of values, including zero and infinitely many.
你可以总是做的是编写函数,将一个包含"的值作为输入并返回另一个值作为输出.这是此类函数的任意示例:
What you can always do, though, is to write functions that take a 'contained' value as input and returns another value as output. Here's an arbitrary example of such a function:
countAndMultiply :: Foldable t => (t a, Int) -> Int
countAndMultiply (xs, factor) = length xs * factor
虽然您不能从列表中提取值",但您可以将您的函数应用于列表中的每个值:
While you can't 'extract the value' out of a list, you can apply your function to each of the values in a list:
Prelude> fmap countAndMultiply [("foo", 2), ("bar", 3), ("corge", 2)]
[6,9,10]
由于 IO
是一个函子,你也可以用它来做同样的事情:
Since IO
is a functor, you can do the same with it as well:
Prelude> foo = return ("foo", 2) :: IO (String, Int)
Prelude> :t foo
foo :: IO (String, Int)
Prelude> fmap countAndMultiply foo
6
关键是你不从函子中提取值,你进入函子.
The point is that you don't extract a value from a functor, you step into the functor.
有时,您应用于函子的函数会返回一个已经包装在同一个数据容器中的值.例如,您可能有一个将字符串拆分为特定字符的函数.为了简单起见,让我们看看将字符串拆分为单词的内置函数 words
:
Sometimes, the function you apply to a functor returns a value that's already wrapped in the same data container. As an example, you may have a function that splits a string over a particular character. To keep things simple, let's just look at the built-in function words
that splits a string into words:
Prelude> words "foo bar"
["foo","bar"]
如果您有一个字符串列表,并将 words
应用于每个字符串,您将得到一个嵌套列表:
If you have a list of strings, and apply words
to each, you'll get a nested list:
Prelude> fmap words ["foo bar", "baz qux"]
[["foo","bar"],["baz","qux"]]
结果是一个嵌套的数据容器,在本例中是一个列表列表.您可以使用 join
将其展平:
The result is a nested data container, in this case a list of lists. You can flatten it with join
:
Prelude Control.Monad> join $ fmap words ["foo bar", "baz qux"]
["foo","bar","baz","qux"]
这是 monad 的原始定义:它是一个可以展平的函子.在现代 Haskell 中,Monad
由 bind (>>>=
) 定义,从中可以导出 join
,但也可以从 join
派生 >>=
.
This is the original definition of a monad: it's a functor that you can flatten. In modern Haskell, Monad
is defined by bind (>>=
), from which one can derive join
, but it's also possible to derive >>=
from join
.
此时,您可能想知道:这与 IO
有什么关系?IO
不就是一个容器吗?a
?
At this point, you may be wondering: what does that have to do with IO
? Isn't IO a
a container of a single value of the type a
?
不是真的.IO
的一个解释是,它是一个容器,可以保存 a
类型的任意值.根据这种解释,它类似于量子力学的多世界解释.IO a
是 a
类型的所有可能值的叠加.
Not really. One interpretation of IO
is that it's a container that holds an arbitrary value of the type a
. According to that interpretation, it's analogous to the many-worlds interpretation of quantum mechanics. IO a
is the superposition of all possible values of the type a
.
在薛定谔最初的思想实验中,盒子里的猫在被观察之前既是活的又是死的.这是叠加的两种可能状态.如果我们考虑一个名为 catIsAlive
的变量,它相当于 True
和 False
的叠加.因此,您可以将 IO Bool
视为一组可能的值 {True, False}
,它们在观察时只会折叠为单个值.
In Schrödinger's original thought experiment, the cat in the box is both alive and dead until observed. That's two possible states superimposed. If we think about a variable called catIsAlive
, it would be equivalent to the superposition of True
and False
. So, you can think of IO Bool
as a set of possible values {True, False}
that will only collapse into a single value when observed.
同样,IO Word8
可以解释为所有可能的 Word8
值集合的叠加,即 {0, 1, 2,.. 255}
,IO Int
为所有可能的Int
值的叠加,IO String
为所有可能的String
> 值(即无限集),等等.
Likewise, IO Word8
can be interpreted as a superposition of the set of all possible Word8
values, i.e. {0, 1, 2,.. 255}
, IO Int
as the superposition of all possible Int
values, IO String
as all possible String
values (i.e. an infinite set), and so on.
那么你如何观察这个值呢?
你不提取它,你在数据容器中工作.你可以,如上所示,fmap
和 join
在它上面.因此,您可以将应用程序编写为纯函数,然后使用 fmap
、>>=
、join
和不纯值组合以此类推.
You don't extract it, you work within the data container. You can, as shown above, fmap
and join
over it. So, you can write your application as pure functions that you then compose with impure values with fmap
, >>=
, join
, and so on.
这篇关于如何从不纯的方法返回纯值的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!