Hyrule v0.5.0 manual¶
Hyrule is a utility library for the Hy programming language. It can be thought of as the Hy equivalent, or addition, to Python's standard library. While intended primarily for Hy programs, its functions and classes can be used in Python as with any other Python library; just import hyrule
. Hyrule's macros, on the other hand, are only really usable in Hy.
All of Hyrule's contents can be imported or required directly from the top-level module hyrule
, as in (require hyrule [branch])
. The specific submodule an object belongs to may not be stable between releases.
Hyrule's documentation can be read online on Hylang.org.
You can run Hyrule's test suite with the command pytest
and build its documentation with ( cd docs; sphinx-build . _build -b html )
.
Reference¶
anaphoric
— Anaphoric macros¶
The anaphoric macros module makes functional programming in Hy very concise and easy to read.
An anaphoric macro is a type of programming macro that deliberately captures some form supplied to the macro which may be referred to by an anaphor (an expression referring to another).
—Wikipedia (https://en.wikipedia.org/wiki/Anaphoric_macro)
- reader macro(%)¶
Makes an expression into a function with an implicit
%
parameter list.A
%i
symbol designates the (1-based) i th parameter (such as%3
). Only the maximum%i
determines the number of%i
parameters--the others need not appear in the expression.%*
and%**
name the#*
and#**
parameters, respectively.Examples
=> (#%[%1 %6 42 [%2 %3] %* %4] 1 2 3 4 555 6 7 8) [1 6 42 [2 3] #(7 8) 4]
=> (#% %** :foo 2) {"foo" 2}
When used on an s-expression,
#%
is similar to Clojure's anonymous function literals--#()
:=> (setv add-10 #%(+ 10 %1)) => (add-10 6) 16
Note
#%
determines the parameter list by the presence of a%*
or%**
symbol and by the maximum%i
symbol found anywhere in the expression, so nesting of#%
forms is not recommended.
- macro(ap-if test-form then-form else-form)¶
As if, but the result of the test form is named
it
in the subsequent forms. The else-clause is optional.Examples
=> (import os) => (ap-if (.get os.environ "PYTHONPATH") ... (print "Your PYTHONPATH is" it))
- macro(ap-each xs #* body)¶
Evaluate the body forms for each element
it
ofxs
and returnNone
.Examples
=> (ap-each [1 2 3] (print it)) 1 2 3
- macro(ap-each-while xs form #* body)¶
As
ap-each
, but the formpred
is run before the body forms on each iteration, and the loop ends ifpred
is false.Examples
=> (ap-each-while [1 2 3 4 5 6] (< it 4) (print it)) 1 2 3
- macro(ap-map form xs)¶
Create a generator like
map()
that yields each result ofform
evaluated withit
bound to successive elements ofxs
.Examples
=> (list (ap-map (* it 2) [1 2 3])) [2 4 6]
- macro(ap-map-when predfn rep xs)¶
As
ap-map
, but the predicate functionpredfn
(yes, that's a function, not an anaphoric form) is applied to eachit
, and the anaphoric mapping formrep
is only applied if the predicate is true. Otherwise,it
is yielded unchanged.Examples
=> (list (ap-map-when (fn [x] (% x 2)) (* it 2) [1 2 3 4])) [2 2 6 4]
=> (list (ap-map-when (fn [x] (= (% x 2) 0)) (* it 2) [1 2 3 4])) [1 4 3 8]
- macro(ap-filter form xs)¶
The
filter()
equivalent ofap-map
.Examples
=> (list (ap-filter (> (* it 2) 6) [1 2 3 4 5])) [4 5]
- macro(ap-reject form xs)¶
Equivalent to
(ap-filter (not form) xs)
.Examples
=> (list (ap-reject (> (* it 2) 6) [1 2 3 4 5])) [1 2 3]
- macro(ap-dotimes n #* body)¶
Equivalent to
(ap-each (range n) body…)
.Examples
=> (setv n []) => (ap-dotimes 3 (.append n it)) => n [0 1 2]
- macro(ap-first form xs)¶
Evaluate the predicate
form
for each elementit
ofxs
. When the predicate is true, stop and returnit
. If the predicate is never true, returnNone
.Examples
=> (ap-first (> it 5) (range 10)) 6
- macro(ap-last form xs)¶
Usage:
(ap-last form list)
Evaluate the predicate
form
for every elementit
ofxs
. Return the last element for which the predicate is true, orNone
if there is no such element.Examples
=> (ap-last (> it 5) (range 10)) 9
- macro(ap-reduce form o!xs initial-value)¶
This macro is an anaphoric version of
functools.reduce()
. It works as follows:Bind
acc
to the first element ofxs
, bindit
to the second, and evaluateform
.Bind
acc
to the result, bindit
to the third value ofxs
, and evaluateform
again.Bind
acc
to the result, and continue untilxs
is exhausted.
If
initial-value
is supplied, the process instead begins withacc
set toinitial-value
andit
set to the first element ofxs
.Examples
=> (ap-reduce (+ it acc) (range 10)) 45
argmove
— Macros for calls with unusual argument placement¶
- macro(-> head #* args)¶
Thread head first through the rest of the forms.
->
(or the threading macro) is used to avoid nesting of expressions. The threading macro inserts each expression into the next expression's first argument place. The following code demonstrates this:Examples
=> (defn output [a b] (print a b)) => (-> (+ 4 6) (output 5)) 10 5
- macro(->> head #* args)¶
Thread head last through the rest of the forms.
->>
(or the threading tail macro) is similar to the threading macro, but instead of inserting each expression into the next expression's first argument, it appends it as the last argument. The following code demonstrates this:Examples
=> (defn output [a b] (print a b)) => (->> (+ 4 6) (output 5)) 5 10
- macro(as-> head name #* rest)¶
Beginning with head, expand a sequence of assignments rest to name.
Each assignment is passed to the subsequent form. Returns the final assignment, leaving the name bound to it in the local scope.
This behaves similarly to other threading macros, but requires specifying the threading point per-form via the name, rather than fixing to the first or last argument.
Examples
example how
->
andas->
relate:=> (as-> 0 it ... (inc it) ... (inc it)) 2
=> (-> 0 inc inc) 2
create data for our cuttlefish database:
=> (setv data [{:name "hooded cuttlefish" ... :classification {:subgenus "Acanthosepion" ... :species "Sepia prashadi"} ... :discovered {:year 1936 ... :name "Ronald Winckworth"}} ... {:name "slender cuttlefish" ... :classification {:subgenus "Doratosepion" ... :species "Sepia braggi"} ... :discovered {:year 1907 ... :name "Sir Joseph Cooke Verco"}}])
retrieve name of first entry:
=> (as-> (get data 0) it ... (:name it)) "hooded cuttlefish"
retrieve species of first entry:
=> (as-> (get data 0) it ... (:classification it) ... (:species it)) "Sepia prashadi"
find out who discovered slender cuttlefish:
=> (as-> (filter (fn [entry] (= (:name entry) ... "slender cuttlefish")) data) it ... (get it 0) ... (:discovered it) ... (:name it)) "Sir Joseph Cooke Verco"
more convoluted example to load web page and retrieve data from it:
=> (import urllib.request [urlopen]) => (as-> (urlopen "http://docs.hylang.org/en/stable/") it ... (.read it) ... (.decode it "utf-8") ... (lfor x it :if (!= it "Welcome") it) ... (cut it 30) ... (.join "" it)) "Welcome to Hy’s documentation!"
Note
In these examples, the REPL will report a tuple (e.g. ('Sepia prashadi', 'Sepia prashadi')) as the result, but only a single value is actually returned.
- macro(doto form #* expressions)¶
Perform possibly mutating expressions on form, returning resulting obj.
doto
is used to simplify a sequence of method calls to an object.Examples
=> (doto [] (.append 1) (.append 2) (.reverse)) [2 1]
=> (setv collection []) => (.append collection 1) => (.append collection 2) => (.reverse collection) => collection [2 1]
collections
— Tools for data structures¶
- macro(assoc coll k1 v1 #* other-kvs)¶
Associate key/index value pair(s) to a collection coll like a dict or list.
assoc
is used to associate a key with a value in a dictionary or to set an index of a list to a value. It takes at least three parameters: the data structure to be modified, a key or index, and a value. If more than three parameters are used, it will associate in pairs.Examples
=> (do ... (setv collection {}) ... (assoc collection "Dog" "Bark") ... (print collection)) {"Dog" "Bark"}
=> (do ... (setv collection {}) ... (assoc collection "Dog" "Bark" "Cat" "Meow") ... (print collection)) {"Cat" "Meow" "Dog" "Bark"}
=> (do ... (setv collection [1 2 3 4]) ... (assoc collection 2 None) ... (print collection)) [1 2 None 4]
Note
assoc
modifies the datastructure in place and returnsNone
.
- macro(ncut seq key1 #* keys)¶
N-Dimensional
cut
macro with shorthand slice notation.Libraries like
numpy
andpandas
extend Python's sequence slicing syntax to work with tuples to allow for elegant handling of multidimensional arrays (numpy) and multi-axis selections (pandas). A key inncut
can be any valid kind of index; specific, ranged, a numpy style mask. Any library can make use of tuple based slicing, so check with each lib for what is and isn't valid.- Parameters:
seq -- Slicable sequence
key1 -- A valid sequence index. What is valid can change from library to library.
*keys -- Additional indices. Specifying more than one index will expand to a tuple allowing multi-dimensional indexing.
Examples
Single dimensional list slicing
=> (ncut (list (range 10)) 2:8:2) [2 4 6]
numpy multidimensional slicing:
=> (setv a (.reshape (np.arange 36) #(6 6))) => a array([[ 0, 1, 2, 3, 4, 5], [ 6, 7, 8, 9, 10, 11], [12, 13, 14, 15, 16, 17], [18, 19, 20, 21, 22, 23], [24, 25, 26, 27, 28, 29], [30, 31, 32, 33, 34, 35]]) => (ncut a #(0 1 2 3 4) #(1 2 3 4 5)) array([ 1, 8, 15, 22, 29]) => (ncut a 3: #(0 2 5)) array([[18, 20, 23], [24, 26, 29], [30, 32, 35]]) => (ncut a 1:-1:2 3:5) array([[ 9, 10], [21, 22]]) => (ncut a ::2 3 None) array([[ 3], [15], [27]]) => (ncut a ... 0) array([ 0, 6, 12, 18, 24, 30])
Because variables can have colons in Hy (eg:
abc:def
is a valid identifier), the sugared slicing form only allows numeric literals. In order to construct slices that involve names and/or function calls, the form(: ...)
can be used in anncut
expresion as an escape hatch toslice
:=> (setv abc:def -2) => (hy.macroexpand '(ncut a abc:def (: (sum [1 2 3]) None abc:def))) (get a #(abc:def (slice (sum [1 2 3]) None abc:def)))
Pandas allows extensive slicing along single or multiple axes:
=> (setv s1 (pd.Series (np.random.randn 6) :index (list "abcdef"))) => s1 a 0.687645 b -0.598732 c -1.452075 d -0.442050 e -0.060392 f 0.440574 dtype: float64 => (ncut s1 (: "c" None 2)) c -1.452075 e -0.060392 dtype: float64
=> (setv df (pd.DataFrame (np.random.randn 8 4) :index (pd.date-range "1/1/2000" :periods 8) :columns (list "ABCD"))) => df A B C D 2000-01-01 -0.185291 -0.803559 -1.483985 -0.136509 2000-01-02 -3.290852 -0.688464 2.715168 0.750664 2000-01-03 0.771222 -1.170541 -1.015144 0.491510 2000-01-04 0.243287 0.769975 0.473460 0.407027 2000-01-05 -0.857291 2.395931 -0.950846 0.299086 2000-01-06 -0.195595 0.981791 -0.673646 0.637218 2000-01-07 -1.022636 -0.854971 0.603573 -1.169342 2000-01-08 -0.494866 0.783248 -0.064389 -0.960760 => (ncut df.loc : ["B" "A"]) B A 2000-01-01 -0.803559 -0.185291 2000-01-02 -0.688464 -3.290852 2000-01-03 -1.170541 0.771222 2000-01-04 0.769975 0.243287 2000-01-05 2.395931 -0.857291 2000-01-06 0.981791 -0.195595 2000-01-07 -0.854971 -1.022636 2000-01-08 0.783248 -0.494866
- (postwalk f form)¶
Performs depth-first, post-order traversal of
form
. Callsf
on each sub-form, usesf
's return value in place of the original.Examples
=> (import hyrule.contrib.walk [postwalk]) => (setv trail '([1 2 3] [4 [5 6 [7]]])) => (defn walking [x] ... (print "Walking" x :sep "\n") ... x) => (postwalk walking trail) Walking 1 Walking 2 Walking 3 Walking hy.models.Expression([ hy.models.Integer(1), hy.models.Integer(2), hy.models.Integer(3)]) Walking 4 Walking 5 Walking 6 Walking 7 Walking hy.models.Expression([ hy.models.Integer(7)]) Walking hy.models.Expression([ hy.models.Integer(5), hy.models.Integer(6), hy.models.List([ hy.models.Integer(7)])]) Walking hy.models.Expression([ hy.models.Integer(4), hy.models.List([ hy.models.Integer(5), hy.models.Integer(6), hy.models.List([ hy.models.Integer(7)])])]) Walking hy.models.Expression([ hy.models.List([ hy.models.Integer(1), hy.models.Integer(2), hy.models.Integer(3)]), hy.models.List([ hy.models.Integer(4), hy.models.List([ hy.models.Integer(5), hy.models.Integer(6), hy.models.List([ hy.models.Integer(7)])])])]) '([1 2 3] [4 [5 6 [7]]]))
- (prewalk f form)¶
Performs depth-first, pre-order traversal of
form
. Callsf
on each sub-form, usesf
's return value in place of the original.Examples
=> (import hyrule.contrib.walk [prewalk]) => (setv trail '([1 2 3] [4 [5 6 [7]]])) => (defn walking [x] ... (print "Walking" x :sep "\n") ... x) => (prewalk walking trail) Walking hy.models.Expression([ hy.models.List([ hy.models.Integer(1), hy.models.Integer(2), hy.models.Integer(3)]), hy.models.List([ hy.models.Integer(4), hy.models.List([ hy.models.Integer(5), hy.models.Integer(6), hy.models.List([ hy.models.Integer(7)])])])]) Walking hy.models.List([ hy.models.Integer(1), hy.models.Integer(2), hy.models.Integer(3)]) Walking 1 Walking 2 Walking 3 Walking hy.models.List([ hy.models.Integer(4), hy.models.List([ hy.models.Integer(5), hy.models.Integer(6), hy.models.List([ hy.models.Integer(7)])])]) Walking 4 Walking hy.models.List([ hy.models.Integer(5), hy.models.Integer(6), hy.models.List([ hy.models.Integer(7)])]) Walking 5 Walking 6 Walking hy.models.List([ hy.models.Integer(7)]) Walking 7 '([1 2 3] [4 [5 6 [7]]])
- reader macro(s)¶
Shorthand tag macro for constructing slices using Python's sugared form.
Examples
=> #s 1:4:2 (slice 1 4 2) => (get [1 2 3 4 5] #s 2::2) [3 5]
Numpy makes use of
Ellipsis
in its slicing semantics so they can also be constructed with this macro in their sugared...
form.=> #s ... Ellipsis
Slices can technically also contain strings (something pandas makes use of when slicing by string indices) and because Hy allows colons in identifiers, to construct these slices we have to use the form
(...)
:=> #s("colname" 1 2) (slice "colname" 1 2)
- (walk inner outer form)¶
walk
traversesform
, an arbitrary data structure. Appliesinner
to each element of form, building up a data structure of the same type. Appliesouter
to the result.Examples
=> (import hyrule.contrib.walk [walk]) => (setv a '(a b c d e f)) => (walk ord (fn [x] x) a) '(97 98 99 100 101 102)
=> (walk ord (fn [x] (get x 0)) a) 97
control
— Control structures¶
- macro(block #* body)¶
A macro that allows you to jump outside of a list of forms, like the Common Lisp special operator of the same name. The body forms are executed until
(block-ret VALUE)
is reached. The block returnsVALUE
, or the value of the last form, if execution reached the end instead of being terminated byblock-ret
.VALUE
is optional and defaults toNone
. One use ofblock
is to jump out of nested loops:(block (for [x (range 5)] (setv y x) (while y (print x y) (when (and (= x 3) (= y 1)) (block-ret)) (-= y 1))))
Blocks can be named by using a literal keyword or
None
as the first body form. Then you can use(block-ret-from NAME VALUE)
to specify which block to jump out of in a nested sequence of blocks:(setv x "") (block :a (block :b (block :c (+= x "p") (block-ret-from :b) (+= x "q")) (+= x "r")) (+= x "s")) (print x) ; => "ps"
An anonymous block is treated as being named
None
, and(block-ret)
is actually short for(block-ret-from None)
.Block names are matched lexically at the time
block
is macro-expanded.(block-ret-from :foo)
outside of a block named:foo
is an error. Inner blocks names shadow outer blocks of the same name, soblock-ret
will apply to the innermost of a series of nested anonymous blocks.There are no macros or functions named
block-ret
orblock-ret-from
, since these forms are processed entirely byblock
.block-ret
andblock-ret-from
should not be confused with Hy's built-inreturn
, which produces a true Python return statement.block
is implemented with exception-handling rather than functions, so it doesn't create a new scope asfn
anddefn
do.
- macro(branch tester #* rest)¶
Evaluate a test form with the symbol
it
bound to each of several case forms in turn. If the result is true, the result form associated with the matching case is evaluated and returned; no later cases are evaluated. If no case matches, returnNone
. The general syntax is:(branch TEST CASE-1 RESULT-1 CASE-2 RESULT-2 …)
For example,
(branch (in (.lower my-char) it) "aeiou" "it's a vowel" "xyz" "it's one of those last few")
is equivalent to
(cond (in (.lower my-char) "aeiou") "it's a vowel" (in (.lower my-char) "xyz") "it's one of those last few")
If you miss Common Lisp's
typecase
macro, here's how you can usebranch
to branch by type in the same way:(branch (isinstance my-value it) str "It's a string" bytes "It's a bytes object" #(int float) "It's numeric")
A case form that is exactly the symbol
else
is treated specially. In this case, the test form isn't evaluated, and is treated as if it returned true.(branch (= it my-value) True "Correct" False "Wrong" else "Not actually Boolean")
branch
won't complain if you add more cases after theelse
case, even additionalelse
cases, but they'll all be unreachable.If there are no case forms, the test form won't be evaluated, so the whole
branch
is a no-op.ebranch
is a convenience macro for when you want the no-match case to raise an error.case
andecase
are convenience macros for the special case of checking equality against a single test value.
- macro(case key #* rest)¶
Like the Common Lisp macro of the same name. Evaluate the first argument, called the key, and compare it with
=
to each of several case forms in turn. If the key is equal to a case, the associated result form is evaluated and returned; no later cases are evaluated. If no case matches, returnNone
. The general syntax is:(case KEY CASE-1 RESULT-1 CASE-2 RESULT-2 …)
For example, you could translate direction names to vectors like this:
(case direction "north" [ 0 1] "south" [ 0 -1] "east" [ 1 0] "west" [-1 0])
Thus,
(case KEY …)
is equivalent to(branch (= it KEY) …)
, exceptKEY
is evaluated exactly once, regardless of the number of cases.Like
branch
,case
treats the symbolelse
as a default case, and it has an error-raising version,ecase
.case
can't check for collection membership like the Common Lisp version; for that, use(branch (in KEY it) …)
. It also can't pattern-match; for that, seematch
.
- macro(cfor f #* generator)¶
syntactic sugar for passing a
generator
expression to the callablef
Its syntax is the same as generator expression, but takes a function
f
that the generator will be immedietly passed to. Equivalent to(f (gfor ...))
.Examples:
- ::
=> (cfor tuple x (range 10) :if (% x 2) x) #(1 3 5 7 9)
The equivalent in python would be:
>>> tuple(x for x in range(10) if x % 2)
Some other common functions that take iterables:
=> (cfor all x [1 3 8 5] (< x 10)) True => (with [f (open "AUTHORS")] ... (cfor max ... author (.splitlines (f.read)) ... :setv name (.group (re.match r"\* (.*?) <" author) 1) ... :if (name.startswith "A") ... (len name))) 20 ;; The number of characters in the longest author's name that starts with 'A'
- macro(defmain args #* body)¶
Define a function to be called when
__name__
equals"__main__"
(see__main__
).args
is the function's lambda list, which will be matched againstsys.argv
. Recall that the first element ofsys.argv
is always the name of the script being invoked, whereas the rest are command-line arguments. Ifargs
is[]
, this will be treated like[#* _]
, so any command-line arguments (and the script name) will be allowed, but ignored.If the defined function returns an
int
,sys.exit()
is called with that integer as the return code.If you want fancy command-line arguments, you can use the standard Python module
argparse
in the usual way, becausedefmain
doesn't changesys.argv
. See alsoparse-args
.(import argparse) (defmain [] (setv parser (argparse.ArgumentParser)) (.add-argument parser "STRING" :help "string to replicate") (.add-argument parser "-n" :type int :default 3 :help "number of copies") (setv args (.parse-args parser)) (print (* args.STRING args.n)) 0)
- macro(do-n count-form #* body)¶
Execute body a number of times equal to count-form and return
None
. (To collect return values, uselist-n
instead.)The macro is implemented as a
for
loop over arange
call, with the attendent consequences for negative counts,break
, etc. As an exception, if the count is Inf, the loop is run over an infinite iterator instead.=> (do-n 3 (print "hi")) hi hi hi
- macro(ebranch tester #* rest)¶
As
branch
, but if no case matches, raiseValueError
instead of returningNone
. The name is an abbreviation for "error branch".
- macro(ecase key #* rest)¶
As
case
, but if no case matches, raiseValueError
instead of returningNone
.
- macro(lif #* args)¶
A "Lispy if" similar to
if
andcond
. Its most notable property is that it tests the condition with(is-not condition None)
instead of(bool condition)
, so values such as the integer 0, the empty string, andFalse
are considered true, not false. The general syntax is(lif condition1 result1 condition2 result2 … else-value)
which is equivalent to
(cond (is-not condition1 None) result1 (is-not condition2 None) result2 … True else-value)
When no condition matches and there is no else-value, the result is
None
.
- macro(list-n count-form #* body)¶
Like
do-n
, but the results are collected into a list.=> (setv counter 0) => (list-n 5 (+= counter 1) counter) [1 2 3 4 5]
- macro(loop bindings #* body)¶
The loop/recur macro allows you to construct functions that use tail-call optimization to allow arbitrary levels of recursion.
loop
establishes a recursion point. Withloop
,recur
rebinds the variables set in the recursion point and sends code execution back to that recursion point. Ifrecur
is used in a non-tail position, an exception is raised. which causes chaos.Usage:
(loop bindings #* body)
Examples
=> (require hyrule.contrib.loop [loop]) => (defn factorial [n] ... (loop [[i n] [acc 1]] ... (if (= i 0) ... acc ... (recur (dec i) (* acc i))))) => (factorial 1000)
destructure
— Macros for destructuring collections¶
This module is heavily inspired by destructuring from Clojure and provides very similar semantics. It provides several macros that allow for destructuring within their arguments.
Introduction¶
Destructuring allows one to easily peek inside a data structure and assign names to values within. For example,
(setv+ {[{name :name [weapon1 weapon2] :weapons} :as all-players] :players
map-name :map
:keys [tasks-remaining tasks-completed]}
data)
would be equivalent to
(setv map-name (.get data ':map)
tasks-remaining (.get data ':tasks-remaining)
tasks-completed (.get data ':tasks-completed)
all-players (.get data ':players)
name (.get (get all-players 0) ':name)
weapon1 (get (.get (get all-players 0) ':weapons) 0)
weapon2 (get (.get (get all-players 0) ':weapons) 1))
where data
might be defined by
(setv data {:players [{:name Joe :weapons [:sword :dagger]}
{:name Max :weapons [:axe :crossbow]}]
:map "Dungeon"
:tasks-remaining 4})
This is similar to unpacking iterables in Python, such as a, *b, c = range(10)
, however it also works on dictionaries, and has several special options.
Warning
Variables which are not found in the expression are silently set to None
if no default value is specified. This is particularly important with defn+
and fn+
.
(defn+ some-function [arg1
{subarg2-1 "key"
:or {subarg2-1 20}
:as arg2}
[subarg3-1
:& subargs3-2+
:as arg3]]
{"arg1" arg1 "arg2" arg2 "arg3" arg3
"subarg2-1" subarg2-1 "subarg3-1" subarg3-1 "subargs3-2+" subargs3-2+})
(some-function 1 {"key" 2} [3 4 5])
; => {"arg1" 1 "arg2" {"key" 2} "arg3" [3 4 5]
; "subarg2-1" 2 "subarg3-1" 3 "subargs3-2+" [4 5]}
(some-function 1 2 [])
; => {"arg1" 1 "arg2" None "arg3" []
; "subarg2-1" 20 "subarg3-1" None "subargs3-2+" []}
(some-function)
; => {"arg1" None "arg2" None "arg3" None
; "subarg2-1" 20 "subarg3-1" None "subargs3-2+" None}
Note that variables with a default value from an :or
special option will fallback to their default value instead of being silently set to None
.
Patterns¶
Dictionary Pattern¶
Dictionary patterns are specified using dictionaries, where the keys corresponds to the symbols which are to be bound, and the values correspond to which key needs to be looked up in the expression for the given symbol.
(setv+ {a :a b "b" c #(1 0)} {:a 1 "b" 2 #(1 0) 3})
[a b c] ; => [1 2 3]
The keys can also be one of the following 4 special options: :or
, :as
, :keys
, :strs
.
:or
takes a dictionary of default values.:as
takes a variable name which is bound to the entire expression.:keys
takes a list of variable names which are looked up as keywords in the expression.:strs
is the same as:keys
but uses strings instead.
The ordering of the special options and the variable names doesn't matter, however each special option can be used at most once.
(setv+ {:keys [a b] :strs [c d] :or {b 2 d 4} :as full} {:a 1 :b 2 "c" 3})
[a b c d full] ; => [1 2 3 4 {:a 1 :b 2 "c" 3}]
Variables which are not found in the expression are set to None
if no default value is specified.
List Pattern¶
List patterns are specified using lists. The nth symbol in the pattern is bound to the nth value in the expression, or None
if the expression has fewer than n values.
There are 2 special options: :&
and :as
.
:&
takes a pattern which is bound to the rest of the expression. This pattern can be anything, including a dictionary, which allows for keyword arguments.:as
takes a variable name which is bound to the entire expression.
If the special options are present, they must be last, with :&
preceding :as
if both are present.
(setv+ [a b :& rest :as full] (range 5))
[a b rest full] ; => [0 1 [2 3 4] [0 1 2 3 4]]
(setv+ [a b :& {:keys [c d] :or {c 3}}] [1 2 :d 4 :e 5]
[a b c d] ; => [1 2 3 4]
Note that this pattern calls list
on the expression before binding the variables, and hence cannot be used with infinite iterators.
Iterator Pattern¶
Iterator patterns are specified using round brackets. They are the same as list patterns, but can be safely used with infinite generators. The iterator pattern does not allow for recursive destructuring within the :as
special option.
- macro(defn+ fn-name args #* doc+body)¶
Define function fn-name with destructuring within args.
Note that #* etc have no special meaning and are intepretted as any other argument.
- macro(defn/a+ fn-name args #* doc+body)¶
Async variant of
defn+
.
- macro(dict=: #* pairs)¶
Destructure into dict
Same as
setv+
, except returns a dictionary with symbols to be defined, instead of actually declaring them.
- macro(fn+ args #* body)¶
Return anonymous function with destructuring within args
Note that *, /, etc have no special meaning and are intepretted as any other argument.
- macro(fn/a+ args #* body)¶
Async variant of
fn+
.
- macro(let+ args #* body)¶
let macro with full destructuring with args
- macro(setv+ #* pairs)¶
Assignment with destructuring for both mappings and iterables.
Destructuring equivalent of
setv
. Binds symbols found in a pattern using the corresponding expression.Examples
(setv+ pattern_1 expression_1 ... pattern_n expression_n)
iterables
— Tools for iterable objects¶
- (butlast coll)¶
Returns an iterator of all but the last item in coll.
Examples
=> (list (butlast (range 10))) [0 1 2 3 4 5 6 7 8]
=> (list (butlast [1])) []
=> (list (butlast [])) []
=> (import itertools [count islice]) => (list (islice (butlast (count 10)) 0 5)) [10 11 12 13 14]
- (coll? coll)¶
Returns
True
if x inherits fromIterable
but notstr
orbytes
.Examples
=> (coll? [1 2 3 4]) True
=> (coll? {"a" 1 "b" 2}) True
=> (coll? "abc") False
- (distinct coll)¶
Return a generator from the original collection coll with no duplicates.
Examples
=> (list (distinct [ 1 2 3 4 3 5 2 ])) [1 2 3 4 5]
=> (list (distinct [])) []
=> (list (distinct (iter [ 1 2 3 4 3 5 2 ]))) [1 2 3 4 5]
- (drop-last n coll)¶
Return a sequence of all but the last n elements in coll.
Returns an iterator of all but the last n items in coll. Raises
ValueError
if n is negative.Examples
=> (list (drop-last 5 (range 10 20))) [10 11 12 13 14]
=> (list (drop-last 0 (range 5))) [0 1 2 3 4]
=> (list (drop-last 100 (range 100))) []
=> (import itertools [count islice]) => (list (islice (drop-last 100 (count 10)) 5)) [10 11 12 13 14]
- (flatten coll)¶
Return a single flat list expanding all members of coll.
Returns a single list of all the items in coll, by flattening all contained lists and/or tuples.
Examples
=> (flatten [1 2 [3 4] 5]) [1 2 3 4 5]
=> (flatten ["foo" #(1 2) [1 [2 3] 4] "bar"]) ["foo" 1 2 1 2 3 4 "bar"]
- (rest coll)¶
Get all the elements of coll, except the first.
rest
takes the given collection and returns an iterable of all but the first element.Examples
=> (list (rest (range 10))) [1 2 3 4 5 6 7 8 9]
Given an empty collection, it returns an empty iterable:
=> (list (rest [])) []
macrotools
— Tools for writing and handling macros¶
- reader macro(/)¶
Sugar for
hy.I
, to access modules without needing to explicitly import them first. Unlikehy.I
,#/
cannot be used if the module name is only known at runtime.Examples
Access modules and their elements directly by name:
=> (type #/ re) <class 'module'> => #/ os.curdir "." => (#/ re.search r"[a-z]+" "HAYneedleSTACK") <re.Match object; :span #(3 9) :match "needle">
Like
hy.I
, separate submodule names with/
:=> (#/ os/path.basename "path/to/file") "file"
- macro(defmacro/g! name args #* body)¶
Like defmacro, but symbols prefixed with 'g!' are gensymed.
defmacro/g!
is a special version ofdefmacro
that is used to automatically generategensyms
for any symbol that starts withg!
.For example,
g!a
would become(hy.gensym "a")
.
- macro(defmacro! name args #* body)¶
Like defmacro/g!, with automatic once-only evaluation for 'o!' params.
Such 'o!' params are available within body as the equivalent 'g!' symbol.
Examples
=> (defn expensive-get-number [] (print "spam") 14) => (defmacro triple-1 [n] `(+ ~n ~n ~n)) => (triple-1 (expensive-get-number)) ; evals n three times spam spam spam 42
=> (defmacro/g! triple-2 [n] `(do (setv ~g!n ~n) (+ ~g!n ~g!n ~g!n))) => (triple-2 (expensive-get-number)) ; avoid repeats with a gensym spam 42
=> (defmacro! triple-3 [o!n] `(+ ~g!n ~g!n ~g!n)) => (triple-3 (expensive-get-number)) ; easier with defmacro! spam 42
- (macroexpand-all form ast-compiler)¶
Recursively performs all possible macroexpansions in form, using the
require
context ofmodule-name
. macroexpand-all assumes the calling module's context if unspecified.
- macro(with-gensyms args #* body)¶
Execute body with args as bracket of names to gensym for use in macros.
with-gensym
is used to generate a set ofgensyms
for use in a macro. The following code:Examples
=> (with-gensyms [a b c] ... ...)
expands to:
=> (do ... (setv a (hy.gensym) ... b (hy.gensym) ... c (hy.gensym)) ... ...)
pprint
— Pretty-printing data structures¶
hyrule.pprint
is a port of python's built-in pprint
that can pretty
print objects using Hy syntax.
Hy pprint
leverages hy.repr
for much of it's pretty printing and
therefor can be extended to work with arbitrary types using
hy.repr-register
. Like Python's pprint
and hy.repr
, Hy pprint
attempts to maintain round-trippability of it's input where possible. Unlike
Python, however, Hy does not have string literal concatenation,
which is why strings and bytestrings are broken up using the form (+ ...)
.
The API for Hy pprint
is functionally identical to Python's pprint
module, so be sure to reference the Python pprint
docs for more on how to use the module's various methods and arguments.
The differences that do exist are as follows:
isreadable
becomesreadable?
isrecursive
becomesrecursive?
Passing
False
to thePrettyPrinter
argsort-dicts
in Python versions < 3.8 will raise aValueError
- class (PrettyPrinter [indent 1] [width 80] depth stream * [compact False] [sort-dicts True])¶
Handle pretty printing operations onto a stream using a set of configured parameters.
- Parameters:
indent -- Number of spaces to indent for each level of nesting.
width -- Attempted maximum number of columns in the output.
depth -- The maximum depth to print out nested structures.
stream -- The desired output stream. If omitted (or false), the standard output stream available at construction will be used.
compact -- If true, several items will be combined in one line.
sort-dicts -- If True, dict keys are sorted. (only available for python >= 3.8)
- (pformat object #* args #** kwargs)¶
Format a Python object into a pretty-printed representation.
- (pp object [sort-dicts False] #* args #** kwargs)¶
Pretty-print a Python object
- (pprint object #* args #** kwargs)¶
Pretty-print a Python object to a stream [default is sys.stdout].
Examples
=> (pprint {:name "Adam" :favorite-foods #{:apple :pizza} :bio "something very important"} :width 20) {:name "Adam" :bio (+ "something " "very " "important") :favorite-foods #{:apple :pizza}}
- (readable? object)¶
Determine if (saferepr object) is readable by (hy.eval).
- (recursive? object)¶
Determine if object requires a recursive representation.
- (saferepr object)¶
Version of (repr) which can handle recursive data structures.
sequences
— Lazy, indexable iterables¶
The sequences module contains a few macros for declaring sequences that are evaluated only as much as the client code requires. Unlike generators, they allow accessing the same element multiple times. They cache calculated values, and the implementation allows for recursive definition of sequences without resulting in recursive computation.
The simplest sequence can be defined as (seq [n] n)
. This defines a sequence
that starts as [0 1 2 3 ...]
and continues forever. In order to define a
finite sequence, you need to call end-sequence
to signal the end of the
sequence:
(seq [n]
"sequence of 5 integers"
(cond (< n 5) n
True (end-sequence)))
This creates the following sequence: [0 1 2 3 4]
. For such a sequence,
len
returns the amount of items in the sequence and negative indexing is
supported. Because both of these require evaluating the whole sequence, calling
one on an infinite sequence would take forever (or at least until available
memory has been exhausted).
Sequences can be defined recursively. For example, the Fibonacci sequence could be defined as:
(defseq fibonacci [n]
"infinite sequence of fibonacci numbers"
(cond (= n 0) 0
(= n 1) 1
True (+ (get fibonacci (- n 1))
(get fibonacci (- n 2)))))
This results in the sequence [0 1 1 2 3 5 8 13 21 34 ...]
.
- macro(defseq seq-name param #* seq-code)¶
Creates a sequence defined in terms of
n
and assigns it to a given name.Examples
=> (defseq numbers [n] n)
- (end-sequence)¶
Signals the end of a sequence when an iterator reaches the given point of the sequence.
Internally, this is done by raising
IndexError
, catching that in the iterator, and raisingStopIteration
Examples
=> (seq [n] (if (< n 5) n (end-sequence)))
- macro(seq param #* seq-code)¶
Creates a sequence defined in terms of
n
.Examples
=> (seq [n] (* n n))
misc
— Everything else¶
- macro(comment #* body)¶
Ignores body and always expands to None
The
comment
macro ignores its body and always expands toNone
. Unlike linewise comments, the body of thecomment
macro must be grammatically valid Hy, so the compiler can tell where the comment ends. Besides the semicolon linewise comments, Hy also has the#_
discard prefix syntax to discard the next form. This is completely discarded and doesn't expand to anything, not evenNone
.Examples
=> (print (comment <h1>Surprise!</h1> ... <p>You'd be surprised what's grammatically valid in Hy</p> ... <p>(Keep delimiters in balance, and you're mostly good to go)</p>) ... "Hy") None Hy
=> (print #_(comment <h1>Surprise!</h1> ... <p>You'd be surprised what's grammatically valid in Hy</p> ... <p>(Keep delimiters in balance, and you're mostly good to go)</p>)) ... "Hy") Hy
- (constantly value)¶
Create a new function that always returns value regardless of its input.
Create a new function that always returns the given value, regardless of the arguments given to it.
Examples
=> (setv answer (constantly 42)) => (answer) 42
=> (answer 1 2 3) 42
=> (answer 1 :foo 2) 42
- (dec n)¶
Decrement n by 1.
Returns one less than x. Equivalent to
(- x 1)
. RaisesTypeError
if x is not numeric.Examples
=> (dec 3) 2
=> (dec 0) -1
=> (dec 12.3) 11.3
- (inc n)¶
Increment n by 1.
Returns one more than x. Equivalent to
(+ x 1)
. RaisesTypeError
if x is not numeric.Examples
=> (inc 3) 4
=> (inc 0) 1
=> (inc 12.3) 13.3
- macro(of base #* args)¶
Shorthand for indexing for type annotations.
If only one arguments are given, this expands to just that argument. If two arguments are given, it expands to indexing the first argument via the second. Otherwise, the first argument is indexed using a tuple of the rest.
of
has three forms:(of T)
will simply becomeT
.(of T x)
will become(get T x)
.(of T x y ...)
(where the...
represents zero or more arguments) will become(get T #(x y ...))
.
Examples
=> (of str) str
=> (of List int) List[int]
=> (of Set int) Set[int]
=> (of Dict str str) Dict[str, str]
=> (of Tuple str int) Tuple[str, int]
=> (of Callable [int str] str) Callable[[int, str], str]
- (parse-args spec args #** parser-args)¶
Return arguments namespace parsed from args or
sys.argv
withargparse.ArgumentParser.parse_args()
according to spec.spec should be a list of arguments which will be passed to repeated calls to
argparse.ArgumentParser.add_argument()
. parser-args may be a list of keyword arguments to pass to theargparse.ArgumentParser
constructor.Examples
=> (parse-args [["strings" :nargs "+" :help "Strings"] ... ["-n" "--numbers" :action "append" :type int :help "Numbers"]] ... ["a" "b" "-n" "1" "-n" "2"] ... :description "Parse strings and numbers from args") Namespace(numbers=[1, 2], strings=['a', 'b'])
- macro(profile/calls #* body)¶
profile/calls
allows you to create a call graph visualization. Note: You must have Graphviz installed for this to work.Examples
=> (require hyrule.contrib.profile [profile/calls]) => (profile/calls (print "hey there"))
- macro(profile/cpu #* body)¶
Profile a bit of code
Examples
=> (require hyrule.contrib.profile [profile/cpu]) => (profile/cpu (print "hey there"))
hey there <pstats.Stats instance at 0x14ff320> 2 function calls in 0.000 seconds Random listing order was used ncalls tottime percall cumtime percall filename:lineno(function) 1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects} 1 0.000 0.000 0.000 0.000 {print}
- macro(smacrolet _hy-compiler bindings #* body)¶
symbol macro let.
Replaces symbols in body, but only where it would be a valid let binding. The bindings pairs the target symbol and the expansion form for that symbol
Examples
(smacrolet [b c] (defn foo [a [b 1]] (* b (+ a 1))) (* b (foo 7)))
Would compile to:
(defn foo [a [b 1]] (* b (+ a 1))) (* c (foo 7))
Notice that the
b
symbol defined by thedefn
remains unchanged as it is not a validlet
binding. Only the top levelb
sym has been replaced withc
- (xor a b)¶
Perform exclusive or between a and b.
xor
performs the logical operation of exclusive OR. It takes two arguments. If exactly one argument is true, that argument is returned. If neither is true, the second argument is returned (which will necessarily be false). Otherwise, when both arguments are true, the valueFalse
is returned.Examples
=> [(xor 0 0) (xor 0 1) (xor 1 0) (xor 1 1)] [0 1 1 False]
Contributing to Hyrule¶
Hyrule is open to contributions generally. It's intended to grow with the needs of Hy programmers. The primary criteria for adding a feature to Hyrule are:
It's not redundant with preexisting Python libraries.
It's of general interest to Hy programmers. In other words, it's not extremely specialized. Specialized code should go into a specialized library instead.
It takes at most an entire new file to implement. If your feature spans multiple files, it should probably have its own library instead.
Other than that, Hyrule's contribution guidelines and core development team are shared with Hy itself.