如何将IO操作转换为“纯”功能

时间:2018-09-25 02:22:11

标签: haskell io-monad

Haskell新手在这里。 我手头有一个更高阶的函数myTransform,它需要一个函数fn :: String -> String并做一些花哨的事情。 假设实现是

myTransform :: Int -> (String -> String) -> String -> [String]
myTransform n f = take n . iterate f

现在,我想转换一个外部程序,如果我理解正确的话,那就是IO操作。签名最好为String -> IO String

import System.Process
externProg :: String -> IO String
externProg s = readProcess "echo" ["-n", s, "+ 1"] ""

问题是,有什么方法可以在不更改甚至不知道String -> IO String如何实现的情况下,将String -> String函数放入该myTransform参数槽中?

3 个答案:

答案 0 :(得分:4)

不,您不能。您将必须制作myTransform的单声道版本。通常会附加一个大写的M.地图变成mapM。 fold成为foldM ...不幸的是没有迭代。因此,我将跳过iterateM并直接实现它。

myTransformM' :: (Monad m) => Int -> (String -> m String) -> String -> m [String]
myTransformM' 0 f str = return [str]
myTransformM' n f str = do
    results <- myTransformM (n-1) f str
    next <- f (head results)
    return (next:results)

myTransformM n f str = do
    results <- myTransformM' n f str
    return $ reverse results

您可能会注意到,第一个函数的结果是相反排列的。这是为了避免函数是二次函数。

您可以尝试实现iterateM会发生什么。它将永远循环。这是因为Haskell永远无法知道您是否会真正获得列表,或者在将来某个地方是否会出现IOError。同样,如果您使用Maybe单子,Haskell将永远不会知道您是否真的得到了Just list,还是在路上某个地方Nothing

iterateM :: (Monad m) => (a -> m a) -> a -> m [a]
iterateM f a = do
    result <- f a
    results <- iterateM f result
    return (result:results)

答案 1 :(得分:1)

这是一个常见的重复,但是我有一点...

否,您应该运行IO操作并因此获得String类型的值,该值将传递给myTransform

例如:

main :: IO ()
main =
  do stdout <- externProg "myProg"  -- "execute" the IO action and obtain "stdout :: String"
     let res = myTransform stdout  -- res :: String
     putStrLn res

或者,一旦您对语言感到满意并且对样式感到满意,就可以:

main :: IO ()
main = putStrLn . myTransform =<< externProg "myProg"

答案 2 :(得分:0)

yokto的回答很好地解释了这个问题。我只会再添加一个示例。

考虑此功能:

f1 :: (Int -> Int) -> Int
f1 g = g 10 + g 20

f2 :: (Int -> Int) -> Int
f2 g = g 20 + g 10

这些函数具有完全相同的语义。 Haskell实现可以将它希望的第一个重写为第二个,而不会影响结果。

现在,考虑

myG :: Int -> IO Int
myG x = print x >> return x

main :: IO ()
main = do
   x <- f1 myG  -- assume this could be made to work, somehow
   print x

该打印什么?直观地,它可以打印

10
20
30

20
10
30

取决于f1中使用的评估顺序。这很不好,因为也可以使用f2,它应该导致相同的结果,但可能会导致另一个结果。更糟糕的是,编译器可以将一个优化为另一个,因此不能真正保证任何特定的输出:编译器可以立即对其进行更改。

这是一个大问题,Haskell旨在避免该问题。 IO效果的顺序必须在程序中完全指定。为此,必须防止程序员将IO内容(如Int -> IO Int)转换为非IO内容(如Int -> Int)。

如果我们为f使用单子类型,如

f3 :: (Int -> IO Int) -> IO Int
f3 g = ...

然后Haskell将迫使我们指定g 10g 20之间的顺序。