如何从不纯方法中返回纯值 [英] How to return a pure value from a impure method
问题描述
我知道它听起来一定是微不足道的,但是我想知道如何从函子中解开一个值并将其作为纯值返回?
我尝试过:
f::IO a->a
f x=(x>>=)
f= >>=
我应该在右侧放置什么?我不能使用return
,因为它会再次将其重新包装.
这是一个经常被问到的问题:我如何从我的monad中提取'the'值,不仅在Haskell,而且在其他语言也是如此.我有一个关于为什么这个问题不断出现的理论,因此我将尝试据此回答.希望对您有帮助.
单个值的容器
您可以将 functor (因此也称为 monad )视为值的容器. (冗余)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
等.是它们包装了一个值,这意味着您可以提取该值.
我认为,当人们第一次遇到函子和monad时,他们倾向于以这种方式想到数据容器的概念:作为单个值的容器.
可选值的容器
不幸的是,Haskell中一些常见的单子似乎支持该想法.例如,Maybe
也是一个数据容器,但其中一个可以包含零或一个值.不幸的是,如果存在,您仍然可以提取该值:
Prelude Data.Maybe> fromJust $ Just 42
42
此问题是fromJust
并非 total ,因此如果您使用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
这样的函数,因此我想它会欺骗函子和monad概念的新手,将其视为可以从中提取值的数据容器.
那么,当您第一次遇到类似IO Int
的东西时,我就能理解为什么您会被诱惑将其视为单个值的容器.从某种意义上说是这样,但从另一个意义上说不是.
多个值的容器
即使有列表,您也可以(尝试)从列表中提取"the"值:
Prelude> head [42..1337]
42
仍然,它可能会失败:
Prelude> head []
*** Exception: Prelude.head: empty list
但是,在这一点上,应该很清楚,试图从任意函子中提取"the"值是胡说八道.列表是一个函子,但它包含任意数量的值,包括零和无限多个.
您可以总是做的是编写将包含"值作为输入并返回另一个值作为输出的函数.这是此类函数的任意示例:
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
重点是您不会从函子中提取值,您进入函子.
Monad
有时,您应用于函子的函数将返回已包装在同一数据容器中的值.例如,您可能具有将字符串分割为特定字符的功能.为了简单起见,让我们看一下内置函数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的原始定义:它是一个可以展平的函子.在现代的Haskell中,Monad
由 bind (>>=
)定义,从中可以导出join
,但是也可以从join
导出>>=
.
IO为所有值
在这一点上,您可能想知道:与IO
有什么关系? IO a
不是一个唯一类型为a
的单个值的容器吗? /p>
并非如此. IO
的解释是它是一个容器,其中保存有类型为a
的任意值.根据该解释,它类似于量子力学的许多世界解释. IO a
是类型a
的所有可能值的叠加.
在薛定ding最初的思想实验中,盒子里的猫既活着又死了,直到被观察到.这是两个可能的状态的叠加.如果我们考虑一个名为catIsAlive
的变量,它将等同于True
和False
的叠加.因此,您可以将IO Bool
视为一组可能的值{True, False}
,这些值只有在观察时才会折叠为单个值.
同样,IO Word8
可以解释为所有可能的Word8
值的集合的叠加,即{0, 1, 2,.. 255}
,IO Int
可以解释为所有可能的Int
值的叠加,IO String
String
值(即无限集),依此类推.
那您怎么观察该值呢?
您不提取它,而是在数据容器内 工作.如上所示,您可以在其上fmap
和join
.因此,您可以将应用程序编写为纯函数,然后使用fmap
,>>=
,join
等用不纯值组成.
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?
I have tried:
f::IO a->a
f x=(x>>=)
f= >>=
What should I place in the right side? I can't use return
since it will wrap it back again.
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.
Containers of single values
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
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
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).
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.
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.
Containers of optional values
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
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
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
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
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.
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.
Containers of multiple values
Even with lists, you can (attempt to) extract 'the' value from a list:
Prelude> head [42..1337]
42
Still, it could fail:
Prelude> head []
*** Exception: Prelude.head: empty list
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]
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.
Monad
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"]
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"]]
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"]
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 as all values
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
?
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
.
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.
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.
So how do you observe the value, then?
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屋!