Deviations
Conformance is partial by design. Where behavior departs from the Clojure oracle (Clojure CLI 1.12), it does so here, on every backend alike:
falseis a distinct object fromnil. Both are falsey, soif/when/cond/and/or/nottreat them alike, while=andnil?tell them apart;false?/true?/boolean?answer accordingly.println/printjoin their parts with a single space and spell the three valuestrue/false/nil;strconcatenates bare and spells themtrue/false/""(a collection inside it spells readably, strings quoted, like the oracle'stoString);pr/prn/pr-strare the readable arms (strings print quoted,pr-strjoining its parts with a space likepr);print-str/prn-str/println-strare the same arms answered as a string. The print family answersnil, like the oracle.- Collections print in Clojure notation (
[1 :a s],{:a 1},#{1},(true false nil :k)); a quoted symbol demangles from behindc%.nilstaysnil(never()), and map/set walk order stays unspecified (same askeys/vals), so only single-entry maps and single-member sets print deterministically. A value that closes a cycle prints with a datum label (#0=(1 . #0#)), like Scheme'swrite; sharing without a cycle prints twice. An atom prints unreadably (#<Atom value>), a function as#<procedure>, an exception as itstoString(clojure.lang.ExceptionInfo: m {}; the oracle prints#error {...}), an unbound var's root as#<Unbound: #'user/x>(the oracle's#objectcarries a hash). A stream prints as the oracle's#objectof the host class its kind is without the identity hash (#object[java.io.StringWriter "ab"],#object[java.io.OutputStreamWriter "java.io.OutputStreamWriter"]), andstranswers itstoStringthe same way: a string input stream is aclojure.lang.LineNumberingPushbackReader(with-in-str's), also where the oracle's is ajava.io.PushbackReaderover aStringReader. *print-meta*writes a value's metadata ahead of it like the oracle, but no quoted list carries the oracle reader's:line/:columnmetadata;*print-dup*is a plain value the printer does not read.assertreads*assert*where it expands, so a top-levelset!of it to a literal switches off the asserts after it; aset!inside a function, or to a computed value, does not (the oracle's takes effect once it runs).~S/~Aon Clojure values stay Common Lisp notation (formatis a CL surface);print-method/pprintstay absent.- A map, set or memo key finds an
=key like the oracle's, vectors, lists, maps and sets included, but a stored collection key is the first=key of its kind (vector, list, lazy seq) the program stored, so its metadata and the spelling of a nested member follow that earlier object; those keys stay alive for the whole run, one per distinct value and kind. A repeated set-literal element is refused by spelling.=compares vectors, lists and lazy seqs element-wise like the oracle, and sincenilis the empty list,(= [] nil)and(= (java.util.ArrayList.) nil)aretruewhere the oracle answersfalse.=asks a Java object on the left itsequalslike the oracle, but hands it only a number, string, character,true,nilor Java object:false, a keyword, a symbol or a collection is=to no Java object but a JavaList,MaporSetof its kind. A map or set finds a Java collection key by its ownequals, like the oracle's hash maps and sets, never by=, which the oracle's small array maps use:(get {[1 2] :v} (java.util.ArrayList. [1 2]))isnilhere,:vthere. seqand the verbs over it,count,empty?,get,contains?,keysandvalsread a JavaIterable,MaporCharSequence, andfind,select-keys,reduce-kv,update-vals,update-keys,conj,mergeandmerge-witha JavaMap, like the oracle, but the seq is read whole when it is taken (the oracle's walks the iterator lazily) and aMap's entries are[k v]vectors (the oracle's are the Java entries, printed#object[...]).clojure.set/unionwhose largest input is a map signals, where the oracle conjoins the other inputs'[k v]members into it; aclojure.setanswer carries no metadata.- A map entry is a plain two-member vector, so
map-entry?istrueof every[k v](the oracle:falsefor one the program built) andkey/valread any such vector. For the same reason(conj {} #{[1 2]}),(conj {} (seq [[1 2]]))and(merge {} (seq [[1 2]]))answer{1 2}(the oracle:ClassCastException, the members must be real entries). Keywords are not interned, sofind-keywordanswers the keyword for a spelling no keyword ever used (the oracle:nil). - The type predicates follow the representation.
nilis the empty list, soseq?,list?,coll?,sequential?andcounted?answerfalsefor(); a seq a verb answers over a strict input is a list, solist?,counted?andrealized?answertruefor it (the oracle's lazy or chunked seq:false); no seq is chunked (chunked-seq?is alwaysfalse), and aniterate/cycleseq isrealized?only once forced. A decimal orNliteral is a plain rational, sodecimal?is alwaysfalseandratio?,integer?andint?answer for the rational ((ratio? 1.5M),(int? 2N)aretrue).identical?compares numbers, characters and symbols by value ((identical? 1000 1000)istrue) and two keywords of one spelling as one object. - A program's own top-level definition of a core name (
(defn peek ...)) shadows the core verb in the whole file, calls above the definition included (the oracle's calls above it still reach the core verb); a local binding shadows it in its scope, like the oracle. - The seq family's empty
rest/nextisnil, where the oracle prints();nthpast the end answers the default instead of throwing; map/set seq order is the table's walk order; strings seq to characters printing in Common Lisp notation. Lazy seqs realize one element at a time (no chunking);strof a lazy seq spells its members, where the oracle answersclojure.lang.LazySeq@<hash>;maptakes any number of collections. - A
forover strict collections answers a strict list, realized when theforruns (the oracle's waits to be consumed); with no elements it isnil, where the oracle prints()(the same empty-as-nilposition asrest/next/take). From its first lazy collection on, it is lazy like the oracle's. condkeeps the lenient reading: an odd trailing arm is the default, where Clojure signals. A threading step over a collection literal signals (collections are not functions here).- A function that calls itself by name in tail position (a
defn, a namedfn, aletfnentry) runs in constant stack on every backend, asrecurdoes, and so do functions that call each other in tail position (letfnentries,defns). The oracle keeps a frame per such call, so a deep one overflows there and runs to completion here: a test asserting that overflow ((is (thrown? StackOverflowError (tail-fibo 1000000N)))) fails. clojure.testruns the tests in definition order (the oracle's order is its namespace map's); an error report prints the exception'stoString(a runtime error its report) with no stack trace, at theisform's line where the oracle names the frame that threw; a failedthrown-with-msg?shows the condition's message where the oracle prints#error {...}; a host stack overflow (catch StackOverflowError,(is (thrown? StackOverflowError ...))) is no condition on the interpreter, where it ends the program with the one-line report, and a trap on WASM; only the JVM backend catches it, like the oracle.use-fixturesis refused by name.- A
catch(andthrown?) takes a runtime error by the class the oracle throws where the runtime signals its Common Lisp condition, and a refusal of the Clojure runtime by the class the oracle throws for the same call ((first 5)anIllegalArgumentException, a failedassertanAssertionError, which anExceptioncatch does not take). An error naming no class -- a refusal of a construct the oracle accepts (a regex lookahead,(partition 0 coll)) -- is taken by the first catch of any class butclojure.lang.ExceptionInfo. A misuse a lower verb refuses first carries that verb's class:(shuffle 5)is theIllegalArgumentExceptionofseq, where the oracle casts tojava.util.Collection. An index past its bound is anIndexOutOfBoundsExceptionthat a catch of any of its subclasses takes too (the oracle'sagetthrowsArrayIndexOutOfBoundsException,.charAtaStringIndexOutOfBoundsException). A catch must name a class that resolves on this host (java.*,clojure.lang's throwables); one the oracle finds on its class path only is refused. - An exception is a condition carrying its class, a message, data and a cause. A runtime
error is the Common Lisp condition the runtime signals, whose message is the Common Lisp
report (
(.getMessage e)of a failed(inc nil)is+: The value NIL is not of type NUMBER, the oracle's aNullPointerExceptiontext) and whosestris that report without the oracle's class prefix. A throwable construction is an exception only for a class that carries nothing but a message and a cause; one with members of its own (java.net.URISyntaxException) stays a host object. On the interpreter and the JVM, an exception a Java member throws, and a host object thrown, is the host's own, like the oracle's: a catch takes it by its class and binds that very object.classof an exception the program built answers its class name as a keyword (:java.lang.Exception, where the oracle answers the host class),:java.lang.RuntimeExceptionfor an error naming no class;.printStackTracewrites thetoStringline to*err*(the oracle writes it and a line per frame to the process's stderr, whatever*err*is bound to) and.getStackTraceanswers an empty vector;.getClassanswers whatclassdoes. On the interpreter and the JVM, any other method is called on, and a Java member is passed, a host exception of the exception's class built once from its message and cause (anex-info's is aRuntimeException):(.getCause (UncheckedIOException. "u" e))answers that host exception, noteitself (the oracle's isidentical?toe). A runtime error and a refusal of the runtime have no such host exception: a Java member taking aThrowablefinds no match for one.throwof a value that is no exception is aClassCastExceptionwhose message is the value's rendering, where the oracle's message names the two classes. - Multimethod dispatch values compare like map keys (by
=, vectors included); dispatch through a hierarchy prefers the strictly most specific method, thenprefer-methodchoices. Adefmethodover a host class stores under the keywordclassanswers for it, merging every numeric spelling into:number(where the oracle tellsLongfromDouble); a true nil maps onto the(:C%NIL)marker, so no table ever keys on nil, while a dispatch value that literally is:nilkeeps its keyword row, like the oracle (aclasscall inside the dispatch function answers nil itself for a nil argument, so the null test maps it onto the marker too -- bare, wrapped in another function, through a nameddefn/def'd function re-lowered from its recorded definition, or a call to one nested inside an inline dispatch datum (inlined at the call site the same way), like the oracle); anObjectmethod catches past the search but ahead of the default. A throwable or stream class stores under its name as a keyword (the oneclassanswers), and the search follows its Java supers like the oracle's inheritance, interfaces (java.io.Serializable,java.io.Closeable) andObjectincluded;isa?,derive,underive,parents,ancestorsanddescendantsread a class spelling as the same keyword, so(isa? (class "a") String)istrueandparents/ancestorsof a class add its Java supers like the oracle. A core kind (:string,:number, ...), a record or a deftype stands for host classes that are no one value here: itisa?Objectand itsancestorsaddObject, but its host class's other supers are not modeled. A program that spells no class in those positions and uses no host interop reads only the hierarchy, so(ancestors (class e))there answers whatderiverecorded. Since the keyword is the class here, a keyword spelled:java.lang.Exceptionis that class too. A host class object (classof a host object, interpreter and JVM) is the same class as its name's keyword and, by its simple name, a core kind's, so(isa? (class (java.util.ArrayList.)) java.util.List)istrue-- and so is itsisa?ofclojure.lang.IPersistentList, which also spells:list, where the oracle answersfalse. Any other class (java.io.File) is its class object, in a dispatch value too, like the oracle. Protocol dispatch reads no hierarchy (exact tag match plus theObjectdefault) and mergesLong/Doubleinto:number, where the oracle tells them apart. (methods mt)andget-method,remove-method,prefer-methodtake the multimethod's name (adefmultivar, through an alias or a referred one), not an expression: a local bound to a multimethod is refused at lowering. The mapmethodsanswers keys a host class row by the keywordclassanswers for it, where the oracle keys it by theClass.- A record prints as its literal (
#user.R{:a 7}, like the oracle), butstrof one spells that literal too, where the oracle answersuser.R@<hash>. A deftype prints as its wrapper list ((:C%TYPE ...)), a reify as(:C%REIFY ...); only the entry maps print deterministically. - A deftype's
^:volatile-mutablefield is the same plain slot as an^:unsynchronized-mutableone (no cross-thread ordering). split/replaceanswer seqs, never vectors, and plain strings stay literal (only pattern values match by pattern);index-ofanswers-1when missing, like the oracle (whereclojure.string/index-ofanswersnil).definside a body sets the global when the body runs;defninside a body works only in statement position (a multi-arity one only at the top level).- Verbs assume the right collection kind; misuse may signal the Common Lisp type error instead of the oracle's.
- Macros expand while lowering, so every backend runs expanded code; the interpreter's
evalof a macro call expands the same way. A macro body sees the core builtins and theclojure.lisplibrary, not the program's own definitions; a call above its definition is refused, and a macro has no function value. Adefmacroof a special form (if,do,let*,new, ...), of a head the reader spells (deref,syntax-quote,ns,in-ns) is refused by name, where the oracle accepts it (and ignores it at call sites, for a special form). #(...)reads as the oracle's(fn* [p1__N# ...] (body))in source, under a quote and inread-string/read, but N restarts at each top-level form (each datum read), where the oracle's counter runs across the process: the parameter names differ, and two reads of one text answer=forms here. A regex literal passed to a macro reaches the expansion as a fresh pattern compiled from the same source, where the oracle's expansion holds the onePatternobject.- Syntax-quote qualifies every symbol but a special form, like the oracle: a core name
spells
clojure.core/name(one a(:refer-clojure ...)filter hides spells its own namespace instead), any other unresolved spelling the defining namespace, an alias head its namespace, a class head its fully qualified name. Eachx#binds one gensym per expansion -- the oracle resolves one per compilation, so two expansions share its suffixes where ours differ (fresher, never captured).macroexpand-1/macroexpandanswer the mangled data itself, so=against a quoted form holds and printing spells the oracle's lowercase. Nested syntax-quote evaluates its levels in the one expansion. - A var's metadata comes from the definitions lowered above the
#'site: a body lowered above a redefinition keeps the older docstring where the oracle's var shows the newest (the same split as its calls).:nsis the namespace's symbol (the oracle's is a Namespace object),:fileof the entry file is the path as given (the oracle absolutizes it), and a var defined by anything butdef/defn/defn-/defmacro(defmulti,deftest, a record's factory, ...) carries only:nameand:ns. Deref of a macro's var signals (the oracle answers its expander function). Aclojure.corevar's metadata is only:name,:nsand a macro's:macro(the oracle's also carries:arglists,:doc,:addedand the position), and a core var with no value here (#'all-ns) is refused. - A namespace prints as the oracle's
#object[clojure.lang.Namespace "user"]without the identity hash, andclassof one answers:clojure.lang.Namespace.the-nsandfind-nsknow the namespaces the program created above the call, the libraries it required and the fourclj -Mloads first (clojure.core,clojure.edn,clojure.java.io,clojure.string);in-nsanswersnil(the oracle's answers the namespace).set!andbindingof*ns*change what*ns*reads, not the namespace the forms below resolve in, whichnsandin-nswith a literal name decide. A compiled program's*file*is the entry file's path when it was compiled. classanswers a keyword naming the kind (:string,:number,:keyword, ...); the oracle answers host classes, which no wasm backend has. A record or deftype answers its tag keyword instead; a host object (interpreter and JVM) its host class.- An instance call on a collection, keyword, symbol, ratio or atom answers through the core
functions and shares their deviations (
.getClassanswers whatclassdoes). A method left unmapped is refused asMethod m taking N args is not supported for class C, where the oracle may answer (.hashCode); the class named for a map is an array map up to eight entries and a hash map past them, by size alone.nilis the empty list here, so a collection method answers on it ((.count nil)is0) where the oracle throws aNullPointerException; any other method onnilis one. On a record, deftype or reify, a name that is no protocol method and no field of a record or deftype the program defined is refused that way too, where the oracle saysNo matching field found; a site lowered before a later REPL input defines a record or deftype does not see its methods or fields. instance?answers a class by the oracle classes of each kind of value: a list or strict seq is aclojure.lang.PersistentList, soIPersistentListandCountedaretrueof(map inc [1])andLazySeqis not (the oracle's is aLazySeq); every two-member vector is ajava.util.Map$Entry(map-entry?); an integer is aLong,(int 1)too, never anInteger. Aclojure.langclass no kind is an instance of (clojure.lang.PersistentQueue) is an unknown name, where the oracle answersfalse. A protocol's interface (user.P) knows the records and deftypes defined when the site is lowered, so one a later REPL input defines is not an instance there; the protocol's own name (P, a var) is an unknown name, where the oracle throws aClassCastException.- The
unchecked-arithmetic verbs wrap integers at 64 bits (-intverbs at 32) and the casts (int,long,short,byte,char,double,float) match the oracle, with one deviation: an integer past 64 bits is a plain integer here, so the oracle's unwrapped bigint operand ((unchecked-add 9223372036854775807N 1)) wraps too.inc,decand the checked verbs never overflow (integers are bignums). bigintandbigintegeranswer a plain integer, andbigdeca plain rational ((bigdec "1.5")prints3/2, the oracle1.5M), like theNandMliterals;bigdecof a ratio with an infinite decimal expansion signals, like the oracle.formatrenders%s/%d/%x/%X/%o/%c/%b/%f/%%/%n(with widths, float precision);%e/%g, flags and non-literal patterns are named refusals.%sspellsnil"null", like the oracle.line-seqtakes a path or an open reader (such as aclojure.java.io/reader, whichwith-opencloses) and answers strictly either way (the oracle takes a reader and answers lazily);spit/slurp/line-seq/readerrun on the interpreter and the JVM, and on wasm with a--dirpreopen covering the path.- A sorted map or set orders, prints and finds keys like the oracle's, but every verb
copies it (an association costs the collection's size, like a hash map's);
nthsteps through one where the oracle refuses;classanswers:map/:set; asubseqorrsubseqwalking from the first member that finds nothing answersnil(the oracle()); a test passed tosubseqas a value is recognized by how it answers(1 0),(0 0)and(-1 0), where the oracle compares it with the core functions.compareorders strings by code point (the oracle by UTF-16 unit, which differs past U+FFFF). floatanswers a double, so(float 1/3)is0.3333333333333333(the oracle's Float prints0.33333334); a value past the float range still signals.intandlongtruncate and do not refuse a value out of range (the oracle:integer overflow,Value out of range for long: ...).vector-ofanswers an ordinary vector: a laterconjorassocstores its value as given, where the oracle's keeps casting, and:floatholds doubles.emptyof a list, a lazy seq or a seq answersnil(the oracle(), the empty-as-nilposition ofrest), so it carries no metadata, and of a map entry[](the oraclenil).partitiontakes no pad.partition-allwith a non-positive size or step signals, where the oracle answers an endless seq of().pmapismap, run in order on the calling thread.take-nthwith a zero step signals, and its seq arity steps by the magnitude of a negative one, where the oracle's repeats the first member forever.- Transducers are the oracle's functions over reducing functions, but an
eductionis thesequenceof its input through them, computed once (strictly over a strict input, lazily over a lazy one), where the oracle re-runs the transformation every time it is reduced;printlnprints it as that seq where the oracle prints the object. Areducedvalue prints as its wrapper list. - Transactions are single-threaded extents:
dosyncnever retries,commuteruns its function once (the oracle may run it twice), validators run on the write and a failed one leaves the old value;alterand friends outsidedosyncsignal. - Agents are synchronous atoms:
send/send-offapply at once and answer the agent (which prints unreadably,#<Atom ...>, not the oracle's object),awaitandshutdown-agentsanswernil, and*agent*is bound only while a send runs (niloutside one, where the oracle leaves it unbound). bindingrebinds only^:dynamicvars (anything else is refused, like the oracle's non-dynamic error); a^:dynamicdefnis rebindable too (its calls go through the var while the definition stays direct); reader metadata on names and locals otherwise parses and drops, never affecting dispatch.- In the REPL, a local named like a
^:dynamicvar that a later input defines binds that var for its extent once it is defined, so a function called there reads the local's value; the oracle binds a local lexically, and the function reads the var. In a file such a local is lexical. with-metaanswers a copy carrying the metadata; a value derived from it (assoc,conj, ...) starts without metadata, where the oracle keeps it, and a symbol carries none (with-metaanswers the symbol). A:tagfrom reader metadata on a collection literal stays the symbol as written (String), where the oracle resolves the class (java.lang.String).with-opencloses through theclosemethod, so only closeables the backend reaches work (Java closeables need the JVM);timeanswers its value but its millisecond count never pins, and it counts whole milliseconds (42.0) where the oracle's carries nanosecond digits.read-string/readanswer what a quote answers:@xreads(deref x)and a syntax-quote stays unexpanded, where the oracle reads(clojure.core/deref x)and expands it. A record literal reads for any class the program defines, also one a laterrequireloads (the oracle needs the class loaded first); a deftype literal is refused.readtakes a stream -- a plainclojure.java.io/readertoo, where the oracle requires aPushbackReader-- and refuses a host reader.*out*/*in*/*err*are*standard-output*/*standard-input*/*error-output*(rebinding rebinds the standard streams); read at the root,*out*and*in*are stream values over the process standard streams;defoncekeeps the root on reload wheredefresets it.- A host-object boolean answers
falseonly when the receiver's class is known at lowering (a construction literal, alet/if-let/when-letlocal bound to one, or a..step's declared return) and every overload at that arity answers a primitive boolean, or when the receiver is a string, number or character and every overload at that arity of its class answers one ((.matches "abc" "x")); any other host boolean keeps the sharedjava:unmarshal and printsnilforfalse. - An integer receiver is called as an
Integerwhen it fits one, else as aLong(the oracle's is always aLong):(.getClass 1)answersjava.lang.Integer. - A
_param tag leaves that parameter to the cost rule of thejava:surface, so(^[_] Math/abs -2)answers2where the oracle refuses tags that leave more than one overload. - Only
.cljfiles below the source roots are read (no.cljc, no classpath). - Records and deftypes of one simple name in two namespaces share a dispatch tag, which
class, protocol dispatch and=read. - A name referred from two namespaces keeps the later refer (the oracle refuses it), and a definition replaces a refer of its name without the oracle's warning.