#map{...}, #set{...}, and [...]Turmeric's data literals give map, vec, and set construction a compact
literal syntax whose slot values are ordinary expressions, evaluated at
runtime in the surrounding scope. They are sugar over the hamt-of,
vec-of, and set-of stdlib macros.
(def payload #map{:name name :age (+ age 1) :active 1})
(def points [(make-point 0 0) (make-point 1 1) origin])
(def small #set{1 2 3})
| Syntax | Lowers to | Notes |
|---|---|---|
[e1 e2 e3 ...] (expression position) |
(vec-of e1 e2 e3 ...) |
element type inferred from the first element |
#map{k1 v1 k2 v2 ...} |
(hamt-of k1' v1 k2' v2 ...) |
keys normalized (see below); last duplicate key wins |
#set{e1 e2 e3 ...} |
(set-of e1 e2 e3 ...) |
duplicate elements collapse |
#refine{ var : T \| pred } |
contract-type annotation | see Contract Types Guide |
[1 2 3] ; => (vec-of 1 2 3)
#map{:a 1 :b x} ; => map {:a -> 1, :b -> x}
#set{:literal x (compute)} ; (compute) evaluated once
[...] and the value slots of #map{...} accept any scalar element type --
:int, :cstr, :bool, :nil, or :float -- not just :int. All
elements of a vector must unify to one type, and all map values to one type
(a mixed literal such as [1 "x"] is a TUR-E0001 on the offending
element). Elements are stored through an :int carrier slot, so reads
recover the type with an ascription:
[1.5 2.5 3.5] ; Vec[float]
(:: (vec-get fs 0) :float) ; => 1.5
#map{1 "one" 2 "two"} ; Map[int cstr]
(:: (map-get m 1) :cstr) ; => "one"
Map keys may be any Hash/MapKey scalar -- int, keyword, string,
bool, float32, float, or a user type with Hash/Eq/MapKey
instances. The literal has type Map[K V]: the typed map-get/map-assoc/
map-has?/map-dissoc surface reads and writes it, and a key/value-type
mismatch is a TUR-E0001. Int and keyword keys are int-valued (a keyword
normalizes to a content hash); string keys are compared by content (two
distinct pointers with equal text are one key). See
#map{...} keys below.
Keyword keys: a keyword key is a first-class
:Symvalue rather than a content-hashed string -- the map is keyed bySympointer identity viaHash[Sym]/MapKey[Sym]. See the symbols guide. Aggregate (multi-word struct/ADT) key types must supply their ownMapKeyinstance; values may be any type (scalars, floats, and heap handles likeVec[A]all ride the int64 carrier).
Slots are arbitrary expressions -- variable references, calls, nested literals -- and the normal typechecker handles them.
[...] is only a vec in expression positionThe reader parses every [...] as the same vector form. Binding forms
(defn/fn/defmethod parameter lists, and let/loop/for/... binding
vectors) consume that form as a binding spec before it is ever treated as a
value. Only a [...] that reaches expression position lowers to (vec-of
...). This is the same rule Clojure uses: [x y] is always a vector; the
surrounding form decides whether it is read as a binding spec or a value.
(defn add [x : int y : int] : int (+ x y)) ; [x :int y :int] is a param list
(let [a 1 b 2] (+ a b)) ; [a 1 b 2] is a binding vector
(def v [1 2 3]) ; [1 2 3] is a vec literal
#map{...} keysKeys must be a keyword, string literal, or int literal -- anything
else (a variable, a call) is rejected at read time with TUR-E0282. Computed
keys are intentionally disallowed so a reader can tell keys from values at a
glance.
How a key lowers depends on its form:
(hamt/hash-str "name") -- the keyword's content
hash is the int key, so two equal keywords collide deliberately. Look a
keyword key back up by hashing it the same way:turmeric
(let [m #map{:name 1 :age 2}
k (hamt/hash-str "name")]
(map-get m k)) ; => 1
let [m #map{:name 1 :age 2}
k (hamt/hash-str "name")]
map-get(m k)
; => 1
map-get / map-has? / map-dissoc accessors
(they dispatch through Hash[cstr] / MapKey[cstr] by content):turmeric
(let [m #map{"name" 1 "age" 2}]
(:: (map-get m "name") :int)) ; => 1
let [m #map{"name" 1 "age" 2}]
::(map-get(m "name") :int)
; => 1
A direct (hamt-of "k" v ...) call with string keys is content-keyed too --
there is one hamt-of builder for every key type (the old smap-of /
smap-* split was retired in TMS3). (Runtime-built string keys must outlive
the map -- the HAMT does not copy keys; see
GMK / TCE4.)
An odd number of slot forms is a TUR-E0280 read error.
#set{...} elements and hashingSet elements are arbitrary value expressions (no key-form restriction). Each
element is used as its own hash -- the identity-hash convention the typed
Set[A] uses everywhere ((set-add s 42 42)). Scalar element types (int,
etc.) dedupe by value:
#set{1 1 2} ; => set with two elements, {1, 2}
#set{x (+ x x) y} ; each element expression evaluated exactly once
Note on the
Hash[A]typeclass. The original design sketched injecting a(hash x)typeclass call per element. TheHash[A]method does not monomorphize in the current compiled codegen, soset-ofuses identity hashing instead (matching all existingSet[A]usage). Keyword values are not runtime values in Turmeric, so#set{:a :b}does not type-check; use scalar elements.
#map{} ; => empty Map
#set{} ; => empty Set
[] ; => empty Vec (expression position)
[]:T / #set{}:TAn empty literal has no element to infer a type from, so a later typed
operation (.eq?, .push!) can't recover its element type. Pin it with a
fused :T element-type suffix immediately after the closer:
[]:int ; empty Vec[int]
#set{}:cstr ; empty Set[cstr]
[]:(Vec int) ; empty Vec[Vec[int]] (parenthesize a compound element type)
The suffix desugars to an ascription on the literal, so []:int is exactly
(:: (vec-of) (Vec int)) and #set{}:T is (:: (set-of) (Set T)) -- the
generated code is identical. It replaces the verbose
(:: (vec-new) (Vec int)) scaffolding that empty containers previously
needed:
;; before
(let [a (:: (vec-new) (Vec int))]
(vec-push! a 1) ...)
;; after
(let [a []:int]
(vec-push! a 1) ...)
Rules:
: must be immediately adjacent to the closing ] / } (no
intervening whitespace). A space-separated [] :int is two forms, not a
suffix -- this is what keeps binding vectors ([x :int y :int], whose ]
is followed by a space) unaffected.:: after the closer is not a suffix (it is the ascription operator).[1 2 3]:int), where it is
merely a redundant re-statement of the inferred type.: is a read error: "expected an element type
after the ':' container-literal suffix".#map{} has two type parameters (key and value); a typed-empty suffix for
maps is not yet supported -- ascribe the full (Map K V) type instead.| Code | Condition |
|---|---|
TUR-E0280 |
Odd number of slot forms in #map{...} (unmatched key) |
TUR-E0281 |
Unexpected EOF inside #map{...} or #set{...} |
TUR-E0282 |
Invalid key form in #map{...} (must be keyword, string, or int literal) |
TUR-E0283 |
Unknown #<tag>{...} dispatch tag (only #map{/#set{ are defined) |
The data literals supersede the JSON reader-macro
plan for the common "literal shape, computed
values" case: #map{...} already accepts arbitrary value expressions, which a
JSON-only reader cannot. A #json(...) reader would remain useful only for
pasting a literal JSON blob verbatim; for everything else, write the map
literal directly.
The reader dispatch sits below the sweet-expression layer, so the literals
work transparently inside #lang sweet-exp files alongside neoteric calls
and curly-infix arithmetic:
#lang sweet-exp
(defn build [] :int
(let [m #map{:a 1 :b 2}]
map-count(m)))