Jus*_* L. 15 haskell referential-transparency
我正在挑选一个特定的任务来说明我在说什么
假设我想通过检查下面的每个数字(如果它是一个因子,然后将它们加在一起)来天真地找到大数的所有因子的总和.
在命令式编程语言中,IO和纯计算之间没有分离,你可能会做这样的事情
def sum_of_factors(n):
sum = 0
for i between 1 and n:
if (n % i == 0):
sum += i
return sum
Run Code Online (Sandbox Code Playgroud)
但是如果我n很大,我最终会在计算完成之前长时间盯着空屏幕.所以我添加一些日志记录 -
def sum_of_factors(n):
sum = 0
for i between 1 and n:
if (i % 1000 == 0):
print "checking $i..."
if (n % i == 0):
print "found factor $i"
sum += 1
return sum
Run Code Online (Sandbox Code Playgroud)
实际上,这个增加是微不足道的.
现在,如果我要在教科书中使用haskell,我可能会这样做
sum_of_factors :: Int -> Int
sum_of_factors n = foldl' (+) 0 factors
where
factors = filter ((== 0) . (mod n)) [1..n]
Run Code Online (Sandbox Code Playgroud)
我遇到了和以前一样的问题......对于大数字,我只是盯着空白的屏幕看了一会儿.
但我无法弄清楚如何在Haskell代码中注入相同类型的跟踪/日志记录.除了可能使用显式递归重新实现折叠之外,我不确定获得与命令式不纯代码相同的跟踪模式/结果.
Haskell有一个教员可以做到这一点吗?一个不需要重构一切的东西?
谢谢
Yur*_*ras 14
有许多可能的解决方案.
最简单的方法是更改函数以返回事件流而不是最终结果.你sum_of_factors不为我编译,所以我将使用一个sum函数作为例子.想法是发送Left message以显示进度,并Right result在完成后发送.感谢懒惰评估,您将在功能正常工作时看到进度事件:
import Control.Monad
sum' :: [Int] -> [Either String Int]
sum' = go step 0
where
step = 10000
go _ res [] = [Right res]
go 0 res (x:xs) = Left ("progress: " ++ show x) : go step (res + x) xs
go c res (x:xs) = go (c - 1) (res + x) xs
main :: IO ()
main = do
forM_ (sum' [1..1000000]) $ \event ->
case event of
Right res -> putStrLn $ "Result: " ++ show res
Left str -> putStrLn str
Run Code Online (Sandbox Code Playgroud)
其他(从我的观点来看更好)解决方案是使函数monadic:
class Monad m => LogM m where
logMe :: String -> m ()
instance LogM IO where
logMe = putStrLn
sum' :: LogM m => [Int] -> m Int
sum' = go step 0
where
step = 10000
go _ res [] = return res
go 0 res (x:xs) = logMe ("progress: " ++ show x) >> go step (res + x) xs
go c res (x:xs) = go (c - 1) (res + x) xs
main :: IO ()
main = sum' [1..1000000] >>= print
Run Code Online (Sandbox Code Playgroud)
或使用foldM:
import Control.Monad
sum' :: LogM m => [Int] -> m Int
sum' = liftM snd . foldM go (0, 0)
where
step = 10000
-- `!` forces evaluation and prevents build-up of thunks.
-- See the BangPatterns language extension.
go (!c, !res) x = do
when (c == 0) $ logMe ("progress: " ++ show x)
return $ ((c + 1) `mod` step, res + x)
Run Code Online (Sandbox Code Playgroud)
Pet*_*lák 10
如果需要快速和脏的日志记录,可以使用Debug.Trace.它允许您快速将日志记录功能添加到纯代码中.(当然,它使用不安全的东西.)准备好它的日志记录输出出现在你预期的不同时间(或根本不出现) - 这是将不纯的调试代码添加到懒惰的纯计算中的结果评估.
否则,您必须使用monadic代码,以便正确排序日志输出.其中一个使用良好的库IO是hslogger.
如果您不想将代码绑定到IO(这是非常明智的),Yuras的方法是可行的方法.创建自己的monad类型类,描述您的日志记录操作(可能具有不同的级别等).然后,有一个生成日志输出的实例,比如答案,以及一个不做任何事情的实例,比如
instance LogM Identity where
logMe _ = return ()
Run Code Online (Sandbox Code Playgroud)
然后,只需切换你正在使用的monad,就可以打开/关闭loggin,编译器会优化Identitymonad.