逐步显示减少过程

时间:2014-08-16 16:34:13

标签: haskell

我在Haskell的SICP做运动1.20并想知道是否有办法以编程方式可视化减少步骤?它应该如下所示:http://community.schemewiki.org/?sicp-ex-1.20

Insted of "b is 6"我希望看到像gcd' 40 (rem 206 40)这样的未计算(或咖喱)表达式。

示例程序:

import Debug.Trace

gcd' a b =
    if b == 0
        then a
        else trace ("b is " ++ show b) gcd' b (a `rem` b)

main = print $ gcd' 206 40

问题的原始定义可以在这里找到:http://sarabander.github.io/sicp/html/1_002e2.xhtml#g_t1_002e2_002e5

2 个答案:

答案 0 :(得分:4)

执行此操作的right way是使用Writer monad。您可以使用它来轻松地为代码添加日志记录功能:

import Control.Monad.Writer

gcd' :: Int -> Int -> (Int, [String])
gcd' a b = runWriter $ gcdW a b
    where
        gcdW :: Int -> Int -> Writer [String] Int
        gcdW a 0 = return a
        gcdW a b = do
            tell ["b is " ++ show b]
            gcdW b (a `rem` b)

main :: IO ()
main = do
    let (result, logs) = gcd' 206 40
    mapM_ putStrLn logs
    print result

此实现的作用是将GCD算法包装在Writer monad中,该monad用于在许多计算中聚合某种结果。通常,它用于记录消息或输出中间结果。这里我们想要的是以漂亮的印刷形式获得b的每个中间值的结果和日志。由于每条消息都是String,我们希望建立一个String列表,这就是为什么它必须用tell中的方括号括起来的原因。算法的其余部分基本相同,只需return ab == 0,否则以与以前完全相同的方式执行递归。 gcd'函数只包含Writer monad,以便对用户透明。然后我们可以获得结果和日志消息并将它们打印出来然后我们想要。 trace函数通常仅用于调试,特别是因为它使用了unsafePerformIO。使用Writer monad可以重复使用此代码,而无需担心每次都打印出这些步骤。

答案 1 :(得分:1)

首先,让我们重写您的gcd函数,为了便于阅读,我将摆脱trace

gcd :: Int -> Int -> Int
gcd a 0 = a
gcd a b = gcd b (a `rem` b)

请注意,在此函数中(以及在您编写的函数中),在我们给您答案之前,我们必须知道b是否为零。这意味着gcd在其第二个参数中是严格的。减少gcd 206 40现在应该非常简单:

gcd 206 40
gcd 40 (206 `rem` 40)
gcd 40 6
gcd 6 (40 `rem` 6)
gcd 6 4
gcd 4 (6 `rem` 4)
gcd 4 2
gcd 2 (4 `rem` 2)
gcd 2 0
2