Skip to main content

Reader Shortcuts

Intermediate 4 min read Last updated
On this page

After this page you can read any Phel shortcut syntax and know the plain form it stands for.

The reader turns source text into Phel data before the compiler sees it. On the way, it expands a few short #-prefixed and single-character forms into ordinary forms. Topics with their own page are linked, not repeated.

Quick reference

SyntaxNameStands forExample
[]Vector(vector ...)[1 2 3]
{}Hash map(hash-map ...){:a 1 :b 2}
#{}Set(hash-set ...)#{1 2 3}
'()Listquoted (list ...)'(1 2 3)
'Quote(quote x)'x
`Quasiquotequote with selective evaluation`(1 ~x)
~Unquoteevaluate inside a quasiquote~x
~@Unquote-splicesplice a sequence inside a quasiquote~@xs
name#Auto-gensymfresh unique symbol inside a quasiquote`(let [g# 1] g#)
#?()Reader conditionalform for the current platform#?(:phel 1 :default 0)
#?@()Conditional splicesplice by platform#?@(:phel [1 2])
@Deref(deref x)@my-atom
#'Var-quote(var my-fn)#'my-fn
#(...)Anonymous function(fn [...] ...) with % args#(+ %1 %2)
#"..."Regex literal(re-pattern "...")#"\d+"
#<tag>Tagged literalthe tag's reader function#inst "2026-01-01T00:00:00Z"
#phpPHP array(php-indexed-array ...) or (php-associative-array ...)#php [1 2 3]
; ;;Line commentcomment to end of line;; note
#_Form commentskip the next form#_ expr
^Metadataattach metadata to the next form^:private

Collection literals

Each collection literal is a shorter way to call its constructor:

[1 2 3]       ; => [1 2 3]      same as (vector 1 2 3)
{:a 1 :b 2}   ; => {:a 1 :b 2}  same as (hash-map :a 1 :b 2)
#{1 2 3}      ; => #{1 2 3}     same as (hash-set 1 2 3)
'(1 2 3)      ; => (1 2 3)      same as (list 1 2 3)

Operations on each collection: Data Structures.

Quote '

Quote returns the next form as data instead of evaluating it:

'x            ; => x          the symbol x, same as (quote x)
'(+ 1 2)      ; => (+ 1 2)    the list, not 3

Quasiquote `

Quasiquote is like quote, but lets you evaluate parts of the form. ~ inserts the value of one form. ~@ splices in the elements of a sequence. Macros are built on it:

(let [x 5]   `(1 ~x 3))        ; => (1 5 3)
(let [xs [2 3 4]] `(1 ~@xs 5)) ; => (1 2 3 4 5)
Warning

Removed in 0.50: , (unquote) and ,@ (unquote-splicing). Use ~ and ~@. , is now whitespace, so `(f ,x) still parses but quotes x instead of unquoting it. There is no error, only a wrong expansion.

Auto-gensym name#

Inside a quasiquote, a symbol that ends in # becomes a fresh unique name. The same name# maps to the same generated name everywhere in that template. This keeps macro locals from clashing with user code. See Macros.

Warning

Removed in 0.50: the name$ suffix. Use name#.

Reader conditionals #?() and #?@()

#?() picks one form by platform key: Phel uses :phel, then falls back to :default. #?@() splices a platform-specific collection into the surrounding one. With them, one .cljc file can compile under both Phel and Clojure. See Reader Conditionals.

Deref @

@x is (deref x). It reads the current value of an atom:

(def counter (atom 41))
(swap! counter inc)
@counter ; => 42

Atoms: Global and local bindings.

Var-quote #'

#'my-fn returns the Var for a global definition, the same as (var my-fn). A call through the Var always uses the current definition, which is what makes redefining functions in the REPL work:

(def greeting "hello")
(deref #'greeting) ; => "hello"

Anonymous functions #(...)

#(...) defines a short function. % (or %1) is the first argument, %2 the second, and %& the rest:

(map #(* % 2) [1 2 3])          ; => (2 4 6)
(reduce #(+ %1 %2) 0 [1 2 3 4]) ; => 10

When to use a named fn instead: Functions and Recursion.

Warning

Removed in 0.50: the |(...) form with $, $1, $&. Use #(...) with %.

Regex literals #"..."

#"..." is a PCRE pattern, the same as (re-pattern "..."). Backslashes do not need doubling:

(re-find #"\d+" "abc123") ; => "123"

Matching functions: Basic Types.

Tagged literals #<tag> form

A tagged literal passes the next form to the tag's reader function while the code is read. Three tags are built in:

#inst "2026-01-01T00:00:00Z"                   ; a \DateTimeImmutable
#uuid "550e8400-e29b-41d4-a716-446655440000"   ; a Phel\Lang\UUID
#regex "\\d+"                                  ; a PCRE pattern string

Register your own tag with register-tag from phel.reader. For tags used across a project or shipped with a library, put the registrations in a data-readers.phel file at any source root. It loads automatically:

;; src/phel/data-readers.phel
(ns my-app.data-readers
  (:require phel.reader :refer [register-tag]))

(register-tag "money" (fn [[amount currency]]
                        {:amount amount :currency currency}))

After that, any source file can use the tag:

#money [100 "EUR"] ; => {:amount 100 :currency "EUR"}

phel.reader also has tag-registered?, unregister-tag, and registered-tags.

PHP array literals #php

#php builds a native PHP array without calling php-indexed-array or php-associative-array:

#php [1 2 3]        ; same as (php-indexed-array 1 2 3)
#php {"a" 1 "b" 2}  ; same as (php-associative-array "a" 1 "b" 2)

Only the outer form becomes a PHP array. Nested forms stay Phel data. More on PHP arrays: PHP interop.

Comments

; comments to the end of the line. #_ skips the next form:

[1 #_(+ 2 3) 4] ; => [1 4]

Comment styles and the comment macro: Basic Types.

Metadata ^

^ attaches metadata to the next form. A keyword sets that key to true. A map adds several keys:

^:private (def x 10)
^{:doc "Example"} (defn foo [] nil)

Type hints use the same syntax: ^int, ^"?int", ^{:tag "\\Foo\\Bar"}. See Return and parameter types.