Haskell。如何在纯 Haskell 函数中执行 IO?如何在执行函数时打印中间结果?
Haskell. How to do IO inside pure Haskell function? How to print intermediate results while function is being executed?
在许多命令式编程语言中,如 Java、C 或 Python,我们可以轻松添加一个 print 函数,它可以为我们提供有关中间状态的信息程序。
我的目标是找到一种方法来完成 Haskell 中类似的事情。我想要的功能不仅可以计算值,还可以打印一些东西。下面的功能是我想做的事情的简化版本。我的实际功能太复杂和不全面,没有上下文在这里展示。
我的想法是拥有一个“纯”Haskell 函数,其中包含一个内部具有 [Int] -> IO () -> Int
类型签名的辅助函数。 IO 参数在 where
子句中初始化为 do
块。但不幸的是,当我 运行 GHCI 中的函数时, do
块没有执行。函数编译成功
module Tests where
-- Function returns the sum of the list and tries to print some info
-- but no IO actually happens
pureFuncWithIO :: [Int] -> Int
pureFuncWithIO [] = 0
pureFuncWithIO nums = auxIOfunc nums (return ())
where
auxIOfunc [] _ = 0
auxIOfunc (n : ns) _ = n + auxIOfunc ns (sneakyIOaction n)
sneakyIOaction n
= do -- Not executed
putStrLn $ "adding " ++ (show n);
return ()
GHCI 测试中的输出:
*Tests> pureFuncWithIO [1,2,3,4,5]
15
与此同时,我期待这样的事情:
*Tests> pureFuncWithIO [1,2,3,4,5]
adding 1
adding 2
adding 3
adding 4
adding 5
15
是否有可能想出一种方法让 IO
进入内部,同时保持最外层函数的 return 类型,而不是 IO a
风格?谢谢!
你的 sneakyIOaction
没有被执行,因为你将它的结果作为参数传递给 auxIOfunc
,但从不使用那个参数,而且 haskell
作为懒惰的混蛋,它永远不会执行它。
如果你尝试使用所述参数,你会发现你不能。它的类型不允许你用它做任何事情,除了与其他 IO
东西结合。
有一种方法可以做你想做的事,但它是黑暗的一面。你需要 unsafePerformIO
unsafePerformIO :: IO a -> a
这些东西基本上可以让你执行任何 IO
。棘手的事情你必须消耗结果,否则你可能会因为它的懒惰而 haskell
跳过它。如果你真的想使用它,你可能想查看 seq
,但实际上并不需要结果。
这种类型的签名
pureFuncWithIO :: [Int] -> Int
向调用者承诺不会观察到任何副作用(如打印)。编译器将拒绝任何执行 IO 的尝试。存在一些用于调试的异常 (Debug.Trace
),但它们不应留在生产代码中。还有一些“禁止的”、不安全的低级函数,永远不要在常规代码中使用——你应该假装这些根本不存在。
如果你想做IO,你需要一个IOreturn类型。
pureFuncWithIO :: [Int] -> IO Int
这样做可以在其余代码中加入副作用。
pureFuncWithIO [] = return 0
pureFuncWithIO (n : ns) = do
putStrLn $ "adding " ++ show n
res <- pureFuncWithIO ns
return (n + res)
Haskell设计的一个重点是将不能做IO的函数和可以做IO的函数严格分开。 Haskell 类型系统旨在防止在非 IO 上下文中执行 IO。
在许多命令式编程语言中,如 Java、C 或 Python,我们可以轻松添加一个 print 函数,它可以为我们提供有关中间状态的信息程序。
我的目标是找到一种方法来完成 Haskell 中类似的事情。我想要的功能不仅可以计算值,还可以打印一些东西。下面的功能是我想做的事情的简化版本。我的实际功能太复杂和不全面,没有上下文在这里展示。
我的想法是拥有一个“纯”Haskell 函数,其中包含一个内部具有 [Int] -> IO () -> Int
类型签名的辅助函数。 IO 参数在 where
子句中初始化为 do
块。但不幸的是,当我 运行 GHCI 中的函数时, do
块没有执行。函数编译成功
module Tests where
-- Function returns the sum of the list and tries to print some info
-- but no IO actually happens
pureFuncWithIO :: [Int] -> Int
pureFuncWithIO [] = 0
pureFuncWithIO nums = auxIOfunc nums (return ())
where
auxIOfunc [] _ = 0
auxIOfunc (n : ns) _ = n + auxIOfunc ns (sneakyIOaction n)
sneakyIOaction n
= do -- Not executed
putStrLn $ "adding " ++ (show n);
return ()
GHCI 测试中的输出:
*Tests> pureFuncWithIO [1,2,3,4,5]
15
与此同时,我期待这样的事情:
*Tests> pureFuncWithIO [1,2,3,4,5]
adding 1
adding 2
adding 3
adding 4
adding 5
15
是否有可能想出一种方法让 IO
进入内部,同时保持最外层函数的 return 类型,而不是 IO a
风格?谢谢!
你的 sneakyIOaction
没有被执行,因为你将它的结果作为参数传递给 auxIOfunc
,但从不使用那个参数,而且 haskell
作为懒惰的混蛋,它永远不会执行它。
如果你尝试使用所述参数,你会发现你不能。它的类型不允许你用它做任何事情,除了与其他 IO
东西结合。
有一种方法可以做你想做的事,但它是黑暗的一面。你需要 unsafePerformIO
unsafePerformIO :: IO a -> a
这些东西基本上可以让你执行任何 IO
。棘手的事情你必须消耗结果,否则你可能会因为它的懒惰而 haskell
跳过它。如果你真的想使用它,你可能想查看 seq
,但实际上并不需要结果。
这种类型的签名
pureFuncWithIO :: [Int] -> Int
向调用者承诺不会观察到任何副作用(如打印)。编译器将拒绝任何执行 IO 的尝试。存在一些用于调试的异常 (Debug.Trace
),但它们不应留在生产代码中。还有一些“禁止的”、不安全的低级函数,永远不要在常规代码中使用——你应该假装这些根本不存在。
如果你想做IO,你需要一个IOreturn类型。
pureFuncWithIO :: [Int] -> IO Int
这样做可以在其余代码中加入副作用。
pureFuncWithIO [] = return 0
pureFuncWithIO (n : ns) = do
putStrLn $ "adding " ++ show n
res <- pureFuncWithIO ns
return (n + res)
Haskell设计的一个重点是将不能做IO的函数和可以做IO的函数严格分开。 Haskell 类型系统旨在防止在非 IO 上下文中执行 IO。