如何在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(或 coerceing)Identity 包装器的额外价格。 (旁注:由于 Identitynewtype,这只是代码可读性成本,而不是运行时成本。)

 ghci> data Color = Red | Green | Blue deriving (Read, Show, Eq)

这是一个monadic示例(注意仅包含RedBlueBlue的行在提示符处是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.