Hyrule v0.6.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 ).


anaphoric — Anaphoric macros

Hyrule's anaphoric macros can make functional programming more concise and easier to read. An anaphoric macro assigns values to designated symbols (typically it) that may be used in the code passed to the macro.

reader macro(%)

Define an anonymous function with an implicit parameter list, similarly to Clojure's literal anonymous functions. A single form is read and interpreted as the body of the function. The first parameter is named %1, the second %2, and so on.

(list (map #%(+ %1 3) (range 5)))  ; => [3 4 5 6 7]

The number of parameters is set by the largest %i symbol that appears in the code:

(setv f #%(+ %1 %3))
(f 1 10 100)  ; => 101

Use %* for a #* args parameter and %** for a #** kwargs parameter:

(#%[%1 %*] 1 2 3)  ; => [1 #(2 3)]

The implementation searches the input recursively for %-symbols and doesn't attempt to detect nested #% calls, so nested calls are of limited value.

macro(ap-if test true-value false-value)

As if, but the result of the test form is named it in the subsequent forms, and the else-clause is optional.

(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 of xs and return None.

(ap-each [1 2 3] (print it))
macro(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.

(ap-each-while [1 2 3 4 5 6] (< it 4) (print it))
  ; Prints only 1, 2, and 3
macro(ap-map form xs)

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

(list (ap-map (* it 2) [1 2 3]))  ; => [2 4 6]
macro(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.

(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 of ap-map.

(list (ap-filter (> (* it 2) 6) [1 2 3 4 5]))
  ; => [4 5]
macro(ap-reject form xs)

Shorthand for (ap-filter (not form) xs). See ap-filter.

macro(ap-dotimes n #* body)

Shorthand for (ap-each (range n) body…). See ap-each.

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

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

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.

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

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

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

(setv variable -1)
(ap-when (+ variable 2)
  (setv result it)
  (print it))
(print result)
macro(ap-with form #* body)

Shorthand for (with [it form] body…). See with.

argmove — Macros for calls with unusual argument placement

This module provides macros similar to Clojure's threading macros, also known as arrow macros.

macro(-> head #* args)

Evaluate the first expression in args with head inserted as the second argument, then the next expression with that result as its second argument, then the next expression with that result as its second argument, and so on. In other words,

(-> x (foo a b) (bar c d))

is equvalent to

  (setv value x)
  (setv value (foo value a b))
  (setv value (bar value c d))

but without actually using an intermediate variable. For example:

(-> (+ 4 6) (print 5))
  ; Prints "10 5"

Arguments of the form .foo are automatically expanded to (.foo):

(-> " hello " .upper .strip)  ; => "HELLO"

And if an argument arg isn't an expression, it's expanded to (arg):

(-> "0" int bool)  ; => False
macro(->> head #* args)

As ->, but each result is placed as the last argument of each expression instead of the second.

(->> "a" (+ "b" "c"))  ; => "bca"
macro(as-> head name #* rest)

Assign head to the symbol name and evaluate each form in rest. After each evaluation, the result is reassigned to name.

(as-> "a" it
      (+ "b" it "c")
      (.upper it)
      (print it))
  ; Prints "BAC".

Thus as-> is analogous to e.g. ->, but the threading point is set per form by name, rather than always being the second argument.

Here's a more complex example, which prints a sentence from a Project Gutenberg e-book:

  urllib.request [urlopen])

(as-> (urlopen "https://www.gutenberg.org/ebooks/219.txt.utf-8") it
      (.read it)
      (.decode it "UTF-8")
      (re.search r"\. ([^.]+ paw-strokes [^.]+\.)" it)
      (.group it 1)
      (.replace it "\r\n" " ")
      (print it))
macro(some-> head #* args)

As ->, but if an intermediate result is None, all further forms are ignored.

(defn lookup [char]
  (.get {"a" 1 "b" 2} char))
(some-> "q" lookup (print "is the value"))
  ; Prints nothing, since `(lookup "q")` returns `None`.
macro(doto form #* expressions)

As ->, but instead of the return value of each expression being passed to the next, a single object (obtained by evaluating the orignial first argument form) is used every time. In other words,

(doto x (foo a b) (bar c d))

is equvalent to

  (setv value x)
  (foo value a b)
  (bar value c d)

Thus, doto is useful when value is an object that gets mutated by each expression:

(doto [] (.append 1) (.append 2) (.reverse))  ; => [2 1]

collections — Tools for data structures

(assoc coll #* kvs)

Associate key-value pairs by assigning to elements of coll. Thus,

(assoc coll  k1 v1  k2 v2  k3 v3)

is equivalent to

(setv (get coll k1) v1)
(setv (get coll k2) v2)
(setv (get coll k3) v3)

except coll is evaluated exactly once. Notice that this implies the return value is None, not coll or one of the newly assigned elements.

macro(ncut seq key1 #* keys)

Shorthand for (get seq …) with various kinds of complex indices as used by NumPy and pandas. Thus ncut provides an n-dimensional equivalent of cut.

Simple uses are identical to get:

(ncut x i)  ; (get x i)

When multiple arguments are provided, they're combined into a tuple:

(ncut x i1 i2)  ; (get x #(i1 i2))

A keyword, or a symbol containing a colon, is understood as shorthand for a slice of integer literals. An omitted integer is understood as None, as in Python's own slicing syntax.

(ncut x :)      ; (get x (slice None None))
(ncut x 1:2)    ; (get x (slice 1 2))
(ncut x 1:)     ; (get x (slice 1 None))
(ncut x 1:2:3)  ; (get x (slice 1 2 3))
(ncut x ::2)    ; (get x (slice None None 2))

An expression of the form (: ) is understood as (slice …):

(ncut x (: a b))  ; (get x (slice a b))

Here are some executable examples:

(ncut (list (range 10)) 2:8:2)
  ; => [2 4 6]

(import numpy :as np)
(setv a (.reshape (np.arange 36) #(6 6)))
(ncut a 3: #(0 2 5))
  ; => array([[18, 20, 23],
  ;           [24, 26, 29],
  ;           [30, 32, 35]])
(ncut a ... 0)
  ; => array([ 0,  6, 12, 18, 24, 30])

(import pandas :as pd)
(setv df (pd.DataFrame [[1 2 3 4] [5 6 7 8]]
                       :columns (list "ABCD")))
(ncut df.loc : ["B" "A"])
  ; =>    B  A
  ;    0  2  1
  ;    1  6  5
(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.


=> (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)
'([1 2 3] [4 [5 6 [7]]]))
(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.


=> (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)
'([1 2 3] [4 [5 6 [7]]])
reader macro(s)

Read one form and interpret it like an index argument of ncut. The "s" stands for "slice".

(setv x (list (range 10)))
(get x #s 1:4:2)
  ; …is equivalent to…
(ncut x 1:4:2)
  ; …is equivalent to…
(get x (slice 1 4 2))
(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.


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

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

For example,

(branch (in (.lower my-char) it)
  "aeiou" "it's a vowel"
  "xyz"   "it's one of those last few")

is equivalent to

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

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(cfor f #* gfor-args)

Shorthand for (f (gfor …)). See gfor.

(cfor tuple
  x (range 10)
  :if (% x 2)
  x)           ; => #(1 3 5 7 9)
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 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.

(defmain [program-name argument]
  (print "Welcome to" program-name)
  (print "The answer is" (* (float argument) 2)))
macro(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"))
macro(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(ecase key #* rest)

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

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

  condition1 result1
  condition2 result2

which is equivalent to

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


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

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]}

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.


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}
                       :& 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+" []}

; => {"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.

Pattern types

Several kinds of patterns are understood.

Dictionary patterns

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 patterns

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 patterns

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.


(setv+ pattern_1 expression_1 ...  pattern_n expression_n)

iterables — Tools for iterable objects

(butlast coll)

Return an iterator of all but the last item in coll.

(list (butlast (range 5)))  ; => [0 1 2 3]

When coll is empty, the new iterator will also be empty.

(coll? x)

Return True if x inherits from collections.abc.Iterable but not str or bytes.

(coll? ["abc"])         ; True
(coll? {"a" 1 "b" 2})   ; True
(coll? "abc")           ; False
(distinct coll)

Return an iterator from the original iterable coll with no duplicates. Duplicates are detected by calling in on a set. Elements will be produced in order of their first appearance in coll.

(list (distinct [1 2 3 4 3 5 0 2]))  ; => [1 2 3 4 5 0]
(drop-last n coll)

Return an iterator of all but the last n elements in coll. n must be nonnegative.

(list (drop-last 3 (range 10)))  ; => [0 1 2 3 4 5 6]
(flatten coll)

Recurisvely collect all the elements and subelements of coll into a single list. coll? is used to decide whether objects should be descended into.

(flatten ["foo" #(1 2) [1 [2 3] 4] "bar"])
  ; => ["foo" 1 2 1 2 3 4 "bar"]
(rest coll)

Return an iterator of all the elements of coll excepting the first.

(list (rest (range 5)))  ; => [1 2 3 4]

When coll is empty, the new iterator will also be empty.

(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(/)

Read one identifier and interpret it as a one-shot import in the same way as hy.I.

#/ os.curdir
  ; hy.I.os.curdir
  ; => "."
(#/ os/path.basename "path/to/file")
  ; (hy.I.os/path/basename "path/to/file")
  ; => "file"
macro(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))

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

As defmacro, but with automatic gensyms. For each symbol in the body beginning with "g!", a gensym is implicitly created and assigned to that symbol.

(defmacro! m []
     (setv ~g!accum [])
     (for [x (range 3)]
       (.append ~g!accum x))
(m)  ; => [0 1 2]

Furthermore, for each parameter of the macro beginning with "o!", the argument is evaluated exactly once, and the corresponding g! symbol is bound to a gensym to hold the value.

(defmacro! m [o!x]
  `(+ ~g!x ~g!x ~g!x))
(setv l (list "abc"))
(m (.pop l))  ; => "ccc"
(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.

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

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

  [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(with-gensyms args #* body)

Evaluate body with each name in args (a list of symbols) bound to a gensym. The syntax

(with-gensyms [a b c]

is equivalent to

  (setv a (hy.gensym 'a))
  (setv b (hy.gensym 'b))
  (setv c (hy.gensym 'c))

pprint — Pretty-printing data structures

Hyrule provides various tools for pretty-printing objects in Hy syntax. The interface is largely compatible with Python's own pprint, with two cosmetic differences:

Much of the work is done by hy.repr, and thus Hyrule's pretty-printing can be extended to new types with hy.repr-register.

Since Hy lacks implicit concatenation of string literals, strings (and bytestrings) are broken up using +.

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. See pprint.PrettyPrinter.

(pformat object #* args #** kwargs)

Format an object into a pretty-printed representation. Return a string.

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

As pprint, but with sort-dicts defaulting to False.

(pprint object #* args #** kwargs)

Pretty-print the object to a stream.

(readable? object)

Determine if (saferepr object) is readable by Hy's parser.

(recursive? object)

Determine if the object requires a recursive representation.

(saferepr object)

As hy.repr, but with a different approach to recursive objects.

sequences — Cached, indexable iterables

class (Sequence iterable)

A wrapper for iterables that caches values and supports len(), indexing, and slicing. For example:

(setv s (Sequence (gfor  x (range 10)  (** x 2))))
(print (len s))           ; => 10
(print (get s 2))         ; => 4
(print (get s -1))        ; => 81
(print (list (cut s 3)))  ; => [0, 1, 4]

Sequence supports infinite iterables, but trying to compute the length of such a sequence or look up a negative index will of course fail.

macro(seq param #* seq-code)

Define a Sequence with code to compute the n-th element, where n starts from 0. The first argument is a literal list with a symbol naming the parameter, like the lambda list of defn, and the other arguments are the code to be evaluated.

(setv s (seq [n] (** n 2)))
(print (get s 2))  ; => 4

You can define the function recursively by getting previous elements of the sequence:

(setv fibonacci (seq [n]
  (if (< n 2)
      (get fibonacci (- n 1))
      (get fibonacci (- n 2))))))
(print (list (cut fibonacci 7)))  ; => [0, 1, 1, 2, 3, 5, 8]

To define a finite sequence, call end-sequence when the argument is too large:

(setv s (seq [n]
  (if (< n 5)
    (** n 2)
(print (list s))  ; => [0, 1, 4, 9, 16]
macro(defseq seq-name param #* seq-code)

Shorthand for assigning seq to a symbol. (defseq sequence-name [n] ...) is equivalent to (setv sequence-name (seq [n] ...)).


Shorthand for (raise (IndexError "sequence index out of range")).

misc — Everything else

macro(comment #* body)

Ignore any arguments and expand to None.

(setv x ["a"
         (comment <h1>Surprise!</h1>
                  You might be surprised what's lexically valid in Hy
                  (keep delimiters balanced and you're mostly good to go))
 x  ; => ["a" None "b"]

Contrast with Hy's built-in semicolon comments and discard prefix:

(setv x [1 ; 2 3
x  ; => [1 3]
(setv x [1 #_ 2 3])
x  ; => [1 3]
(setv x [1 (comment 2) 3])
x  ; => [1 None 3]
(constantly value)

Return a constant function, which ignores its arguments and always returns value.

(setv answer (constantly 42))
(answer)           ; => 42
(answer 1 :foo 2)  ; => 42
(dec n)

Shorthand for (- n 1). The name stands for "decrement".

(inc n)

Shorthand for (+ n 1). The name stands for "increment".

(import-path path name)

Import the Python or Hy source code at path as a module with importlib.util.spec_from_file_location(), per Python's documentation. Return the new module object. name defaults to (str (hy.gensym "import-file")).

(setv p (hy.I.pathlib.Path "mymodule.hy"))
(.write-text p "(setv foo 3)")
(setv m (import-path p))
(print m.foo)  ; => 3
macro(of base #* args)

Shorthand for type annotations with indexing. If only one argument is given, the macro expands to just that argument. If two arguments are given, it expands to indexing the first argument with the second. Otherwise, the first argument is indexed using a tuple of the rest. Thus:

  • (of T) becomes T.

  • (of T x) becomes (get T x).

  • (of T x y z) becomes (get T #(x y z)).

Here are some Python equivalents of example uses:

  • (of str)str

  • (of List int)List[int]

  • (of Callable [int str] str)Callable[[int, str], str]

(parse-args spec args #** parser-args)

Shorthand for typical uses of argparse. spec is a list of arguments to pass in repeated calls to ArgumentParser.add_argument. args, defaulting to sys.argv, will be used as the input arguments. parser-args, if provided, will be passed on to the constructor of ArgumentParser. The return value is that of parse_args.

(parse-args :spec [["strings" :nargs "+" :help "Strings"]
                   ["-n" "--numbers" :action "append" :type int :help "Numbers"]]
            :description "Parse strings and numbers from args"
            :args ["a" "b" "-n" "1" "-n" "2"])
   ; => Namespace(strings=['a', 'b'], numbers=[1, 2])
macro(profile/calls #* body)

profile/calls allows you to create a call graph visualization. Note: You must have Graphviz installed for this to work.


=> (require hyrule.contrib.profile [profile/calls])
=> (profile/calls (print "hey there"))
macro(profile/cpu #* body)

Profile a bit of code


=> (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}
(sign x)

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

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


(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

(xor a b)

A logical exclusive-or operation.

  • If exactly one argument is true, return it.

  • If neither is true, return the second argument (which will necessarily be false).

  • Otherwise (that is, when both arguments are true), return False.

[(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.