如何在IO代码中与纯算法进行交互
How to interact with pure algorithm in IO code
为了用一个简单的例子来说明这一点,假设我已经实现了 filter
:
filter :: (a -> Bool) -> [a] -> [a]
我有一个与现实世界交互的谓词p
:
p :: a -> IO Bool
如何在不编写单独实现的情况下使其与 filter
一起工作:
filterIO :: (a -> IO Bool) -> [a] -> IO [a]
大概如果我能把p
变成p'
:
p': IO (a -> Bool)
那我可以
main :: IO ()
main = do
p'' <- p'
print $ filter p'' [1..100]
但是我没能找到转换。
已编辑:
正如人们在评论中指出的那样,这样的转换没有意义,因为它会破坏 IO Monad 的封装。
现在的问题是,我能否构建我的代码,使 pure 和 IO 版本不完全重复核心逻辑?
How do it make it work with filter without writing a separate implementation
这是不可能的,事实上这种事情不可能是设计使然 - Haskell 对其类型设置了严格的限制,您必须遵守它们。你不能把 IO
洒在整个地方 willy-nilly.
Now the question is, can I structure my code so that the pure and IO versions don't completely duplicate the core logic?
您会对 filterM
. Then, you can get both the functionality of filterIO
by using the IO
monad and the pure functionality using the Identity
monad 感兴趣。当然,对于纯粹的情况,您现在必须支付 wrapping/unwrapping(或 coerce
ing)Identity
包装器的额外价格。 (旁注:由于 Identity
是 newtype
,这只是代码可读性成本,而不是运行时成本。)
ghci> data Color = Red | Green | Blue deriving (Read, Show, Eq)
这是一个monadic示例(注意仅包含Red
、Blue
和Blue
的行在提示符处是user-entered):
ghci> filterM (\x -> do y<-readLn; pure (x==y)) [Red,Green,Blue]
Red
Blue
Blue
[Red,Blue] :: IO [Color]
这里是一个纯粹的例子:
ghci> filterM (\x -> Identity (x /= Green)) [Red,Green,Blue]
Identity [Red,Blue] :: Identity [Color]
如前所述,您可以使用 filterM
完成此特定任务。然而,通常最好保持 Haskell 的特征,即 IO 和计算的严格分离。在你的情况下,你可以一次性勾选所有必要的 IO
然后用漂亮、可靠、容易的 testable 纯代码进行有趣的过滤(即在这里,只需使用普通的 filter
):
type A = Int
type Annotated = (A, Bool)
p' :: Annotated -> Bool
p' = snd
main :: IO ()
main = do
candidates <- forM [1..100] $ \n -> do
permitted <- p n
return (n, permitted)
print $ fst <$> filter p' candidates
在这里,我们首先用一个标志来注释每个数字,该标志表明环境在说什么。然后可以在实际的过滤步骤中简单地读出该标志,而不需要任何进一步的 IO。
简而言之,可以这样写:
main :: IO ()
main = do
candidates <- forM [1..100] $ \n -> (n,) <$> p n
print $ fst <$> filter snd candidates
虽然对于这个特定的任务不可行,但我还要补充一点,你 可以 原则上用你的 [=17] 之类的东西实现 IO
分离=].这要求 A
类型“足够小”,以便您可以使用 all 值评估谓词,而这些值是完全可能的。例如,
import qualified Data.Map as Map
type A = Char
p' :: IO (A -> Bool)
p' = (Map.!) . Map.fromList <$> mapM (\c -> (c,) <$> p c) ['[=12=]'..]
这为 1114112 个字符中的 all 计算谓词一次,并将结果存储在查找 table.
中
为了用一个简单的例子来说明这一点,假设我已经实现了 filter
:
filter :: (a -> Bool) -> [a] -> [a]
我有一个与现实世界交互的谓词p
:
p :: a -> IO Bool
如何在不编写单独实现的情况下使其与 filter
一起工作:
filterIO :: (a -> IO Bool) -> [a] -> IO [a]
大概如果我能把p
变成p'
:
p': IO (a -> Bool)
那我可以
main :: IO ()
main = do
p'' <- p'
print $ filter p'' [1..100]
但是我没能找到转换。
已编辑: 正如人们在评论中指出的那样,这样的转换没有意义,因为它会破坏 IO Monad 的封装。
现在的问题是,我能否构建我的代码,使 pure 和 IO 版本不完全重复核心逻辑?
How do it make it work with filter without writing a separate implementation
这是不可能的,事实上这种事情不可能是设计使然 - Haskell 对其类型设置了严格的限制,您必须遵守它们。你不能把 IO
洒在整个地方 willy-nilly.
Now the question is, can I structure my code so that the pure and IO versions don't completely duplicate the core logic?
您会对 filterM
. Then, you can get both the functionality of filterIO
by using the IO
monad and the pure functionality using the Identity
monad 感兴趣。当然,对于纯粹的情况,您现在必须支付 wrapping/unwrapping(或 coerce
ing)Identity
包装器的额外价格。 (旁注:由于 Identity
是 newtype
,这只是代码可读性成本,而不是运行时成本。)
ghci> data Color = Red | Green | Blue deriving (Read, Show, Eq)
这是一个monadic示例(注意仅包含Red
、Blue
和Blue
的行在提示符处是user-entered):
ghci> filterM (\x -> do y<-readLn; pure (x==y)) [Red,Green,Blue]
Red
Blue
Blue
[Red,Blue] :: IO [Color]
这里是一个纯粹的例子:
ghci> filterM (\x -> Identity (x /= Green)) [Red,Green,Blue]
Identity [Red,Blue] :: Identity [Color]
如前所述,您可以使用 filterM
完成此特定任务。然而,通常最好保持 Haskell 的特征,即 IO 和计算的严格分离。在你的情况下,你可以一次性勾选所有必要的 IO
然后用漂亮、可靠、容易的 testable 纯代码进行有趣的过滤(即在这里,只需使用普通的 filter
):
type A = Int
type Annotated = (A, Bool)
p' :: Annotated -> Bool
p' = snd
main :: IO ()
main = do
candidates <- forM [1..100] $ \n -> do
permitted <- p n
return (n, permitted)
print $ fst <$> filter p' candidates
在这里,我们首先用一个标志来注释每个数字,该标志表明环境在说什么。然后可以在实际的过滤步骤中简单地读出该标志,而不需要任何进一步的 IO。
简而言之,可以这样写:
main :: IO ()
main = do
candidates <- forM [1..100] $ \n -> (n,) <$> p n
print $ fst <$> filter snd candidates
虽然对于这个特定的任务不可行,但我还要补充一点,你 可以 原则上用你的 [=17] 之类的东西实现 IO
分离=].这要求 A
类型“足够小”,以便您可以使用 all 值评估谓词,而这些值是完全可能的。例如,
import qualified Data.Map as Map
type A = Char
p' :: IO (A -> Bool)
p' = (Map.!) . Map.fromList <$> mapM (\c -> (c,) <$> p c) ['[=12=]'..]
这为 1114112 个字符中的 all 计算谓词一次,并将结果存储在查找 table.
中