2013-06-26 15 views
7

przestrzegać następujących sesji repl:^char wskazówka nie jest dozwolone dla parametru 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) 

więc użyliśmy typu podpowiedź ^char pozbyć refleksji - świetnie. Teraz spróbuj to samo w parametrze funkcji:

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 

Rozumiem, że Clojure only supports long or double type hints for numeric primitives i że Java char jest numeryczny typ danych - nie trzeba wyjaśnić. Ale powyższe wydaje się niespójne - podpowiedź typu ^char jest dozwolona w pierwszej funkcji wewnątrz for, ale nie w sygnaturze funkcji blah-c, gdzie musiałem określić Character. Jaki jest tego powód (tj. Z perspektywy implementacji kompilatora)?

+3

Kombinatoryczna eksplozja - do dynamicznej kompilacji musi istnieć interfejs dla każdej dozwolonej kombinacji elementów pierwotnych i obiektów. –

+0

To brzmi jak wspaniała odpowiedź, jeśli chcesz ją rozwinąć, podać referencje/przykłady. – noahlz

+3

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

Odpowiedz

3

W typowym wskazanym wyrażeniu for oznaczono c jako char jako wskazówkę dla kompilatora. Kiedy kompilator emituje (statyczną) metodę dla isDigit, wtedy wie, że chcesz wersję akceptującą char (w przeciwieństwie do prawdopodobnie wersji int). Kod bajtowy jest emitowany do obiektu funkcji implementującego O (jeden argument Object) interfejsu IFn (wszystkie argumenty są domyślnie oznaczone ramką).

W innym przypadku blah-c, kod binarny, że muszą być wysyłane do obiektu funkcji wykonawczych nieistniejącego C (na przykład, dla char) wersja interfejsu IFn. Czy mogą istnieć interfejsy dla każdego prymitywu? Pewnie, ale nie ma. Dla każdej możliwej kombinacji? Niewykonalne ze względu na kombinatoryczną eksplozję.

Można powiedzieć, dlaczego nie po prostu emitować blah-c do interfejsu O? Spowoduje to pokonanie punktu wskazującego typ na argumencie funkcji, który ma na celu uniknięcie boksowania/rozpakowywania, ponieważ prymityw musi być w pudełku, aby wykonać połączenie. Wskazówki dotyczące typów argumentów funkcji nie mają na celu jedynie uniknięcia refleksji. Jeśli chcesz uniknąć tutaj refleksji, to nie tagowałbyś argumentu funkcji, ale zamiast tego przymuszałeś go do bloku char w bloku let przed wykonaniem połączenia isDigit.

Uwaga w clojure.lang.IFn, wyliczonych interfejsy (obecnie) ograniczona do dowolnej ilości przedmiotów (pudłowego) i do czterech kombinacji double i long typów. Wersje double i long są dostarczane jako optymalizacja, aby uniknąć boksowania/rozpakowywania podczas pisania krytycznego kodu wydajności na elementach pierwotnych i powinny być wystarczające dla większości celów.

0

Jest to oparte na komentarzach z @A. Webb i @kotarak, o ile mogę je zrozumieć.

Są to dwa aspekty: po pierwsze, dlaczego ^char jest dostępny w niektórych kontekstach (np. W for)? Jest to konieczne nie tylko dla optymalizacji, ale dla poprawnego interakcji Java, jak pokazuje przykład. Poza tym wydaje mi się, że jest względnie tania w implementacji, ponieważ każda zmienna jest niezależna, więc może być przetwarzana sama.

Nie ma to miejsca w przypadku definicji funkcji, gdzie dla każdej kombinacji obsługiwanego typu należy zdefiniować nowy interfejs: np.

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);} 

Każdy nowy obsługiwany typ dodawałby wiele nowych interfejsów (wykładniczy wzrost). Dlatego obsługiwane są tylko najbardziej pierwotne typy: long i double.

+0

Co z 'dla'? – noahlz

+0

Cóż, mówię coś o kontekście 'for'. Sądzę, że zasadniczą różnicą jest to, że nie definiujesz tam nowego 'fn', więc nie trzeba implementować' IFn'. Ponownie, odpowiedź A. Webb jest pełniejsza i na pewno bardziej poinformowana. – ivant

Powiązane problemy