krótka odpowiedź : Jest to niejasny szczegół implementacji Clojure. Jedynym gwarantowanym przez język jest to, że reszta-param funkcji variadic zostanie przekazana jako instancja clojure.lang.ISeq
lub nil
, jeśli nie ma żadnych dodatkowych argumentów. Powinieneś kodować odpowiednio.
Długa odpowiedź: Ma to związek z tym, czy wywołanie funkcji zostało skompilowane czy po prostu ocenione. Nie wdając się w pełną rozprawę na temat różnicy między ewaluacją a kompilacją, wystarczy wiedzieć, że kod Clojure zostaje przetworzony na AST. W zależności od kontekstu wyrażenia w AST mogą zostać ocenione bezpośrednio (coś podobnego do interpretacji) lub mogą zostać skompilowane do kodu bajtowego Java jako część dynamicznie generowanej klasy. Typowy przypadek, w którym to drugie się dzieje, jest w ciele wyrażenia lambda, który oceni instancję dynamicznie generowanej klasy, która implementuje interfejs IFn
. Bardziej szczegółowe objaśnienie oceny znajduje się w Clojure documentation.
Znaczna większość czasu, różnica między skompilowanym a ocenianym kodem będzie niewidoczna dla twojego programu; będą się zachowywać dokładnie w ten sam sposób. Jest to jedna z tych rzadkich przypadków, w których kompilacja i ocena powodują subtelne różnice w zachowaniu. Należy jednak podkreślić, że oba zachowania są poprawne, ponieważ są zgodne z obietnicami złożonymi przez język.
Wywołania funkcji w kodzie Clojure są przetwarzane na instancje InvokeExpr
w clojure.lang.Compiler
. Jeśli kod jest kompilowany, kompilator emituje kod bajtowy, który wywoła metodę na obiekcie IFn
przy użyciu odpowiedniej arii (Compiler.java, line 3650). Jeśli kod jest właśnie oceniany i nie jest kompilowany, argumenty funkcji są grupowane w PersistentVector
i przekazywane do metody applyTo
na obiekcie IFn
(Compiler.java, line 3553).
Funkcje Clojure z listą argumentów variadic są kompilowane w podklasach klasy clojure.lang.RestFn
. Ta klasa implementuje wszystkie metody z IFn
, zbiera argumenty i wywołuje do odpowiedniej arytmetycznej doInvoke
. W implementacji applyTo
można zauważyć, że w przypadku 0 wymaganych argumentów (jak w przypadku funkcji wtf
), parametr wejściowy seq jest przekazywany do metody doInvoke
i widoczny dla implementacji funkcji. Natomiast wersja 4-arg invoke
łączy argumenty w postaci ArraySeq
i przekazuje je do metody doInvoke
, więc teraz twój kod widzi ArraySeq
.
Aby komplikować sprawę, implementacja funkcji Clojure's eval
(która jest tym co wywołuje REPL) wewnętrznie zawinie formularz listy oceniany wewnątrz funkcji thunk (anoymous, no-arg function), a następnie skompiluje i wykona thunk . Dlatego prawie wszystkie wywołania używają kompilowanych wywołań do metody invoke
, a nie są interpretowane bezpośrednio przez kompilator. Jest specjalny przypadek dla formularzy def
, które jawnie oceniają kod bez kompilacji, co odpowiada różnym zachowaniom, które tam widzisz.
Implementacja clojure.core/apply
również nazywa się metodą applyTo
i przez tę logikę cokolwiek typ listy przekazywany do apply
powinien być widoczny jako treść funkcji. Rzeczywiście:
user=> (apply wtf [1 2 3 4])
clojure.lang.PersistentVector$ChunkedSeq
:ok
user=> (apply wtf (list 1 2 3 4))
clojure.lang.PersistentList
:ok
To staje się lepsze: '((fn [] (def x (wtf 1 2 3 4))))' – Alex