Hyrule v1.0.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

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

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

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:

(import
  re
  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 original first argument form) is used every time. In other words,

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

is equvalent to

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

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 #** kwargs)

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.

Keyword arguments work like positional arguments with the keyword used as a string key, subject to Hy's usual mangling rules. Thus, (assoc coll :foo-bar 1) is equivalent to (assoc coll "foo_bar" 1) or (setv (get coll "foo_bar") 1). Assignments for keyword arguments occur after (and thus may override) assignments for positional arguments.

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

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

(defmain [program-name argument]
  (print "Welcome to" program-name)
  (print "The answer is" (* (float argument) 2)))

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.

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 values against None and False with is-not, rather than calling bool. Thus, values such as the integer 0 and the empty string are considered true, not false. The general syntax is

(lif
  condition1 result1
  condition2 result2
  
  else-value)

which is equivalent to

(cond
  (is-not None condition1 False) result1
  (is-not None condition2 False) result2
  
  True                           else-value)

When no condition obtains 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)

Construct and immediately call an anonymous function with explicit tail-call elimination. To see how it's used, consider this tail-recursive implementation of the factorial function:

(defn factorial [n [acc 1]]
  (if n
    (factorial (- n 1) (* acc n))
    acc))

With loop, this would be written as:

(require hyrule [loop])
(import hyrule [recur])

(defn factorial [n]
  (loop [[n n] [acc 1]]
    (if n
      (recur (- n 1) (* acc n))
      acc)))

The recur object holds the arguments for the next call. When the function returns a recur, loop calls it again with the new arguments. Otherwise, loop ends and the final value is returned. Thus, what would be a nested set of recursive calls becomes a series of calls that are resolved entirely in sequence.

Note that while loop uses the same syntax as ordinary function definitions for its lambda list, all parameters other than #* args and #** kwargs must have a default value, because the function will first be called with no arguments.

class (recur #* args #** kwargs)

A simple wrapper class used by loop. The attribute args holds a tuple and kwargs holds a dictionary.

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 similar semantics. It provides several macros that allow for destructuring within their arguments, not unlike iterable unpacking, as in (setv [a #* b c] (range 10)), but also functioning on dictionaries and allowing several special options.

Destructuring allows you to easily peek inside a data structure and assign names to values within. For example, suppose you have a data structure like this:

(setv data {:players [{:name "Joe" :weapons [:sword :dagger]}
                      {:name "Max" :weapons [:axe :crossbow]}]
            :map "Dungeon"
            :tasks-remaining 4})

You could manually write out a lot of assignments like this:

(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 all-players 0 :weapons 0)
      weapon2 (get all-players 0 :weapons 1))

Or, for the same result, you could use a destructuring macro:

(setv+ {[{name :name [weapon1 weapon2] :weapons} :as all-players] :players
        map-name :map
        :keys [tasks-remaining tasks-completed]}
       data)

Warning

Variables which are not found in the data 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}

Notice how a variable with a default value from an :or special option will fall back to that value instead of None.

Pattern types

Several kinds of patterns are understood.

Dictionary patterns

Dictionary patterns are specified using a hy.models.Dict, where the keys corresponds to the symbols to be bound, and the values correspond to keys to be looked up in the data.

(setv+ {a :a  b "b"  c #(1 0)}
       {:a 1  "b" 2  #(1 0) 3})
[a b c] ; => [1 2 3]

A key in a dictionary pattern can also be one of the following special options:

  • :or takes a dictionary of default values.

  • :as takes a symbol, which is bound to the entire input dictionary.

  • :keys takes a list of symbols, which are looked up as keywords in the data.

  • :strs works like :keys, but looks up strings instead of keywords.

For example:

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

The ordering of the special options and the ordinary variable names doesn't matter, but each special option can be used at most once.

If a lookup fails, and the symbol to be bound to doesn't have an associated default, None is bound instead:

(setv+ {out "a"} {})
(is out None) ; => True
List patterns

List patterns are specified with a hy.models.List. The nth symbol in the pattern is bound to the nth value in the data, or None if the data has fewer than n values.

List patterns support these special options:

  • :& takes a pattern, which is bound to the rest of the data. This pattern can be anything, including a dictionary pattern, which allows for keyword arguments.

  • :as takes a symbol, which is bound to the whole input iterable.

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 list patterns call list on the data before binding the variables, so they can't be used with infinite iterators.

Iterator patterns

Iterator patterns are specified with a hy.models.Expression. They work the same as list patterns except that they only consume as much of the input as is required for matching, so they can be safely used with infinite iterators. :rest and :as create iterables instead of lists, and no recursive destructuring within :as is allowed.

(import itertools [count islice])
(setv+ (a b :& rest :as full) (count))
[a b] ; => [0 1]
(list (islice rest 5)) ; => [2 3 4 5 6]
(list (islice full 5)) ; => [0 1 2 3 4]

API

macro(defn+ #* args)

As defn, but the lambda list is destructured as a list pattern. The usual special parameter names in lambda lists, such as #*, aren't special here. No type annotations are allowed are in the lambda list, but a return-value annotation for the whole function is allowed.

macro(dict=: #* pairs)

Take pairs of destructuring patterns and input data structures, and return a dictionary of bindings, where the keys are hy.models.Symbol objects. The syntax is the same as that of setv+.

(dict=: {a "apple"  b "banana"}
        {"apple" 1  "banana" 2})
  ; => {'a 1  'b 2}
macro(fn+ #* args)

A version of fn that destructures like defn+.

macro(let+ args #* body)

A version of let that allows destructuring patterns in place of plain symbols for binding.

macro(setv+ #* pairs)

Take pairs of destructuring patterns and input data structures, assign to variables in the current scope as specified by the patterns, and return None.

(setv+ {a "apple"  b "banana"}  {"apple" 1  "banana" 2}
       [c d] [3 4])
[a b c d] ; => [1 2 3 4]

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 are 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, depth-first, and return them in 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"]

Since iteration is used to collect the elements of coll, dictionaries are reduced to lists of keys:

(flatten [{"a" 1  "b" 2} {"c" 3  "d" 4}])
  ; => ["a" "b" "c" "d"]

If coll isn't a collection at all, it's returned in a singleton list:

(flatten "hello")
  ; => ["hello"]
(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).

(list (thru 3))
  ; => [0 1 2 3]
(list (thru 0 10 2))
  ; => [0 2 4 6 8 10]
(list (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(def-gensyms #* symbols)

Define a number of gensyms, binding each symbol to a call to hy.gensym. The syntax

(def-gensyms a b c)

is equivalent to

(setv
  a (hy.gensym 'a)
  b (hy.gensym 'b)
  c (hy.gensym 'c))
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]]
  (def-gensyms i)
  `(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(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 []
  `(do
     (setv ~g!accum [])
     (for [x (range 3)]
       (.append ~g!accum x))
     ~g!accum))
(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 model module macros)

As hy.macroexpand, but with recursive descent through the input model, attempting to expand all macro calls throughout the tree:

(defmacro m [] 5)
(print (hy.repr (hy.macroexpand '(m))))
  ; => '5
(print (hy.repr (hy.macroexpand '(do (m)))))
  ; => '(do (m))
(print (hy.repr (macroexpand-all '(do (m)))))
  ; => '(do 5)

macroexpand-all even expands macros in unquoted portions of quasiquoted forms:

(print (hy.repr (macroexpand-all '(do `[4 (m) ~(m) 6]))))
  ; => '(do `[4 (m) ~5 6])
(map-hyseq x f)

Apply the function f to the contents of the sequential model x gathered into a tuple. f should return an iterable object. This result is then wrapped in the original model type, preserving attributes such as the brackets of an hy.models.FString.

(map-hyseq '[:a :b :c] (fn [x]
  (gfor  e x  (hy.models.Keyword (.upper e.name)))))
  ; => '[:A :B :C]

Unlike map-model, map-hyseq isn't inherently recursive.

If x isn't a sequential Hy model, it's returned as-is, without calling f.

(map-model x f)

Recursively apply a callback to some code. The unary function f is called on the object x, calling hy.as-model first. 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.

(match-fn-params args params)

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

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

oop — Tools for object-oriented programming

macro(meth #* args)

A replacement for defn that provides syntactic sugar for self. As the name suggests, it's most useful for defining methods. The parameter list is automatically prepended with self, and any reference to a symbol beginning with @ is replaced such that @foo becomes self.foo:

(defclass BirdWatcher []

  (meth observe [bird]
    (@log bird)
    (setv @last-seen bird)
    @last-seen)

  (meth log [bird]
    (print "I just saw:" bird)))

(setv x (BirdWatcher))
(.observe x "sparrow")   ; I just saw: sparrow
(.observe x "cardinal")  ; I just saw: cardinal
(print x.last-seen)      ; cardinal

@-symbols that appear in the lambda list of the method are special: @foo is replaced with simply foo, and the method body is prepended with (setv self.foo foo). This is convenient for parameters to __init__ that set attributes of the same name:

(defclass Rectangle []

  (meth __init__ [@width @height])
    ; Look Ma, no body!

  (meth area []
    (* @width @height)))

(setv x (Rectangle 3 4))
(print (.area x))  ; => 12

The symbol @, is replaced with just plain self. By contrast, the symbol @ is left untouched, since it may refer to the Hy core macro @.

macro(ameth #* args)

Define an anonymous method. ameth is to meth as fn is to defn: it has the same syntax except that no method name (or decorators) are allowed.

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 nth element, where n starts at 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)
    n
    (+
      (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)
    (end-sequence))))
(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] ...)).

(end-sequence)

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))
         "b"])
 x  ; => ["a" None "b"]

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

(setv x [1 ; 2 3
           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-path")).

(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(pun #* body)

Evaluate body with a shorthand for keyword arguments that are set to variables of the same name. Any keyword whose name starts with an exclamation point, such as :!foo, is replaced with a keyword followed by a symbol, such as :foo foo:

(setv  a 1  b 2  c 3)
(pun (dict :!a :!b :!c))
  ; i.e., (dict :a a :b b :c c)
  ; => {"a" 1  "b" 2  "c" 3}

This macro is named after the NamedFieldPuns language extension to Haskell.

(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(smacrolet _hy-compiler bindings #* body)

Tell the Hy compiler to translate certain symbols when compiling the body. The effect is similar to symbol macros (as seen in e.g. Common Lisp) and uses the same scoping logic as let, hence the name smacrolet, i.e., "symbol macro let". The first argument is a list of bindings, which must be pairs of symbols.

(setv x "a")
(setv y "other")
(smacrolet [y x  z x]
  (+= y "b")
  (+= z "c"))
(print x)  ; "abc"
(print y)  ; "other"

The translation doesn't occur in uses of the symbol that wouldn't apply to a let binding. Here, for example, a in an attribute assignment isn't replaced:

(setv x 1)
(smacrolet [a x]
  (defclass C []
    (setv a 2))
  (print a))  ; 1
(print C.a)   ; 2
(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.