如何将 IO 操作转换为"纯"函数



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是如何实现的?

不,你不能。你将不得不制作一个 myTransform 的 monadic 版本。习惯上附加一个大写的M.即地图变成mapM。折叠成为折叠...不幸的是,没有迭代M。因此,我将跳过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。同样,如果你拿了也许的单子,哈斯克尔永远不会知道你是否真的拿回了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)

这是一个常见的重复,但我有一刻所以...

否,您应该运行 IO 操作,从而获取传递给myTransformString类型化值。

例如:

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"

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之间的顺序.

最新更新