我知道它听起来很琐碎,但我想知道如何从函子中解开一个值并将其作为纯值返回?
我尝试过:
f::IO a->a
f x=(x>>=)
f= >>=
我应该在右侧放置什么?我不能使用return
,因为它将再次将其重新包装。
答案 0 :(得分:7)
这是一个经常被问到的问题:我如何从我的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
,{ {1}}等。所有这些共同点是它们包装单个值,这意味着您可以提取该值。
我认为,当人们第一次遇到函子和monad时,他们倾向于以这种方式想到数据容器的概念:作为单个值的容器。
不幸的是,Haskell中一些常见的单子似乎支持该想法。例如,Min
也是一个数据容器,但其中一个可以包含零或一个值。不幸的是,如果有,您仍然可以提取该值:
Maybe
此问题是Prelude Data.Maybe> fromJust $ Just 42
42
不是 total ,因此如果您使用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
之类的函数,因此我想它会欺骗函子和monad概念的新手,将其视为可以从中提取值的数据容器。
那么,当您第一次遇到fromJust
之类的东西时,我就能理解为什么您很想将它视为一个单一值的容器。从某种意义上说是,但是从另一个意义上说不是。
即使有列表,您也可以(尝试)从列表中提取“ the”值:
IO Int
仍然,它可能会失败:
Prelude> head [42..1337]
42
但是,在这一点上,应该很清楚,试图从任意函子中提取“ the”值都是胡说八道。列表是一个函子,但它包含任意数量的值,包括零和无限多个。
您可以一直做的事情是编写将“包含”值作为输入并返回另一个值作为输出的函数。这是此类函数的任意示例:
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
这是monad的原始定义:它是一个可以展平的函子。在现代的Haskell中,Prelude Control.Monad> join $ fmap words ["foo bar", "baz qux"]
["foo","bar","baz","qux"]
由 bind (Monad
)定义,从中可以得出>>=
,但是也可以得出join
来自>>=
。
在这一点上,您可能想知道:与join
有什么关系? IO
是否不是一个类型为{的单一值的容器{1}}?
不是。 IO a
的一个解释是它是一个容器,其中包含类型为a
的任意值。根据这种解释,它类似于量子力学的许多世界解释。 IO
是a
类型的所有可能值的叠加。
在薛定ding最初的思想实验中,盒子里的猫既活着又死了,直到被观察到。这是两个可能的状态叠加。如果我们考虑一个名为IO a
的变量,它将等同于a
和catIsAlive
的叠加。因此,您可以将True
视为一组可能的值False
,这些值只会在观察时折叠为一个值。
同样,IO Bool
可以解释为所有可能的{True, False}
值的集合的叠加,即IO Word8
,Word8
是所有可能的{{1 }}值,{0, 1, 2,.. 255}
作为所有可能的IO Int
值(即无限集),等等。
那您怎么观察该值呢?
您不提取它,而是在数据容器中工作。如上所示,您可以在其上Int
和IO String
。因此,您可以将应用程序编写为纯函数,然后用String
,fmap
,join
等不纯值组成。
答案 1 :(得分:4)
这是微不足道的,所以这将是一个很长的答案。简而言之,问题在于签名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
。 State的另一个有用的示例evalState :: State s a -> s -> a
的论点颠倒了,但是概念是相同的。给定单子State s a
和初始状态s
,我们将解开纯值a
。
最后是IO
的细节。 Haskell语言或库中没有提供IO
的“评估程序”。我们可能会考虑将程序本身作为评估程序运行,这与evalState
同样。但是,如果这是一个有效的概念上的举措,那么它只会有助于说服您没有从IO
解包的理智方法-编写的任何程序只是对其评估函数的IO a
输入。
相反,根据设计,您被迫要做的是在IO
monad中工作。例如,如果您有一个纯函数f :: a -> b
,则可以通过IO
fmap f :: IO a -> IO b
上下文中应用它
TL; DR 您无法从IO
单子中获得纯价值。在IO
上下文中应用纯函数,例如fmap