, a=1, b='Hi')
#> (doto []
#.. (.extend "bar")
#.. (.sort)
#.. (.append "foo"))
>>> # doto
... (lambda _self_QzNo20_=[]:(
... _self_QzNo20_.extend(
... ('bar')),
... _self_QzNo20_.sort(),
... _self_QzNo20_.append(
... ('foo')),
... _self_QzNo20_)[-1])()
['a', 'b', 'r', 'foo']
;;; Threading
#> (-> "world!" ;Thread-first
#.. (.title)
#.. (->> (print "Hello"))) ;Thread-last
>>> # Qz_QzGT_
... # hissp.basic..QzMaybe_.Qz_QzGT_
... # hissp.basic..QzMaybe_.Qz_QzGT_
... # Qz_QzGT_QzGT_
... # hissp.basic..QzMaybe_.Qz_QzGT_QzGT_
... print(
... ('Hello'),
... ('world!').title())
Hello World!
(help _macro_.->)
(help _macro_.->>)
;;; The Basic Prelude
;; An inline convenience micro-prelude for Hissp.
;; Imports partial and reduce; star imports from operator and
;; itertools; defines the en- group utilities; and imports a copy of
;; hissp.basic.._macro_ (if available). Usually the first form in a file,
;; because it overwrites _macro_, but completely optional.
;; Implied for $ lissp -c commands.
#> (prelude) ;/!\ Or (hissp.basic.._macro_.prelude)
>>> # prelude
... __import__('builtins').exec(
... ('from functools import partial,reduce\n'
... 'from itertools import *;from operator import *\n'
... 'def entuple(*xs):return xs\n'
... 'def enlist(*xs):return[*xs]\n'
... 'def enset(*xs):return{*xs}\n'
... "def enfrost(*xs):return __import__('builtins').frozenset(xs)\n"
... 'def endict(*kvs):return{k:i.__next__()for i in[kvs.__iter__()]for k in i}\n'
... "def enstr(*xs):return''.join(''.__class__(x)for x in xs)\n"
... 'def engarde(xs,h,f,/,*a,**kw):\n'
... ' try:return f(*a,**kw)\n'
... ' except xs as e:return h(e)\n'
... "_macro_=__import__('types').SimpleNamespace()\n"
... "try:exec('from hissp.basic._macro_ import *',vars(_macro_))\n"
... 'except ModuleNotFoundError:pass'),
... __import__('builtins').globals())
;;; Control Flow
;; Hissp has no control flow, but you can build them with macros.
#> (any-for i (range 1 11) ;Imperative loop with break.
#.. (print i : end " ")
#.. (not_ (mod i 7)))
>>> # anyQz_for
... __import__('builtins').any(
... __import__('builtins').map(
... (lambda i:(
... print(
... i,
... end=(' ')),
... not_(
... mod(
... i,
... (7))))[-1]),
... range(
... (1),
... (11))))
1 2 3 4 5 6 7 True
;; 1 2 3 4 5 6 7 True
(if-else (eq (input "? ") 't) ;ternary conditional
(print "Yes")
(print "No"))
(let (x (float (input "? ")))
;; Multi-way branch.
(cond (lt x 0) (print "Negative")
(eq x 0) (print "Zero")
(gt x 0) (print "Positive")
:else (print "Not a number"))
(when (eq x 0) ;Conditional with side-effects & no alternative.
(print "In when")
(print "was zero"))
(unless (eq x 0)
(print "In unless")
(print "wasn't zero")))
;; Shortcutting logical and.
#> (&& True True False)
>>> # QzET_QzET_
... # hissp.basic.._macro_.let
... (lambda _G_QzNo33_=True:
... # hissp.basic.._macro_.ifQz_else
... (lambda test,*thenQz_else:
... __import__('operator').getitem(
... thenQz_else,
... __import__('operator').not_(
... test))())(
... _G_QzNo33_,
... (lambda :
... # hissp.basic..QzMaybe_.QzET_QzET_
... # hissp.basic.._macro_.let
... (lambda _G_QzNo33_=True:
... # hissp.basic.._macro_.ifQz_else
... (lambda test,*thenQz_else:
... __import__('operator').getitem(
... thenQz_else,
... __import__('operator').not_(
... test))())(
... _G_QzNo33_,
... (lambda :
... # hissp.basic..QzMaybe_.QzET_QzET_
... False),
... (lambda :_G_QzNo33_)))()),
... (lambda :_G_QzNo33_)))()
False
#> (&& False (print "oops"))
>>> # QzET_QzET_
... # hissp.basic.._macro_.let
... (lambda _G_QzNo33_=False:
... # hissp.basic.._macro_.ifQz_else
... (lambda test,*thenQz_else:
... __import__('operator').getitem(
... thenQz_else,
... __import__('operator').not_(
... test))())(
... _G_QzNo33_,
... (lambda :
... # hissp.basic..QzMaybe_.QzET_QzET_
... print(
... ('oops'))),
... (lambda :_G_QzNo33_)))()
False
#> (&& True 42)
>>> # QzET_QzET_
... # hissp.basic.._macro_.let
... (lambda _G_QzNo26_=True:
... # hissp.basic.._macro_.ifQz_else
... (lambda test,*thenQz_else:
... __import__('operator').getitem(
... thenQz_else,
... __import__('operator').not_(
... test))())(
... _G_QzNo26_,
... (lambda :
... # hissp.basic..QzMaybe_.QzET_QzET_
... (42)),
... (lambda :_G_QzNo26_)))()
42
;; Shortcutting logical or.
#> (|| True (print "oops"))
>>> # QzBAR_QzBAR_
... # hissp.basic.._macro_.let
... (lambda _first_QzNo34_=True:
... # hissp.basic.._macro_.ifQz_else
... (lambda test,*thenQz_else:
... __import__('operator').getitem(
... thenQz_else,
... __import__('operator').not_(
... test))())(
... _first_QzNo34_,
... (lambda :_first_QzNo34_),
... (lambda :
... # hissp.basic..QzMaybe_.QzBAR_QzBAR_
... print(
... ('oops')))))()
True
#> (|| 42 False)
>>> # QzBAR_QzBAR_
... # hissp.basic.._macro_.let
... (lambda _first_QzNo27_=(42):
... # hissp.basic.._macro_.ifQz_else
... (lambda test,*thenQz_else:
... __import__('operator').getitem(
... thenQz_else,
... __import__('operator').not_(
... test))())(
... _first_QzNo27_,
... (lambda :_first_QzNo27_),
... (lambda :
... # hissp.basic..QzMaybe_.QzBAR_QzBAR_
... False)))()
42
;;; Obligatory Factorial III
;; With the prelude, we can define a nicer-looking version.
#> (define factorial-III
#.. (lambda i
#.. (if-else (le i 1)
#.. 1
#.. (mul i (factorial-III (sub i 1))))))
>>> # define
... __import__('builtins').globals().update(
... factorialQz_III=(lambda i:
... # ifQz_else
... (lambda test,*thenQz_else:
... __import__('operator').getitem(
... thenQz_else,
... __import__('operator').not_(
... test))())(
... le(
... i,
... (1)),
... (lambda :(1)),
... (lambda :
... mul(
... i,
... factorialQz_III(
... sub(
... i,
... (1))))))))
#> (factorial-III 7)
>>> factorialQz_III(
... (7))
5040
;;;; The En- Group
;; These are small utility functions defined by the basic prelude.
;; Most of them put their arguments into a collection, hence the en-.
#> (entuple 1 2 3)
>>> entuple(
... (1),
... (2),
... (3))
(1, 2, 3)
#> (enlist 1 2 3)
>>> enlist(
... (1),
... (2),
... (3))
[1, 2, 3]
#> (enset 1 2 3)
>>> enset(
... (1),
... (2),
... (3))
{1, 2, 3}
;; From [en]- [fro]zen [s]e[t], because "enfrozenset" is too long.
#> (enfrost 1 2 3)
>>> enfrost(
... (1),
... (2),
... (3))
frozenset({1, 2, 3})
;; Unlike (dict) with kwargs, keys need not be identifiers.
#> (endict 1 2 3 4) ;Note the implied pairs.
>>> endict(
... (1),
... (2),
... (3),
... (4))
{1: 2, 3: 4}
;; The need for endict is apparent, considering alternatives.
#> (dict (enlist (entuple 1 2) (entuple 3 4)))
>>> dict(
... enlist(
... entuple(
... (1),
... (2)),
... entuple(
... (3),
... (4))))
{1: 2, 3: 4}
;; Converts to str and joins. Usually .format is good enough, but
;; sometimes you need interpolations inline, like f-strings. Don't forget
;; the format builtin can apply formatting specs.
#> (enstr ""(format 40 ".2f")" + "(add 1 1)"
")
>>> enstr(
... (''),
... format(
... (40),
... ('.2f')),
... (' + '),
... add(
... (1),
... (1)),
... ('
'))
'40.00 + 2
'
;; OK, so this one's not a collection. Guards against the targeted exception classes.
#> (engarde (entuple FloatingPointError ZeroDivisionError) ;two targets
#.. (lambda e (print "Oops!") e) ;handler (returns exception)
#.. truediv 6 0) ;calls it on your behalf
>>> engarde(
... entuple(
... FloatingPointError,
... ZeroDivisionError),
... (lambda e:(
... print(
... ('Oops!')),
... e)[-1]),
... truediv,
... (6),
... (0))
Oops!
ZeroDivisionError('division by zero')
#> (engarde ArithmeticError repr truediv 6 0) ;superclass target
>>> engarde(
... ArithmeticError,
... repr,
... truediv,
... (6),
... (0))
"ZeroDivisionError('division by zero')"
#> (engarde ArithmeticError repr truediv 6 2) ;returned answer
>>> engarde(
... ArithmeticError,
... repr,
... truediv,
... (6),
... (2))
3.0
;; You can nest them.
#> (engarde Exception ;The outer engarde
#.. print
#.. engarde ZeroDivisionError ; calls the inner.
#.. (lambda e (print "It means what you want it to mean."))
#.. truediv "6" 0) ;Try variations.
>>> engarde(
... Exception,
... print,
... engarde,
... ZeroDivisionError,
... (lambda e:
... print(
... ('It means what you want it to mean.'))),
... truediv,
... ('6'),
... (0))
unsupported operand type(s) for /: 'str' and 'int'
;;;; Advanced Reader Macros
;;; The Discard Macro
#> _#"The discard reader macro _# omits the next form.
#..It's a way to comment out code structurally.
#..It can also make block comments like this one.
#..This would show up when compiled if not for _#.
#.."
>>>
#> (print 1 _#(I'm not here!) 2 3)
>>> print(
... (1),
... (2),
... (3))
1 2 3
;;; Qualified Reader Macros
;; Invoke any qualified callable on the next parsed object at read time.
#> builtins..hex#3840 ;Qualified name ending in # is a reader macro.
>>> 0xf00
3840
#> builtins..ord#Q ;Reader macros make literal notation extensible.
>>> (81)
81
#> math..exp#1 ;e^1. Or to whatever number. At read time.
>>> (2.718281828459045)
2.718281828459045
;; Reader macros compose like functions.
#> 'hissp.munger..demunge#Qz_QzGT_QzGT_ ;Note the starting '.
>>> '->>'
'->>'
#> ''x
>>> ('quote',
... 'x',)
('quote', 'x')
#> '\'x
>>> 'QzAPOS_x'
'QzAPOS_x'
;; The reader normally discards them, but
#> 'builtins..repr#;comments are parsed objects too!
>>> "Comment(content='comments are parsed objects too!')"
"Comment(content='comments are parsed objects too!')"
_#"Except for strings and tuples, objects in Hissp should evaluate to
themselves. But when the object lacks a Python literal notation,
the compiler is in a pickle!
"
#> builtins..float#inf
>>> __import__('pickle').loads( # inf
... b'Finf\n.'
... )
inf
;;; Inject
_#"The 'inject' reader macro compiles and evaluates the next form at
read time and injects the resulting object directly into the Hissp
tree, like a qualified reader macro does.
"
#> '(1 2 (operator..add 1 2)) ;Quoting happens at compile time.
>>> ((1),
... (2),
... ('operator..add',
... (1),
... (2),),)
(1, 2, ('operator..add', 1, 2))
#> '(1 2 .#(operator..add 1 2)) ;Inject happens at read time.
>>> ((1),
... (2),
... (3),)
(1, 2, 3)
#> (fractions..Fraction 1 2) ;Run time eval. Compiles to equivalent code.
>>> __import__('fractions').Fraction(
... (1),
... (2))
Fraction(1, 2)
#> .#(fractions..Fraction 1 2) ;Read time eval. Compiles to equivalent object.
>>> __import__('pickle').loads( # Fraction(1, 2)
... b'cfractions\nFraction\n(V1/2\ntR.'
... )
Fraction(1, 2)
_#"Recall that Hissp-level string objects can represent
arbitrary Python code. It's usually used for identifiers,
but can be anything, even complex formulas.
"
#> (lambda abc
#.. ;; Hissp may not have operators, but Python does.
#.. .#"(-b + (b**2 - 4*a*c)**0.5)/(2*a)")
>>> (lambda a,b,c:(-b + (b**2 - 4*a*c)**0.5)/(2*a))
at 0x...>
_#"Remember the raw string and hash string reader syntax makes Python-
level strings, via a Hissp-level string containing a Python string
literal. It is NOT for creating a Hissp-level string, which would
normally contain Python code. Use inject for that.
"
#> '"a string" ;Python code for a string. In a string.
>>> "('a string')"
"('a string')"
;; Injection of an object to the Hissp level. In this case, a string object.
#> '.#"a string" ;Quoting renders a Hissp-level string as data.
>>> 'a string'
'a string'
_#"Objects without literals don't pickle until the compiler has to emit
them as Python code. That may never happen if another macro gets there first.
"
#> 'builtins..repr#(re..compile#.#"[1-9][0-9]*" builtins..float#inf)
>>> "(re.compile('[1-9][0-9]*'), inf)"
"(re.compile('[1-9][0-9]*'), inf)"
#> re..compile#.#"[1-9][0-9]*"
>>> __import__('pickle').loads( # re.compile('[1-9][0-9]*')
... b'cre\n_compile\n(V[1-9][0-9]*\nI32\ntR.'
... )
re.compile('[1-9][0-9]*')
;; Statement injections work at the top level only.
#> .#"from operator import *" ;All your operator are belong to us.
>>> from operator import *
;;;; The Basic Reader Macros
#> b#"bytes" ;Bytes reader macro.
>>> b'bytes'
b'bytes'
#> b'bytes' ;NameError about 'bQzAPOS_bytesQzAPOS_'
>>> bQzAPOS_bytesQzAPOS_
Traceback (most recent call last):
File "", line 1, in
NameError: name 'bQzAPOS_bytesQzAPOS_' is not defined
#> b#"bytes
#..with
#..newlines
#.." ;Same as b#"bytes\nwith\nnewlines\n".
>>> b'bytes\nwith\nnewlines\n'
b'bytes\nwith\nnewlines\n'
#> (help _macro_.b\#) ;Unqualified reader macros live in _macro_ too.
>>> help(
... _macro_.bQzHASH_)
Help on function in module hissp.basic:
lambda raw
``b#`` bytes literal reader macro
;; The en- reader macro.
#> (en#list 1 2 3) ;Like enlist.
>>> (lambda *_xs_QzNo31_:
... list(
... _xs_QzNo31_))(
... (1),
... (2),
... (3))
[1, 2, 3]
#> (en#.extend _ 4 5 6) ;Methods too.
>>> (lambda _self_QzNo31_,*_xs_QzNo31_:
... _self_QzNo31_.extend(
... _xs_QzNo31_))(
... _,
... (4),
... (5),
... (6))
#> _
>>> _
[1, 2, 3, 4, 5, 6]
#> (en#collections..deque 1 2 3) ;Generalizes to any function of 1 iterable.
>>> (lambda *_xs_QzNo31_:
... __import__('collections').deque(
... _xs_QzNo31_))(
... (1),
... (2),
... (3))
deque([1, 2, 3])
;; Not technically a basic reader macro, but a basic macro for defining them.
;; Alias makes a new reader macro to abbreviate a qualifier.
;; This is an alternative to adding an import to _macro_ or globals.
#> (hissp.basic.._macro_.alias M: hissp.basic.._macro_)
>>> # hissp.basic.._macro_.alias
... # hissp.basic.._macro_.defmacro
... # hissp.basic.._macro_.let
... (lambda _fn_QzNo7_=(lambda _prime_QzNo34_,_reader_QzNo34_=None,*_args_QzNo34_:(
... "('Aliases hissp.basic.._macro_ as MQzCOLON_#')",
... # hissp.basic.._macro_.ifQz_else
... (lambda test,*thenQz_else:
... __import__('operator').getitem(
... thenQz_else,
... __import__('operator').not_(
... test))())(
... _reader_QzNo34_,
... (lambda :
... __import__('builtins').getattr(
... __import__('hissp.basic',fromlist='?')._macro_,
... ('{}{}').format(
... _reader_QzNo34_,
... # hissp.basic.._macro_.ifQz_else
... (lambda test,*thenQz_else:
... __import__('operator').getitem(
... thenQz_else,
... __import__('operator').not_(
... test))())(
... __import__('operator').contains(
... 'hissp.basic.._macro_',
... '_macro_'),
... (lambda :'QzHASH_'),
... (lambda :('')))))(
... _prime_QzNo34_,
... *_args_QzNo34_)),
... (lambda :
... ('{}.{}').format(
... 'hissp.basic.._macro_',
... _prime_QzNo34_))))[-1]):(
... __import__('builtins').setattr(
... _fn_QzNo7_,
... '__qualname__',
... ('.').join(
... ('_macro_',
... 'MQzCOLON_QzHASH_',))),
... __import__('builtins').setattr(
... __import__('operator').getitem(
... __import__('builtins').globals(),
... '_macro_'),
... 'MQzCOLON_QzHASH_',
... _fn_QzNo7_))[-1])()
#> 'M:#alias ;Now short for 'hissp.basic.._macro_.alias'.
>>> 'hissp.basic.._macro_.alias'
'hissp.basic.._macro_.alias'
#> M:#b\# ;b# macro callable
>>> __import__('hissp.basic',fromlist='?')._macro_.bQzHASH_
#> (M:#b\# "b# macro at compile time")
>>> # hissp.basic.._macro_.bQzHASH_
... b'b# macro at compile time'
b'b# macro at compile time'
#> hissp.basic.._macro_.b\##"Fully qualified b# macro at read time."
>>> b'Fully qualified b# macro at read time.'
b'Fully qualified b# macro at read time.'
;; Comment string.
#> <<#;Don't worry about the "quotes".
>>> 'Don\'t worry about the "quotes".'
'Don\'t worry about the "quotes".'
;;; Aside: Extra (!), the Final Builtin Reader Macro
_#"Reader macros take one primary argument, but additional arguments
can be passed in with the extra macro !. A reader macro consumes the
next parsed object, and if it's an Extra, consumes one again. Thus,
extras must be written between the # and primary argument, but because
they're often optional refinements, which are easier to define as
trailing optional parameters in in Python functions, they get passed
in after the primary argument.
"
#> (setattr _macro_ 'L\# enlist)
>>> setattr(
... _macro_,
... 'LQzHASH_',
... enlist)
#> L#primary
>>> ['primary']
['primary']
#> L#!1 primary
>>> ['primary', 1]
['primary', 1]
;; Alias can work on reader macros too!
#> M:#!b"Read-time b# via alias." ;Extra arg for alias with (!)
>>> b'Read-time b# via alias.'
b'Read-time b# via alias.'
#> L# !1 !2 primary ;Note the order!
>>> ['primary', 1, 2]
['primary', 1, 2]
#> .#(enlist "primary" 1 2) ;Inject. Note the order.
>>> ['primary', 1, 2]
['primary', 1, 2]
#> !1 ;! is for a single Extra.
>>> __import__('pickle').loads( # Extra([1])
... b'ccopyreg\n_reconstructor\n(chissp.reader\nExtra\ncbuiltins\ntuple\n(I1\nttR.'
... )
Extra([1])
#> hissp.reader..Extra#(: :? 0 :* (1 2 3)) ; but Extra can have multiple elements.
>>> __import__('pickle').loads( # Extra([':', ':?', 0, ':*', (1, 2, 3)])
... b'ccopyreg\n_reconstructor\n(chissp.reader\nExtra\ncbuiltins\ntuple\n(V:\nV:?\nI0\nV:*\n(I1\nI2\nI3\ntttR.'
... )
Extra([':', ':?', 0, ':*', (1, 2, 3)])
#> !!!1 2 3 ;Extras can have extras. They stack.
>>> __import__('pickle').loads( # Extra([1, 2, 3])
... b'ccopyreg\n_reconstructor\n(chissp.reader\nExtra\ncbuiltins\ntuple\n(I1\nI2\nI3\nttR.'
... )
Extra([1, 2, 3])
#> L#!: !:* !(0 1 2) !:? !3 primary ;Unpacking works like calls.
>>> ['primary', 0, 1, 2, 3]
['primary', 0, 1, 2, 3]
#> L#!0 !: !:* !(1 2 3)primary ;Same effect.
>>> ['primary', 0, 1, 2, 3]
['primary', 0, 1, 2, 3]
#> L#hissp.reader..Extra#(0 : :* (1 2 3))primary ;Same effect.
>>> ['primary', 0, 1, 2, 3]
['primary', 0, 1, 2, 3]
#> (setattr _macro_ 'X\# hissp.reader..Extra)
>>> setattr(
... _macro_,
... 'XQzHASH_',
... __import__('hissp.reader',fromlist='?').Extra)
#> L# !0 X#(1 2) !3 primary ;Same effect.
>>> ['primary', 0, 1, 2, 3]
['primary', 0, 1, 2, 3]
#> L#X#(0 : :* (1 2 3))primary ;Same effect.
>>> ['primary', 0, 1, 2, 3]
['primary', 0, 1, 2, 3]
;; Kwargs also work like calls.
#> builtins..dict#()
>>> {}
{}
#> builtins..dict#!: !spam !1 !foo !2 !:** !.#(dict : eggs 3 bar 4)()
>>> {'spam': 1, 'foo': 2, 'eggs': 3, 'bar': 4}
{'spam': 1, 'foo': 2, 'eggs': 3, 'bar': 4}
#> builtins..dict#X#(: spam 1 foo 2 :** .#(dict : eggs 3 bar 4))()
>>> {'spam': 1, 'foo': 2, 'eggs': 3, 'bar': 4}
{'spam': 1, 'foo': 2, 'eggs': 3, 'bar': 4}
#> builtins..dict#!: !!spam 1 !!foo 2 !!:** .#(dict : eggs 3 bar 4)()
>>> {'spam': 1, 'foo': 2, 'eggs': 3, 'bar': 4}
{'spam': 1, 'foo': 2, 'eggs': 3, 'bar': 4}
;; Yeah, you can nest these if you have to.
#> L# !x
#.. !L# !1 L# !A
#.. inner
#.. !y
#..outer
>>> ['outer', 'x', [['inner', 'A'], 1], 'y']
['outer', 'x', [['inner', 'A'], 1], 'y']
;; The compiler will evaluate tuples no matter how the reader produces them.
#> builtins..tuple#L# !"Hello" !"World!" print
>>> print(
... ('Hello'),
... ('World!'))
Hello World!
;;; Joined Comment String
#> <<#!;C:\bin
#.. !;C:\Users\ME\Documents
#.. !;C:\Users\ME\Pictures
#..";"
>>> 'C:\\bin;C:\\Users\\ME\\Documents;C:\\Users\\ME\\Pictures'
'C:\\bin;C:\\Users\\ME\\Documents;C:\\Users\\ME\\Pictures'
;; Embed other languages without escapes.
#> (exec
#.. <<#
#.. !;for i in 'abc':
#.. !; for j in 'xyz':
#.. !; print(i+j, end=" ")
#.. !;print('.')
#.. !;
#.. #"\n")
>>> exec(
... ("for i in 'abc':\n"
... " for j in 'xyz':\n"
... ' print(i+j, end=" ")\n'
... "print('.')\n"))
ax ay az bx by bz cx cy cz .
;;;; Collections
;;; Templates and Tuples
#> '(1 2 3) ;tuple
>>> ((1),
... (2),
... (3),)
(1, 2, 3)
#> `(,(pow 42 0) ,(+ 1 1) 3) ;Interpolate with templates.
>>> (lambda * _: _)(
... pow(
... (42),
... (0)),
... QzPLUS_(
... (1),
... (1)),
... (3))
(1, 2, 3)
#> `("a" 'b c ,'d ,"e") ;These can be tricky. Careful.
>>> (lambda * _: _)(
... "('a')",
... (lambda * _: _)(
... 'quote',
... '__main__..b'),
... '__main__..c',
... 'd',
... ('e'))
("('a')", ('quote', '__main__..b'), '__main__..c', 'd', 'e')
#> '(1 "a") ;Recursive quoting.
>>> ((1),
... "('a')",)
(1, "('a')")
#> '(1 .#"a") ;Injected Hissp-level string.
>>> ((1),
... 'a',)
(1, 'a')
#> `(1 ,"a") ;Interpolated string.
>>> (lambda * _: _)(
... (1),
... ('a'))
(1, 'a')
;; Helper functions may be easier than templates for data.
#> (entuple 0 "a" 'b :c)
>>> entuple(
... (0),
... ('a'),
... 'b',
... ':c')
(0, 'a', 'b', ':c')
#> (en#tuple 0 "a" 'b :c)
>>> (lambda *_xs_QzNo32_:
... tuple(
... _xs_QzNo32_))(
... (0),
... ('a'),
... 'b',
... ':c')
(0, 'a', 'b', ':c')
;;; Other Collection Types
#> (list `(1 ,(+ 1 1) 3))
>>> list(
... (lambda * _: _)(
... (1),
... QzPLUS_(
... (1),
... (1)),
... (3)))
[1, 2, 3]
#> (set '(1 2 3))
>>> set(
... ((1),
... (2),
... (3),))
{1, 2, 3}
#> (bytes '(98 121 116 101 115))
>>> bytes(
... ((98),
... (121),
... (116),
... (101),
... (115),))
b'bytes'
#> (bytes.fromhex "6279746573")
>>> bytes.fromhex(
... ('6279746573'))
b'bytes'
;; Read-time equivalents.
#> builtins..bytes.fromhex#.#"6279746573"
>>> b'bytes'
b'bytes'
#> builtins..bytes#(98 121 116 101 115)
>>> b'bytes'
b'bytes'
#> .#"b'bytes'" ;bytes literal Python injection
>>> b'bytes'
b'bytes'
#> (dict : + 0 a 1 b 2) ;Symbol keys are easy. The common case.
>>> dict(
... QzPLUS_=(0),
... a=(1),
... b=(2))
{'QzPLUS_': 0, 'a': 1, 'b': 2}
#> (.__getitem__ _ '+)
>>> _.__getitem__(
... 'QzPLUS_')
0
#> (dict (zip '(1 2 3) "abc")) ;Non-symbol keys are possible.
>>> dict(
... zip(
... ((1),
... (2),
... (3),),
... ('abc')))
{1: 'a', 2: 'b', 3: 'c'}
#> (dict '((a 1) (2 b))) ;Mixed key types. Beware of quoting strings.
>>> dict(
... (('a',
... (1),),
... ((2),
... 'b',),))
{'a': 1, 2: 'b'}
#> (dict `((,'+ 42)
#.. (,(+ 1 1) ,'b))) ;Run-time interpolation with a template.
>>> dict(
... (lambda * _: _)(
... (lambda * _: _)(
... 'QzPLUS_',
... (42)),
... (lambda * _: _)(
... QzPLUS_(
... (1),
... (1)),
... 'b')))
{'QzPLUS_': 42, 2: 'b'}
#> (.__getitem__ _ '+)
>>> _.__getitem__(
... 'QzPLUS_')
42
#> (endict 1 2 'a 'b)
>>> endict(
... (1),
... (2),
... 'a',
... 'b')
{1: 2, 'a': 'b'}
;;; Collection Atoms
#> .#"[]" ;List from a Python injection.
>>> []
[]
#> .#[] ;You can drop the quotes sometimes.
>>> []
[]
#> [] ; And the reader macro!
>>> []
[]
#> [1,2,3] ;List/set/dict atoms are a kind of injection.
>>> [1, 2, 3]
[1, 2, 3]
#> {1,2,3} ; They read in as a single atom, so have
>>> {1, 2, 3}
{1, 2, 3}
#> {'a':1,2:b'b'} ; compile-time literals only--No interpolation!
>>> {'a': 1, 2: b'b'}
{'a': 1, 2: b'b'}
#> [1,{2},{3:[4,5]},'six'] ;Nesting is allowed.
>>> [1, {2}, {3: [4, 5]}, 'six']
[1, {2}, {3: [4, 5]}, 'six']
;; Collection atoms are a convenience for simple cases only.
#> .#"['1 2','3',(4,5),R'6;7\8']"
>>> ['1 2','3',(4,5),R'6;7\8']
['1 2', '3', (4, 5), '6;7\\8']
;; After dropping quotes, these tokenize like other atoms, so you need escapes.
#> ['1\ 2',\"3\",\(4,5\),R'6\;7\\8'] ;Not so convenient now. Simple cases only!
>>> ['1 2', '3', (4, 5), '6;7\\8']
['1 2', '3', (4, 5), '6;7\\8']
;; Constructors or helpers also work. (And can interpolate run-time data.)
#> (list `(,"1 2" ,"3" (4 5) ,"6;7\8"))
>>> list(
... (lambda * _: _)(
... ('1 2'),
... ('3'),
... (lambda * _: _)(
... (4),
... (5)),
... ('6;7\\8')))
['1 2', '3', (4, 5), '6;7\\8']
#> (enlist "1 2" "3" '(4 5) "6;7\8")
>>> enlist(
... ('1 2'),
... ('3'),
... ((4),
... (5),),
... ('6;7\\8'))
['1 2', '3', (4, 5), '6;7\\8']
_#"Even though they evaluate the same, there's a subtle compile-time difference
between a collection atom and a string injection. This can matter because
macros get all their arguments unevaluated.
"
#> '[1,'''2\ 3'''] ;[1, '2 3']
>>> [1, '2 3']
[1, '2 3']
#> '.#"[1,'''2 3''']" ;"[1,'''2 3''']"
>>> "[1,'''2 3''']"
"[1,'''2 3''']"
;; But you can still get a real collection at compile time.
#> '.#(eval "[1,'''2 3''']") ;[1, '2 3']
>>> [1, '2 3']
[1, '2 3']
#> '.#.#"[1,'''2 3''']" ;[1, '2 3']
>>> [1, '2 3']
[1, '2 3']
#> (lambda ['a','b','c']) ;I don't recommend this, but it works.
>>> (lambda a,b,c:())
at 0x...>
#> (lambda .#"['a','b','c']") ;Oops.
>>> (lambda [,',a,',,,',b,',,,',c,',]:())
Traceback (most recent call last):
...
(lambda [,',a,',,,',b,',,,',c,',]:())
^
SyntaxError: invalid syntax
#> (lambda .#.#"['a','b','c']") ;Another inject fixes it.
>>> (lambda a,b,c:())
at 0x...>
#> '(lambda ['a','b','c']) ;Params is a list.
>>> ('lambda',
... ['a', 'b', 'c'],)
('lambda', ['a', 'b', 'c'])
#> '(lambda .#"['a','b','c']") ;Params is a string.
>>> ('lambda',
... "['a','b','c']",)
('lambda', "['a','b','c']")
#> '(lambda .#.#"['a','b','c']") ;Params is a list.
>>> ('lambda',
... ['a', 'b', 'c'],)
('lambda', ['a', 'b', 'c'])
#> (lambda "abc") ;Oops.
>>> (lambda (,',a,b,c,',):())
Traceback (most recent call last):
...
(lambda (,',a,b,c,',):())
^
SyntaxError: invalid syntax
#> (lambda .#"abc") ;Inject fixes it.
>>> (lambda a,b,c:())
at 0x...>
#> '(lambda "abc") ;See why? Extra characters.
>>> ('lambda',
... "('abc')",)
('lambda', "('abc')")
#> '(lambda .#"abc") ;Evaluated object.
>>> ('lambda',
... 'abc',)
('lambda', 'abc')
#> (lambda abc) ;Compare to using a symbol.
>>> (lambda a,b,c:())
at 0x...>
#> '(lambda abc)
>>> ('lambda',
... 'abc',)
('lambda', 'abc')