2013-06-26 26 views
7

遵守以下REPL會話:^char類型提示不允許的Clojure DEFN參數

user=> (set! *warn-on-reflection* true) 
true 

user=> (defn blah [s] (for [c s] (if (Character/isDigit c) true false))) 
Reflection warning, NO_SOURCE_PATH:1:31 - call to isDigit can't be resolved. 
Reflection warning, NO_SOURCE_PATH:1:31 - call to isDigit can't be resolved. 
#'user/blah 

user=> (blah "abc123abc") 
(false false false true true true false false false) 

user=> (defn blah [s] (for [^char c s] (if (Character/isDigit c) true false))) 
#'user/blah 

user=> (blah "abc123abc") 
(false false false true true true false false false) 

所以我們使用的^char一種暗示擺脫反思 - 偉大的。現在嘗試同樣的事情在一個函數參數:

user=> (defn blah-c [c] (if (Character/isDigit c) true false)) 
Reflection warning, NO_SOURCE_PATH:1:22 - call to isDigit can't be resolved. 
#'user/blah-c 

user=> (defn blah-c [^char c] (if (Character/isDigit c) true false)) 
CompilerException java.lang.IllegalArgumentException: Only long and double primitives are supported, compiling:(NO_SOURCE_PATH:1:1) 

user=> (defn blah-c [^Character c] (if (Character/isDigit c) true false)) 
#'user/blah-c 
user=> (blah-c \1) 
true 
user=> (blah-c \a) 
false 

據我所知,Clojure的only supports long or double type hints for numeric primitives,和一個Java char是數字數據類型 - 不需要解釋。但上面看起來不一致 - 在for內的第一個函數中允許使用^char類型的提示,但不在blah-c的函數簽名中,在那裏我必須指定Character。這是什麼原因(即從編譯器實現的角度來看)?

+3

組合爆炸 - 對於動態編譯,必須存在每個允許的基元和對象組合的接口。 –

+0

這聽起來像是一個了不起的答案,如果你想擴大它,提供參考/例子。 – noahlz

+3

@noahlz cf. [clojure.lang.IFn](https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/IFn.java) – kotarak

回答

3

在類型暗示的for表達式中,您將c標記爲char作爲編譯器的提示。當編譯器發出(靜態)方法爲isDigit時,它知道你想要的版本接受char(而不是可能的int版本)。字節碼被髮送到一個函數對象中,該對象實現IFn接口的O(單個Object參數)版本(所有參數均默認爲裝箱)。

在其他情況下,blah-c,字節代碼將需要被髮射到實現IFn接口的一個不存在的C(例如,用於char)版本的功能對象。可以有每個原始的接口嗎?當然,但沒有。對於每種可能的組合?由於組合爆炸,不可行。

你可以這麼說,爲什麼不直接發送blah-cO接口呢?這將打破函數參數的類型提示點,這是爲了避免裝箱/取消裝箱,因爲字符原語必須被裝箱才能進行調用。函數參數的類型提示點不僅僅是爲了避免反射。如果你想在這裏避免反思,那麼你不會標記函數參數,而是在進行isDigit調用之前將其強制轉換爲let塊中的char

請注意,在clojure.lang.IFn中,枚舉的接口(當前)僅限於任何數量的對象(盒裝類型)以及多達4種組合的doublelong類型。提供doublelong版本作爲優化,以在基元上編寫性能關鍵代碼時避免裝箱/取消裝箱,並且對於大多數用途應該足夠了。

0

這是基於來自@A的評論。 Webb和@kotarak,據我瞭解他們。

有兩個方面:第一,爲什麼^char在某些情況下可用(例如在for)?這不僅是優化所必需的,而且正如您的示例所示,正確的Java互操作。另外,它看起來(對我來說)實施相對便宜,因爲每個變量都是獨立的,所以它可以自行處理。

功能定義並非如此,對於支持類型的每種組合,您都必須定義一個新接口:例如,

static public interface L{long invokePrim();} 
static public interface D{double invokePrim();} 
static public interface OL{long invokePrim(Object arg0);} 
// ... 
static public interface OLD{double invokePrim(Object arg0, long arg1);} 
// all the way to 
static public interface DDDDD{double invokePrim(double arg0, double arg1, double arg2, double arg3);} 

每個新的受支持類型都會添加大量新接口(指數級增長)。這就是爲什麼只支持最廣泛的原始類型:longdouble

+0

'for'呢? – noahlz

+0

好吧,我確實說了一些關於'for'的情況。我想,關鍵的區別是,你沒有在那裏定義一個新的'fn',所以它不需要實現'IFn'。 A.韋伯的答案再次充實,肯定會有更多的信息。 – ivant