2012-10-08 91 views
2

我試圖確定一個宏在給定的參數是否是一個函數,像宏參數是函數嗎?

(defmacro call-special? [a b] 
    (if (ifn? a) 
    `(~a ~b) 
    `(-> ~b ~a))) 

這樣的東西,下面的兩個調用將都產生的「Hello World」

(call-special #(println % " World") "Hello") 
(call-special (println " World") "Hello") 

然而,我無法弄清楚如何將「a」轉換爲ifn?可以理解。任何幫助表示讚賞。

回答

3

你可能想問自己爲什麼你想用這種方式定義call-special?。它看起來並不特別有用,甚至不會爲你節省任何輸入 - 你是否真的需要一個宏來做到這一點?

話雖如此,如果你有決心,使其工作,然後一個辦法是看看裏面a,看看它是否是一個函數定義:

(defmacro call-special? [a b] 
    (if (#{'fn 'fn*} (first a)) 
    `(~a ~b) 
    `(-> ~b ~a))) 

這工作,因爲#()函數文本被擴展到形式如下:

(macroexpand `#(println % " World")) 
=> (fn* [p1__2609__2610__auto__] 
    (clojure.core/println p1__2609__2610__auto__ " World")) 

我仍然認爲這個解決方案是相當醜陋,容易發生故障,一旦你開始做更復雜的東西(例如,使用嵌套的宏來生成你的函數)

1

a在你的宏只是一個clojure列表數據結構(它不是一個函數呢)。所以基本上你需要檢查的數據結構a將導致無論是功能還是不是當其被評估,它可以像下面顯示來完成:

(defmacro call-special? [a b] 
    (if (or (= (first a) 'fn) (= (first a) 'fn*)) 
    `(~a ~b) 
    `(-> ~b ~a))) 

通過檢查a的第一個元素是符號fn*或用於創建功能的fn

此宏只適用於2種情況:要麼將其傳遞給匿名函數或表達式。

2

首先,幾個百分點:

  1. 宏是簡單地接收作爲輸入[文字,符號或文字的集合和符號]和輸出[文字,符號或文字的集合和功能符號。參數永遠不是函數,所以你永遠不能直接檢查符號映射到的函數。
  2. (call-special #(println % " World") "Hello")包含閱讀器宏代碼。由於閱讀器宏是在常規宏之前執行的,因此在進行任何更多分析之前應先展開此宏。通過應用(read-string "(call-special #(println % \" World\") \"Hello\")")來完成此操作,該操作將變爲(call-special (fn* [p1__417#] (println p1__417# "world")) "Hello")

雖然一般來說,當你想使用某些東西時,你應該使用替代方法並不明顯,下面是我如何處理它。

您需要致電macroexpand-alla。如果代碼最終變成(fn*)表單,那麼它肯定是一個函數。然後你可以安全地發射(〜a〜b)。如果它最終擴展爲符號,則也可以發出(~a ~b)。如果符號不是函數,那麼在運行時會出現錯誤。最後,如果宏擴展到列表(函數調用或特殊窗體調用),例如(println ...),則可以發出使用線程宏->的代碼。

您還可以覆蓋這種情況,例如表單宏展開到數據結構中但尚未指定所需行爲的情況。