The Hyrule 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.

Hyrule's documentation can be read online on Read the Docs.

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(hyrule.%)

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(hyrule.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(hyrule.ap-each xs #* body)

Evaluate the body forms for each element it of xs and return None.

Examples

=> (ap-each [1 2 3] (print it))
1
2
3
macro(hyrule.ap-each-while xs form #* body)

As ap-each, but the form pred is run before the body forms on each iteration, and the loop ends if pred is false.

Examples

=> (ap-each-while [1 2 3 4 5 6] (< it 4) (print it))
1
2
3
macro(hyrule.ap-map form xs)

Create a generator like map() that yields each result of form evaluated with it bound to successive elements of xs.

Examples

=> (list (ap-map (* it 2) [1 2 3]))
[2 4 6]
macro(hyrule.ap-map-when predfn rep xs)

As ap-map, but the predicate function predfn (yes, that's a function, not an anaphoric form) is applied to each it, and the anaphoric mapping form rep 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(hyrule.ap-filter form xs)

The filter() equivalent of ap-map.

Examples

=> (list (ap-filter (> (* it 2) 6) [1 2 3 4 5]))
[4 5]
macro(hyrule.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(hyrule.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(hyrule.ap-first form xs)

Evaluate the predicate form for each element it of xs. When the predicate is true, stop and return it. If the predicate is never true, return None.

Examples

=> (ap-first (> it 5) (range 10))
6
macro(hyrule.ap-last form xs)

Usage: (ap-last form list)

Evaluate the predicate form for every element it of xs. Return the last element for which the predicate is true, or None if there is no such element.

Examples

=> (ap-last (> it 5) (range 10))
9
macro(hyrule.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 of xs, bind it to the second, and evaluate form.

  • Bind acc to the result, bind it to the third value of xs, and evaluate form again.

  • Bind acc to the result, and continue until xs is exhausted.

If initial-value is supplied, the process instead begins with acc set to initial-value and it set to the first element of xs.

Examples

=> (ap-reduce (+ it acc) (range 10))
45
macro(hyrule.ap-when test-form #* body)

As when, but the result of the test form is named it in the subsequent forms.

Examples

=> (import os)
=> (ap-when (.get os.environ "PYTHONPATH")
...   (print "Your PYTHONPATH is" it)
...   it)
macro(hyrule.ap-with form #* body)

As with, but the result of the form is named it in the subsequent forms.

Examples

=> (ap-with (open "/proc/cpuinfo")
...   (lfor line it line))

argmove — Macros for calls with unusual argument placement

macro(hyrule.-> 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(hyrule.->> 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(hyrule.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 -> and as-> 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(hyrule.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(hyrule.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 returns None.

macro(hyrule.ncut seq key1 #* keys)

N-Dimensional cut macro with shorthand slice notation.

Libraries like numpy and pandas 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 in ncut 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 an ncut expresion as an escape hatch to slice:

=> (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

Note

For more info on the capabilities of multiindex slicing, check with the respective library.

(hyrule.postwalk f form)

Performs depth-first, post-order traversal of form. Calls f on each sub-form, uses f '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]]]))
(hyrule.prewalk f form)

Performs depth-first, pre-order traversal of form. Calls f on each sub-form, uses f '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(hyrule.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)
(hyrule.walk inner outer form)

walk traverses form, an arbitrary data structure. Applies inner to each element of form, building up a data structure of the same type. Applies outer 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(hyrule.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 returns VALUE, or the value of the last form, if execution reached the end instead of being terminated by block-ret. VALUE is optional and defaults to None. One use of block 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, so block-ret will apply to the innermost of a series of nested anonymous blocks.

There are no macros or functions named block-ret or block-ret-from, since these forms are processed entirely by block. block-ret and block-ret-from should not be confused with Hy's built-in return, which produces a true Python return statement. block is implemented with exception-handling rather than functions, so it doesn't create a new scope as fn and defn do.

macro(hyrule.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, return None. 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 use branch 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 the else case, even additional else 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 and ecase are convenience macros for the special case of checking equality against a single test value.

macro(hyrule.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, return None. 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) …), except KEY is evaluated exactly once, regardless of the number of cases.

Like branch, case treats the symbol else 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, see match.

macro(hyrule.cfor f #* generator)

syntactic sugar for passing a generator expression to the callable f

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(hyrule.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 against sys.argv. Recall that the first element of sys.argv is always the name of the script being invoked, whereas the rest are command-line arguments. If args 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, because defmain doesn't change sys.argv. See also parse-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(hyrule.do-n count-form #* body)

Execute body a number of times equal to count-form and return None. (To collect return values, use list-n instead.)

The macro is implemented as a for loop over a range 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(hyrule.ebranch tester #* rest)

As branch, but if no case matches, raise ValueError instead of returning None. The name is an abbreviation for "error branch".

macro(hyrule.ecase key #* rest)

As case, but if no case matches, raise ValueError instead of returning None.

macro(hyrule.lif #* args)

A "Lispy if" similar to if and cond. 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, and False 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(hyrule.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(hyrule.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. With loop, recur rebinds the variables set in the recursion point and sends code execution back to that recursion point. If recur 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)
macro(hyrule.unless test #* body)

Shorthand for (when (not test) …), i.e., (if (not test) (do …) None). See if.

(unless ok
  (print "Failed.")
  (exit 1))

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(hyrule.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(hyrule.defn/a+ fn-name args #* doc+body)

Async variant of defn+.

macro(hyrule.dict=: #* pairs)

Destructure into dict

Same as setv+, except returns a dictionary with symbols to be defined, instead of actually declaring them.

macro(hyrule.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(hyrule.fn/a+ args #* body)

Async variant of fn+.

macro(hyrule.let+ args #* body)

let macro with full destructuring with args

macro(hyrule.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

(hyrule.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]
(hyrule.coll? coll)

Returns True if x inherits from Iterable but not str or bytes.

Examples

=> (coll? [1 2 3 4])
True
=> (coll? {"a" 1 "b" 2})
True
=> (coll? "abc")
False
(hyrule.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]
(hyrule.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]
(hyrule.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"]
(hyrule.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 []))
[]
(hyrule.thru a b [step 1])

A doubly inclusive version of range. It takes the same arguments as range, but includes the endpoint (given a compatible start point and step size).

(thru 3)
  ; => [0 1 2 3]
(thru 0 10 2)
  ; => [0 2 4 6 8 10]
(thru 0 9 2)
  ; => [0 2 4 6 8]

macrotools — Tools for writing and handling macros

reader macro(hyrule./)

Sugar for hy.I, to access modules without needing to explicitly import them first. Unlike hy.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(hyrule.defmacro-kwargs name params #* body)

Define a macro that can take keyword arguments. When the macro is called, match-fn-params is used to match the arguments against params, and the parameters are assigned to local variables that can be used in the macro body.

(defmacro-kwargs do10times [form [print-iteration 'False]]
  (setv i (hy.gensym))
  `(for [~i (range 10)]
    (when ~print-iteration
      (print "Now on iteration:" ~i))
    ~form))

(setv x [])
(do10times
  (.append x 1))
; Nothing is printed.
(do10times
  :print-iteration (> (len x) 17)
  (.append x 1))
; Iterations 8 and 9 are printed.
macro(hyrule.defmacro/g! name args #* body)

Like defmacro, but symbols prefixed with 'g!' are gensymed.

defmacro/g! is a special version of defmacro that is used to automatically generate gensyms for any symbol that starts with g!.

For example, g!a would become (hy.gensym "a").

macro(hyrule.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
(hyrule.macroexpand-all form ast-compiler)

Recursively performs all possible macroexpansions in form, using the require context of module-name. macroexpand-all assumes the calling module's context if unspecified.

(hyrule.map-model x f)

Recursively apply a callback to some code. The unary function f is called on the object x, converting it to a model first if it isn't one already. If the return value isn't None, it's converted to a model and used as the result. But if the return value is None, and x isn't a sequential model, then x is used as the result instead.

(defn f [x]
  (when (= x 'b)
    'B))
(map-model 'a f)  ; => 'a
(map-model 'b f)  ; => 'B

Recursive descent occurs when f returns None and x is sequential. Then map-model is called on all the elements of x and the results are bound up in the same model type as x.

(map-model '[a [b c] d] f)  ; => '[a [B c] d]

The typical use of map-model is to write a macro that replaces models of a selected kind, however deeply they're nested in a tree of models.

(defmacro lowercase-syms [#* body]
  "Evaluate `body` with all symbols downcased."
  (hy.I.hyrule.map-model `(do ~@body) (fn [x]
    (when (isinstance x hy.models.Symbol)
      (hy.models.Symbol (.lower (str x)))))))
(lowercase-syms
  (SETV FOO 15)
  (+= FOO (ABS -5)))
(print foo)  ; => 20

That's why the parameters of map-model are backwards compared to map: in user code, x is typically a symbol or other simple form whereas f is a multi-line anonymous function.

(hyrule.match-fn-params args params)

Match an iterable of arguments against a parameter list in the style of a defn lambda list. The parameter-list syntax here is somewhat restricted: annotations are forbiddden, / and * aren't recognized, and nothing is allowed after #* args other than #** kwargs. Return a dictionary of the parameters and their values.

(match-fn-params
  [1 :foo "x"]
  '[a [b 2] [c 3] #* args #** kwargs])
; => {"a" 1  "b" 2  "c" 3  "args" #()  "kwargs" {"foo" "x"}}

If a default argument is a model, it's evaluated. The evaluation occurs in a minimal environment, with no access to surrounding global or local Python-level objects or macros. If this is too restrictive, use None as the default value and compute the real default value in other code.

This function exists mostly to implement defmacro-kwargs.

macro(hyrule.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 of gensyms 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 becomes readable?

  • isrecursive becomes recursive?

  • Passing False to the PrettyPrinter arg sort-dicts in Python versions < 3.8 will raise a ValueError

class (hyrule.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)

(hyrule.pformat object #* args #** kwargs)

Format a Python object into a pretty-printed representation.

(hyrule.pp object [sort-dicts False] #* args #** kwargs)

Pretty-print a Python object

(hyrule.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}}
(hyrule.readable? object)

Determine if (saferepr object) is readable by (hy.eval).

(hyrule.recursive? object)

Determine if object requires a recursive representation.

(hyrule.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(hyrule.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)

(hyrule.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 raising StopIteration

Examples

=> (seq [n] (if (< n 5) n (end-sequence)))
macro(hyrule.seq param #* seq-code)

Creates a sequence defined in terms of n.

Examples

=> (seq [n] (* n n))

misc — Everything else

macro(hyrule.comment #* body)

Ignores body and always expands to None

The comment macro ignores its body and always expands to None. Unlike linewise comments, the body of the comment 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 even None.

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
(hyrule.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
(hyrule.dec n)

Decrement n by 1.

Returns one less than x. Equivalent to (- x 1). Raises TypeError if x is not numeric.

Examples

=> (dec 3)
2
=> (dec 0)
-1
=> (dec 12.3)
11.3
(hyrule.inc n)

Increment n by 1.

Returns one more than x. Equivalent to (+ x 1). Raises TypeError if x is not numeric.

Examples

=> (inc 3)
4
=> (inc 0)
1
=> (inc 12.3)
13.3
(hyrule.parse-args spec args #** parser-args)

Return arguments namespace parsed from args or sys.argv with argparse.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 the argparse.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(hyrule.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(hyrule.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}
(hyrule.sign x)

Return -1 for negative x, 1 for positive x, and 0 for x equal to 0. The implementation is exactly

(cond
  (< x 0) -1
  (> x 0)  1
  (= x 0)  0
  True     (raise TypeError))

with the corresponding consequences for special cases like negative zero and NaN.

macro(hyrule.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 the defn remains unchanged as it is not a valid let binding. Only the top level b sym has been replaced with c

(hyrule.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 value False 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.