如何在IO代码中与纯算法交互

时间:2017-01-23 17:27:16

标签: haskell io monads purely-functional

为了用一个简单的例子说明这一点,请说我已经实现了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的封装。

现在的问题是,我可以构建我的代码,以便纯版本和IO版本不会完全复制核心逻辑吗?

2 个答案:

答案 0 :(得分:5)

  

如何在不编写单独的实现的情况下使其与过滤器一起使用

这是不可能的,事实上这种事情是不可能的是设计--Haskell对其类型设置了严格的限制,你必须遵守它们。你不能无所事事地洒遍IO

  

现在的问题是,我可以构建我的代码,以便纯版本和IO版本不会完全复制核心逻辑吗?

您会对filterM感兴趣。然后,您可以使用filterIO monad获得IO的功能,使用Identity monad获得纯功能。当然,对于纯粹的情况,您现在必须支付包装/展开(或coerce} Identity包装器的额外费用。 (旁注:因为Identitynewtype,这只是代码可读性成本,而不是运行时成本。)

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

以下是一个monadic示例(请注意,只有RedBlueBlue的行是在提示时输入的用户名:

 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]

答案 1 :(得分:1)

如前所述,您可以使用filterM执行此特定任务。但是,通常最好保持Haskell特有的严格分离IO和计算。在您的情况下,您可以一次性勾选所有必要的IO,然后使用漂亮,可靠,易于测试的纯代码进行有趣的过滤(即,这里只需使用正常的filter):< / p>

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

虽然这项特定任务不可行,但我还要补充一点, 原则上可以通过类似IO的内容实现p'分隔。这要求类型A“足够小”,您可以使用所有值来评估谓词。例如,

import qualified Data.Map as Map

type A = Char

p' :: IO (A -> Bool)
p' = (Map.!) . Map.fromList <$> mapM (\c -> (c,) <$> p c) ['\0'..]

这会对1114112个字符的所有评估一次谓词,并将结果存储在查找表中。