2017-08-30 111 views
5

隨着GHC 8.0.2版下面的程序:兩個函數調用,但只有一個跟蹤顯示

import Debug.Trace 

f=trace("f was called")$(+1) 

main = do 
    print $ f 1 
    print $ f 2 

輸出:

f was called 
2 
3 

它是預期的行爲?如果是,爲什麼?我預計字符串f was called要打印兩次,一次在2之前,一次在3之前打印一次。

對TIO相同的結果:Try it online!

編輯 但這個方案:

import Debug.Trace 

f n=trace("f was called:"++show n)$n+1 

main = do 
    print $ f 1 
    print $ f 2 

輸出:

f was called:1 
2 
f was called:2 
3 

Try it online!

我懷疑這些行爲與懶惰有關,但我的問題依然存在:這是預期的行爲,如果是的話,爲什麼?

Hackage斷言此:

的跟蹤功能輸出給出作爲它的第一個參數 跟蹤消息,返回第二個參數作爲其結果之前。

我在第一個例子中沒有看到它。

EDIT 2基於@amalloy評論第三個例子:

import Debug.Trace 

f n=trace "f was called"$n+1 

main = do 
    print $ f 1 
    print $ f 2 

輸出:

f was called 
2 
f was called 
3 
+0

請注意,'trace'類型爲'String - > a - > a'。在'f ='情況下取代'a'的類型與'f n ='情況下的類型不同。這也應該有助於解釋不同的行爲。 –

回答

2

這確實是懶惰的結果。

懶惰意味着僅僅定義一個值並不意味着它會被評估;這隻會在需要某些東西時纔會發生。如果不需要,實際產生它的代碼不會「做任何事情」。如果特定值所需的代碼運行,但只有第一次它將需要;如果還有其他引用了相同的值並再次使用,則這些用法將直接使用第一次生成的值。

您必須記住,函數在任何意義上都是值;適用於普通數值的所有內容也適用於函數。所以,你的f定義簡單地寫一個值的表達式,表達式的評估將被推遲,直到f值實際需要,並且它的需要值的兩倍(功能)的表達式計算將被保存和再利用第二次。

讓我們看看它的詳細信息:

f=trace("f was called")$(+1) 

您定義的值f用一個簡單的公式(不使用寫入參數中的任何語法糖在等式的左邊,或提供通過多個方程案例)。因此,我們可以簡單地將右側作爲單個表達式來定義值f。只要確定它什麼都不做,坐在那裏,直到你撥打:

print $ f 1 

現在打印需要它的參數進行評估,所以這迫使表達f 1。但是我們不能先申請f1而不先強制f。所以我們需要弄清楚表達式trace "f was called" $ (+1)評估的是什麼函數。所以trace實際上是所謂的,做它的不安全IO印刷f was called出現在終端,然後trace返回第二個參數:(+1)

所以,現在我們知道f是什麼功能:(+1)f現在將直接引用該功能,而不需要評估的原代碼trace("f was called")$(+1)如果f被再次調用。這就是爲什麼第二個print什麼都不做的原因。

這種情況是完全不同的,即使它可能類似於:

f n=trace("f was called:"++show n)$n+1 

在這裏,我們使用語法糖通過在左側寫參數定義的功能。讓我們desugar,爲拉姆達符號來更清楚地看到實際值被綁定到f是:

f = \n -> trace ("f was called:" ++ show n) $ n + 1 

在這裏,我們已經寫了一個函數值直接,而不是可以進行評估,以導致表達一個函數。因此當需要評估f之前,可以調用1時,f的值就是整個函數; trace調用是裏面的函數而不是被調用的函數結果函數中。所以trace不作爲評估f的一部分被調用,它被稱爲評估應用程序f 1的一部分。如果您保存了該結果(例如通過執行let x = f 1),然後多次打印,則只能看到一條曲線。但是當我們來評估f 2時,trace調用仍然存在於該函數中的值爲f,因此當再次調用f時,因此是trace

4

定義f時,不調用它時你跟蹤打印。如果你想跟蹤發生的調用的一部分,你應該確保它不計算,直到接收到一個參數:

f x = trace "f was called" $ x + 1 

此外,當我運行TIO我看不出痕跡出現在所有。 trace並不是真正可靠的打印方式,因爲它欺騙了構建該語言的IO模型。評估順序中最微妙的變化會干擾它。當然對於調試你可以使用它,但即使這個簡單的例子表明它不保證有很大的幫助。

在你的編輯,你引用的trace文檔:

的跟蹤功能輸出給作爲其第一個參數 跟蹤消息,返回第二個參數作爲它的結果之前。

事實上,這正是您的程序中發生的情況!當定義f

trace "f was called" $ (+ 1) 

需要進行評估。首先打印「f被稱爲」。然後,trace評估並返回(+ 1)。這是trace表達式的最終值,因此(+ 1)f定義的值。 trace已經消失了,看?

+0

定義時並非如此:如果您不稱呼它,您將看不到任何痕跡。在TIO中,它在「調試」框(stderr?)中出現一次。查看我的編輯另一個例子。 – jferard

+0

不,你不需要調用函數來打印它:你只需要評估它。例如,'main = seq f $ return()'也打印一次。即使沒有使用'seq'也是必要的,以強制f'被評估。像Haskell中的大多數情況一樣,定義它但不使用它沒有副作用。但是你的'f'的副作用的確確實在於定義它,而不是叫它;你只需確保強制定義的副作用。 – amalloy

+0

@ammaloy我在評論後做了編輯。確定「評估」與「呼叫」。 – jferard

相關問題