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

时间:2018-07-31 13:28:24

标签: haskell monads functor

我知道它听起来很琐碎,但我想知道如何从函子中解开一个值并将其作为纯值返回?

我尝试过:

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

f= >>=

我应该在右侧放置什么?我不能使用return,因为它将再次将其重新包装。

2 个答案:

答案 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中找到许多其他单值容器,例如SumProductLastFirstMax,{ {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

重点是您不会从函子中提取值,您进入函子

Monad

有时,您应用于函子的函数将返回已包装在同一数据容器中的值。例如,您可能具有将字符串分割为特定字符的功能。为简单起见,让我们看一下内置函数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来自>>=

IO为所有值

在这一点上,您可能想知道:join有什么关系? IO是否不是一个类型为{的单一值的容器{1}}?

不是。 IO a的一个解释是它是一个容器,其中包含类型为a的任意值。根据这种解释,它类似于量子力学的许多世界解释。 IOa类型的所有可能值的叠加。

在薛定ding最初的思想实验中,盒子里的猫既活着又死了,直到被观察到。这是两个可能的状态叠加。如果我们考虑一个名为IO a的变量,它将等同于acatIsAlive的叠加。因此,您可以将True视为一组可能的值False,这些值只会在观察时折叠为一个值。

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

那您怎么观察该值呢?

您不提取它,而是在数据容器中工作。如上所示,您可以在其上IntIO String。因此,您可以将应用程序编写为纯函数,然后用Stringfmapjoin等不纯值组成。

答案 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 -> aState的另一个有用的示例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