如何 return 来自不纯方法的纯值
How to return a pure value from a impure method
我知道这听起来很微不足道,但我想知道如何从仿函数中解包一个值并将其 return 作为纯值?
我试过:
f::IO a->a
f x=(x>>=)
f= >>=
我应该在右边放什么?我不能使用 return
,因为它会再次将其包装回去。
这是微不足道的,所以这将是一个很长的答案。简而言之,问题在于签名 IO a -> a
不是 Haskell 中正确允许的类型。这实际上与 IO
是一个仿函数关系不大,而是 IO
是特殊的。
对于某些函子,您可以恢复纯值。例如,部分应用对 (,) a
是函子。我们通过 snd
.
展开值
snd :: (a,b) -> b
snd (_,b) = b
所以这是一个我们可以解包成纯值的函子,但这实际上与函子无关。它更多地与属于不同类别理论概念的对有关,Comonad,其中:
extract :: Comonad w => w a -> a
任何 Comonad
都将是一个函子,您可以为其恢复纯值。
许多(非共生的)仿函数——比方说"evaluators"——允许像被问到的东西。例如,我们可以用 maybe :: a -> Maybe a -> a
计算 Maybe
。通过提供默认值,maybe a
具有所需的类型 Maybe a -> a
。 State、evalState :: State s a -> s -> a
中的另一个有用示例,其参数颠倒但概念相同;给定 monad State s a
和初始状态 s
,我们展开纯值 a
.
终于到了IO
的细节了。 Haskell 语言或库中没有为 IO
提供 "evaluator"。我们可能会认为 运行 程序本身就是一个评估器——与 evalState
非常相似。但是,如果这是一个有效的概念性举措,那么它只会帮助您相信没有理智的方法可以从 IO
展开——任何编写的程序都只是其计算器函数的 IO a
输入。
相反,你被迫做的——设计——是在 IO
monad 中工作。例如,如果您有一个纯函数 f :: a -> b
,您可以通过 fmap f :: IO a -> IO b
在 IO
上下文中应用它
TL;DR 你无法从 IO
monad 中获得纯值。在 IO
上下文中应用纯函数,例如 fmap
这是一个常见问题:如何从我的 monad 中提取 'the' 值,不仅在 Haskell 中,而且在其他语言中也是如此.我有一个关于为什么这个问题不断出现的理论,所以我会尝试根据这个来回答;希望对你有帮助。
单一值的容器
您可以将 functor(因此也是 monad)视为 container 的值。这是最明显的(冗余)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
等。所有这些的共同点是它们包含单个值,这意味着您可以 提取值。
我认为当人们第一次遇到仿函数和单子时,他们往往会这样想数据容器的概念:作为一个单一值的容器。
可选值的容器
不幸的是,Haskell 中的一些常见单子似乎支持这个想法。例如,Maybe
也是一个数据容器,但可以包含零个或一个值。不幸的是,如果它存在,您仍然可以提取该值:
Prelude Data.Maybe> fromJust $ Just 42
42
这个问题是 fromJust
不是 全部 ,所以如果你用 Nothing
值调用它会崩溃:
Prelude Data.Maybe> fromJust Nothing
*** Exception: Maybe.fromJust: Nothing
您可以在 Either
中看到同样的问题。虽然我不知道提取 Right
值的内置部分函数,但您可以轻松地编写一个带有模式匹配的函数(如果您忽略编译器警告):
extractRight :: Either l r -> r
extractRight (Right x) = x
同样,它适用于 'happy path' 场景,但也很容易崩溃:
Prelude> extractRight $ Right 42
42
Prelude> extractRight $ Left "foo"
*** Exception: <interactive>:12:1-26: Non-exhaustive patterns in function extractRight
尽管如此,由于像 fromJust
这样的函数存在,我想它会欺骗那些不熟悉仿函数和单子概念的人,让他们将它们视为可以从中提取值的数据容器。
当你第一次遇到像 IO Int
这样的东西时,我能理解你为什么会想把它当作一个单一值的容器。从某种意义上说是,但从另一种意义上说,又不是。
多个值的容器
即使使用列表,您也可以(尝试)从列表中提取 'the' 值:
Prelude> head [42..1337]
42
不过,它可能会失败:
Prelude> head []
*** Exception: Prelude.head: empty list
然而,在这一点上,应该清楚的是,试图从任意函子中提取 'the' 值是无稽之谈。列表是一个函子,但它包含任意数量的值,包括零和无限多。
但是,您可以 始终做的是编写将 'contained' 值作为输入并 returns 另一个值作为输出的函数。这是此类函数的任意示例:
countAndMultiply :: Foldable t => (t a, Int) -> Int
countAndMultiply (xs, factor) = length xs * factor
虽然您不能 'extract the value' 超出列表,但您可以将函数应用于列表中的每个值:
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
重点是您不从函子中提取值,您进入函子。
单子
有时,您应用于仿函数的函数 returns 一个值已经包装在同一个数据容器中。例如,您可能有一个将字符串拆分为特定字符的函数。为了简单起见,我们只看一下将字符串拆分为单词的内置函数 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
?
类型的值
不是真的。 IO
的一个 解释 是它是一个容器,可以容纳 a
类型的任意值。根据这种解释,它类似于量子力学的 多世界 解释。 IO a
是 a
.
类型所有可能值的叠加
在薛定谔最初的思想实验中,盒子里的猫在被观察之前既是活的又是死的。这是叠加的两种可能状态。如果我们考虑一个名为 catIsAlive
的变量,它就相当于 True
和 False
的叠加。因此,您可以将 IO Bool
视为一组可能的值 {True, False}
,只有在观察到这些值时才会折叠成一个值。
同样,IO Word8
可以解释为所有可能Word8
值集合的叠加,即{0, 1, 2,.. 255}
,IO Int
是所有可能Int
值,IO String
所有可能的 String
值(即无限集),依此类推。
那么你如何观察这个值呢?
您不提取它,您在 数据容器内工作。如上所示,您可以在其上添加 fmap
和 join
。因此,您可以将应用程序编写为纯函数,然后用 fmap
、>>=
、join
等不纯值组合。
我知道这听起来很微不足道,但我想知道如何从仿函数中解包一个值并将其 return 作为纯值?
我试过:
f::IO a->a
f x=(x>>=)
f= >>=
我应该在右边放什么?我不能使用 return
,因为它会再次将其包装回去。
这是微不足道的,所以这将是一个很长的答案。简而言之,问题在于签名 IO a -> a
不是 Haskell 中正确允许的类型。这实际上与 IO
是一个仿函数关系不大,而是 IO
是特殊的。
对于某些函子,您可以恢复纯值。例如,部分应用对 (,) a
是函子。我们通过 snd
.
snd :: (a,b) -> b
snd (_,b) = b
所以这是一个我们可以解包成纯值的函子,但这实际上与函子无关。它更多地与属于不同类别理论概念的对有关,Comonad,其中:
extract :: Comonad w => w a -> a
任何 Comonad
都将是一个函子,您可以为其恢复纯值。
许多(非共生的)仿函数——比方说"evaluators"——允许像被问到的东西。例如,我们可以用 maybe :: a -> Maybe a -> a
计算 Maybe
。通过提供默认值,maybe a
具有所需的类型 Maybe a -> a
。 State、evalState :: State s a -> s -> a
中的另一个有用示例,其参数颠倒但概念相同;给定 monad State s a
和初始状态 s
,我们展开纯值 a
.
终于到了IO
的细节了。 Haskell 语言或库中没有为 IO
提供 "evaluator"。我们可能会认为 运行 程序本身就是一个评估器——与 evalState
非常相似。但是,如果这是一个有效的概念性举措,那么它只会帮助您相信没有理智的方法可以从 IO
展开——任何编写的程序都只是其计算器函数的 IO a
输入。
相反,你被迫做的——设计——是在 IO
monad 中工作。例如,如果您有一个纯函数 f :: a -> b
,您可以通过 fmap f :: IO a -> IO b
IO
上下文中应用它
TL;DR 你无法从 IO
monad 中获得纯值。在 IO
上下文中应用纯函数,例如 fmap
这是一个常见问题:如何从我的 monad 中提取 'the' 值,不仅在 Haskell 中,而且在其他语言中也是如此.我有一个关于为什么这个问题不断出现的理论,所以我会尝试根据这个来回答;希望对你有帮助。
单一值的容器
您可以将 functor(因此也是 monad)视为 container 的值。这是最明显的(冗余)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
等。所有这些的共同点是它们包含单个值,这意味着您可以 提取值。
我认为当人们第一次遇到仿函数和单子时,他们往往会这样想数据容器的概念:作为一个单一值的容器。
可选值的容器
不幸的是,Haskell 中的一些常见单子似乎支持这个想法。例如,Maybe
也是一个数据容器,但可以包含零个或一个值。不幸的是,如果它存在,您仍然可以提取该值:
Prelude Data.Maybe> fromJust $ Just 42
42
这个问题是 fromJust
不是 全部 ,所以如果你用 Nothing
值调用它会崩溃:
Prelude Data.Maybe> fromJust Nothing
*** Exception: Maybe.fromJust: Nothing
您可以在 Either
中看到同样的问题。虽然我不知道提取 Right
值的内置部分函数,但您可以轻松地编写一个带有模式匹配的函数(如果您忽略编译器警告):
extractRight :: Either l r -> r
extractRight (Right x) = x
同样,它适用于 'happy path' 场景,但也很容易崩溃:
Prelude> extractRight $ Right 42
42
Prelude> extractRight $ Left "foo"
*** Exception: <interactive>:12:1-26: Non-exhaustive patterns in function extractRight
尽管如此,由于像 fromJust
这样的函数存在,我想它会欺骗那些不熟悉仿函数和单子概念的人,让他们将它们视为可以从中提取值的数据容器。
当你第一次遇到像 IO Int
这样的东西时,我能理解你为什么会想把它当作一个单一值的容器。从某种意义上说是,但从另一种意义上说,又不是。
多个值的容器
即使使用列表,您也可以(尝试)从列表中提取 'the' 值:
Prelude> head [42..1337]
42
不过,它可能会失败:
Prelude> head []
*** Exception: Prelude.head: empty list
然而,在这一点上,应该清楚的是,试图从任意函子中提取 'the' 值是无稽之谈。列表是一个函子,但它包含任意数量的值,包括零和无限多。
但是,您可以 始终做的是编写将 'contained' 值作为输入并 returns 另一个值作为输出的函数。这是此类函数的任意示例:
countAndMultiply :: Foldable t => (t a, Int) -> Int
countAndMultiply (xs, factor) = length xs * factor
虽然您不能 'extract the value' 超出列表,但您可以将函数应用于列表中的每个值:
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
重点是您不从函子中提取值,您进入函子。
单子
有时,您应用于仿函数的函数 returns 一个值已经包装在同一个数据容器中。例如,您可能有一个将字符串拆分为特定字符的函数。为了简单起见,我们只看一下将字符串拆分为单词的内置函数 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
?
不是真的。 IO
的一个 解释 是它是一个容器,可以容纳 a
类型的任意值。根据这种解释,它类似于量子力学的 多世界 解释。 IO a
是 a
.
在薛定谔最初的思想实验中,盒子里的猫在被观察之前既是活的又是死的。这是叠加的两种可能状态。如果我们考虑一个名为 catIsAlive
的变量,它就相当于 True
和 False
的叠加。因此,您可以将 IO Bool
视为一组可能的值 {True, False}
,只有在观察到这些值时才会折叠成一个值。
同样,IO Word8
可以解释为所有可能Word8
值集合的叠加,即{0, 1, 2,.. 255}
,IO Int
是所有可能Int
值,IO String
所有可能的 String
值(即无限集),依此类推。
那么你如何观察这个值呢?
您不提取它,您在 数据容器内工作。如上所示,您可以在其上添加 fmap
和 join
。因此,您可以将应用程序编写为纯函数,然后用 fmap
、>>=
、join
等不纯值组合。