我在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
答案 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 a
时b == 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