Haskell 如何从不纯方法返回纯值
我知道这听起来很琐碎,但我想知道如何从函子中展开一个值并将其作为纯值返回 我试过:Haskell 如何从不纯方法返回纯值,haskell,monads,functor,Haskell,Monads,Functor,我知道这听起来很琐碎,但我想知道如何从函子中展开一个值并将其作为纯值返回 我试过: f::IO a->a f x=(x>>=) f= >>= 我应该在右边放什么?我不能使用return,因为它会再次将其包装回去。它很简单,所以这将是一个很长的答案。简而言之,问题在于签名,ioa->a,不是Haskell中正确允许的类型。这实际上与IO是一个函子关系不大,而与IO是一个特殊的事实关系不大 对于某些函子,可以恢复纯值。例如,一个部分应用的对,(,)a,是一个函子。
f::IO a->a
f x=(x>>=)
f= >>=
我应该在右边放什么?我不能使用
return
,因为它会再次将其包装回去。它很简单,所以这将是一个很长的答案。简而言之,问题在于签名,ioa->a
,不是Haskell中正确允许的类型。这实际上与IO
是一个函子关系不大,而与IO
是一个特殊的事实关系不大
对于某些函子,可以恢复纯值。例如,一个部分应用的对,(,)a
,是一个函子。我们通过snd
打开值
snd :: (a,b) -> b
snd (_,b) = b
这是一个函子,我们可以把它展开成一个纯值,但这与函子的性质无关。它更多地涉及属于不同范畴理论概念的配对,包括:
任何Comonad
都将是一个函子,您可以为其恢复纯值
许多(非同位语)functor都有——比如说“evaluators”——它们允许类似于被询问的内容。例如,我们可以使用Maybe::a->Maybe a->a
评估Maybe
。通过提供默认值,可能a
具有所需的类型,可能a->a
。另一个有用的例子是,evalState::State sa->s->a
,它的参数颠倒了,但概念是相同的;给定monad,状态sa
,初始状态,s
,我们打开纯值,a
最后是IO
的细节。Haskell语言或库中没有为IO提供“evaluator”。我们可能会认为运行程序本身是一个评估器——与“代码> ValueSt/<代码>完全相同。但是如果这是一个有效的概念性动作,那么它只会帮助你确信没有一种明智的方法可以从IO
中展开——任何编写的程序都只是其求值函数的IO a
输入
相反,您被迫做的是在IO
monad中工作。例如,如果你有一个纯函数,f::a->b
,你可以通过,fmap f::IO a->IO b
TL;DR您无法从
IO
monad中获得纯值。在IO
上下文中应用纯函数,例如通过fmap
这是一个常见问题:如何从monad中提取“the”值,不仅在Haskell中,而且在其他语言中也是如此。我有一个关于为什么这个问题不断出现的理论,所以我将试着根据这个理论来回答;我希望有帮助
单值容器
您可以将函子(因此也是单子)视为值的容器。这在(冗余)Identity
functor中最为明显:
Prelude Control.Monad.Identity> Identity 42
Identity 42
这只是一个值的包装,在本例中是42
。对于此特定容器,您可以提取值,因为它保证存在:
Prelude Control.Monad.Identity> runIdentity $ Identity 42
42
虽然Identity
似乎毫无用处,但您可以找到其他似乎只包含一个值的functor。例如,在F#中,您经常会遇到像Async
这样的容器,它们用于表示异步或惰性计算(Haskell不需要后者,因为默认情况下它是惰性的)
在Haskell中,您会发现许多其他单值容器,例如Sum
,Product
,Last
,First
,Max
,Min
,等等。所有这些容器的共同点是它们包装一个值,这意味着您可以提取值
我认为当人们第一次遇到函子和单子时,他们倾向于这样看待数据容器的概念:作为一个单一值的容器
可选值的容器
不幸的是,哈斯克尔的一些常见单子似乎支持这一观点。例如,可能也是一个数据容器,但它可以包含零或一个值。不幸的是,如果值存在,您仍然可以提取该值:
Prelude Data.Maybe> fromJust $ Just 42
42
这样做的问题是fromJust
不是total,因此如果使用Nothing
值调用它,它将崩溃:
Prelude Data.Maybe> fromJust Nothing
*** Exception: Maybe.fromJust: Nothing
您可以在或中看到相同类型的问题。虽然我不知道有一个内置的分部函数来提取Right
值,但您可以轻松编写一个模式匹配函数(如果忽略编译器警告):
同样,它在“快乐之路”场景中工作,但也很容易崩溃:
Prelude> extractRight $ Right 42
42
Prelude> extractRight $ Left "foo"
*** Exception: <interactive>:12:1-26: Non-exhaustive patterns in function extractRight
不过,它可能会失败:
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
关键是,你不需要从一个函子中提取一个值,你需要进入这个函子
单子
有时,应用于函子的函数返回已包装在同一数据容器中的值。例如,您可能有一个将字符串拆分为特定字符的函数。为了简单起见,让我们看看将字符串拆分为单词的内置函数words
:
Prelude> words "foo bar"
["foo","bar"]
如果您有一个字符串列表,并对每个字符串应用单词
,您将得到一个嵌套列表:
Prelude> fmap words ["foo bar", "baz qux"]
[["foo","bar"],["baz","qux"]]
结果是一个嵌套的数据容器,在本例中是一个列表列表。你可以用手把它压平
Prelude> foo = return ("foo", 2) :: IO (String, Int)
Prelude> :t foo
foo :: IO (String, Int)
Prelude> fmap countAndMultiply foo
6
Prelude> words "foo bar"
["foo","bar"]
Prelude> fmap words ["foo bar", "baz qux"]
[["foo","bar"],["baz","qux"]]
Prelude Control.Monad> join $ fmap words ["foo bar", "baz qux"]
["foo","bar","baz","qux"]