2016-11-11 15 views
1

我使用這個基本的宏作爲構建塊其它的定時宏:簡單的宏似乎不執行給定的形式

(defmacro time-pure 
    "Evaluates expr and returns the time it took. 
    Modified the native time macro to return the time taken." 
    [expr] 
    `(let [start# (current-nano-timestamp) 
     ret# ~expr] 
    (/ (double (- (current-nano-timestamp) start#)) 1000000.0))) 

我測試過這一點,並在其它宏使用過,所以我知道它工作正常。

可以通過下面的代碼片段來說明我的問題:

(defmacro time-each [& exprs] 
    `(mapv #(time-pure %) '~exprs)) 

我希望它可以給每個表達time-each,在那裏執行和時間它;返回結果。然而,當我測試了一下,瞬間完成:

(time-each 
    (Thread/sleep 500) 
    (Thread/sleep 1000)) 

[0.036571 0.0] 

我很困惑這個,因爲我知道,(time-pure (Thread/sleep 1000))將花費大約一秒鐘返回,而這一切的宏觀它委託給time-pure

這是什麼造成的?我真的不知道如何正確調試一個宏。我用macro-expand-1檢查生成的代碼:

(clojure.pprint/pprint 
    (macroexpand-1 
    '(time-each 
     (Thread/sleep 500) 
     (Thread/sleep 1000)))) 

(clojure.core/mapv 
(fn* 
    [p1__1451__1452__auto__] 
    (helpers.general-helpers/time-pure p1__1451__1452__auto__)) 
'((Thread/sleep 500) (Thread/sleep 1000))) 

但沒有真正站出來給我。

這是怎麼回事?

(注意,這是一個問題,我發佈了幾分鐘前,我意識到,我展示了令人費解的情況下的欺騙,所以我做了一個更好的例子。)

+0

你想讓我把另一個連接到這個嗎? –

+0

@ArthurUlfeldt呃,我把它刪除了,所以只有非常高級的用戶才能看到它。除了使用的代碼之外,它基本相同。回想起來,我可能應該編輯原文。我不確定爲什麼我提出了一個新問題。 – Carcigenicate

回答

1
(defmacro time-each [& exprs] 
    `(list [email protected](for [expr exprs] 
      `(time-pure ~expr)))) 

你必須要小心,不要評價time-pure上下文之外的表情,在亞瑟的答案,其評估每個表達式,然後調用的「看(非常快)操作time-pure完成結果」。相反,在評估之前,在每個表達式周圍包裝time-pure

+0

這是什麼在防止表達式在被賦予「時間純粹」之前被評估?對不起,我仍然試圖不用我的頭圍繞宏。 – Carcigenicate

+0

我注意到你的一些宏答案,你在'map'上使用'for'。我會在那裏有一個特別的原因,還是僅僅是個人風格? – Carcigenicate

+0

似乎是最乾淨和最直接的。謝謝。 – Carcigenicate

2

解決方案

我想這裏發生的是你的mapv在編譯時間後執行,這意味着在運行時,這就是爲什麼它不能將代碼列表「粘貼」爲sexp。我認爲更好的辦法是保持mapv出來的語法引用:

(defmacro each-time-mine [& exprs] 
    (mapv #(time-pure %) exprs)) 


(each-time-mine 
    (Thread/sleep 500) 
    (Thread/sleep 1000)) 

[501.580465 1001.196752] 

原始

雖然eval後,它似乎解決在這種情況下,問題通常是皺眉道:

(defmacro time-pure 
    [expr] 
    `(let [start# (current-nano-timestamp) 
     ret# (eval ~expr)] 
    (/ (double (- (current-nano-timestamp) start#)) 1000000.0))) 

(defmacro time-each [& exprs] 
    `(mapv #(time-pure %) '~exprs)) 

(time-each 
    (Thread/sleep 500) 
    (Thread/sleep 1000)) 

[501.249249 1001.242522] 

會發生什麼情況是mapv將sexps視爲列表,而time-pure想要執行它們,它只是指定ret#列表的值。所以這個列表需要是eval ed。

雖然可能有更好的方法來實現這一點。

+0

爲什麼在這種情況下需要'eval',但當直接使用'純時間'時工作正常嗎?我想我明天早上需要再考慮一下。宏正在腐爛我的大腦:/ – Carcigenicate

+1

與-1,一個禮貌的評論解釋原因很長的路要走。 – Shlomi

+0

Idk爲什麼你被低估了。也許使用'eval'觸發某人?我會積極抵消,因爲我真的想了解這裏發生了什麼,這是一個不同的看法。 – Carcigenicate

0

「我仍然試圖不用我的頭圍繞宏。「

這需要一段時間:)有一兩件事讓我很宏觀擴張:

(macroexpand-1 '(time-each (+ 2 2)(+ 3 3))) 

產生:

(clojure.core/mapv 
    (fn* [p1__24013__24014__auto__] 
     (rieclj.core/time-pure p1__24013__24014__auto__)) 
    (quote ((+ 2 2) (+ 3 3)))) 

令人震驚的事情有那個時候,純正在通過一個引用的列表,所以它只是在運行時間,而不是宏展開時間的符號。

然後另一件幫助我的是b Lispy宏的美化在於它們是正常運行的正常函數,它在不同的時間運行(而讀者正在處理源代碼)。但是因爲它們是正常的函數,所以我們可以使用print語句來探測它們,就像我們探測普通代碼一樣

我修改了純時間來打印出它的macroexpansion-time參數expr,並在生成代碼中打印出評估的輸入,我們現在懷疑它是一個符號列表,或'(+ 2 2)在第一種情況下。

(defmacro time-pure 
    "Evaluates expr and returns the time it took. 
    Modified the native time macro to return the time taken." 
    [expr] 
(prn :time-pure-sees expr) 
`(let [start# (now) 
     ret# ~expr] 
    (prn :ret-is ret# (type (first ret#))) 
    (/ (double (- (now) start#)) 1000000.0))) 

我打印ret的第一個類型以驅動home,它是+符號而不是函數。評價:

(time-each (+ 2 2)(+ 3 3)) 

產量:

:time-pure-sees p1__24013__24014__auto__ 
:ret-is (+ 2 2) clojure.lang.Symbol 
:ret-is (+ 3 3) clojure.lang.Symbol 

看到(+ 2 2)可能讓你覺得一切都很好,但關鍵是,rightside是「(+ 2 2),RET#得到綁定到那個符號表達式而不是預期的計算。

再次,打印第一種類型有希望清楚地表明,time-pure在運行時正在處理符號列表。

對不起,如果所有這些令人困惑,但道德很簡單:當宏與你的頭混亂時,使用宏展開-1和嵌入式打印語句。及時你將內化兩個不同的時代,宏觀擴張和運行。