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
%isymbol that appears in the code:(setv f #%(+ %1 %3)) (f 1 10 100) ; => 101
Use
%*for a#* argsparameter and%**for a#** kwargsparameter:(#%[%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 nameditin 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
itofxsand returnNone.(ap-each [1 2 3] (print it))
- macro(ap-each-while xs form #* body)¶
As
ap-each, but the formpredis run before the body forms on each iteration, and the loop ends ifpredis 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 offormevaluated withitbound to successive elements ofxs.(list (ap-map (* it 2) [1 2 3])) ; => [2 4 6]
- macro(ap-map-when predfn rep xs)¶
As
ap-map, but the predicate functionpredfn(yes, that's a function, not an anaphoric form) is applied to eachit, and the anaphoric mapping formrepis only applied if the predicate is true. Otherwise,itis 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 ofap-map.(list (ap-filter (> (* it 2) 6) [1 2 3 4 5])) ; => [4 5]
- macro(ap-first form xs)¶
Evaluate the predicate
formfor each elementitofxs. When the predicate is true, stop and returnit. If the predicate is never true, returnNone.(ap-first (> it 5) (range 10)) ; => 6
- macro(ap-last form xs)¶
Evaluate the predicate
formfor every elementitofxs. Return the last element for which the predicate is true, orNoneif 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
accto the first element ofxs, binditto the second, and evaluateform.Bind
accto the result, binditto the third value ofxs, and evaluateformagain.Bind
accto the result, and continue untilxsis exhausted.
If
initial-valueis supplied, the process instead begins withaccset toinitial-valueanditset to the first element ofxs.(ap-reduce (+ it acc) (range 10)) ; => 45
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
argswithheadinserted 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
.fooare automatically expanded to(.foo):(-> " hello " .upper .strip) ; => "HELLO"
And if an argument
argisn'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
headto the symbolnameand evaluate each form inrest. After each evaluation, the result is reassigned toname.(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 isNone, 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 argumentform) 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,
dotois useful whenvalueis 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
collis evaluated exactly once. Notice that this implies the return value isNone, notcollor 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. Thusncutprovides an n-dimensional equivalent ofcut.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
sliceof integer literals. An omitted integer is understood asNone, 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
control — Control structures¶
- macro(block #* body)¶
A macro that allows you to jump outside of a list of forms, like the Common Lisp special operator of the same name. The body forms are executed until
(block-ret VALUE)is reached. The block returnsVALUE, or the value of the last form, if execution reached the end instead of being terminated byblock-ret.VALUEis optional and defaults toNone.One use of
blockis 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
Noneas 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
blockis macro-expanded.(block-ret-from :foo)outside of a block named:foois an error. Inner blocks names shadow outer blocks of the same name, soblock-retwill apply to the innermost of a series of nested anonymous blocks.There are no macros or functions named
block-retorblock-ret-from, since these forms are processed entirely byblock.block-retandblock-ret-fromshould not be confused with Hy's built-inreturn, which produces a true Python return statement.blockis implemented with exception-handling rather than functions, so it doesn't create a new scope asfnanddefndo.
- macro(branch tester #* rest)¶
Evaluate a test form with the symbol
itbound to each of several case forms in turn. If the result is true, the result form associated with the matching case is evaluated and returned; no later cases are evaluated. If no case matches, returnNone. The general syntax is:(branch TEST CASE-1 RESULT-1 CASE-2 RESULT-2 …)
For example,
(branch (in (.lower my-char) it) "aeiou" "it's a vowel" "xyz" "it's one of those last few")
is equivalent to
(cond (in (.lower my-char) "aeiou") "it's a vowel" (in (.lower my-char) "xyz") "it's one of those last few")
If you miss Common Lisp's
typecasemacro, here's how you can usebranchto 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
elseis 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")
branchwon't complain if you add more cases after theelsecase, even additionalelsecases, but they'll all be unreachable.If there are no case forms, the test form won't be evaluated, so the whole
branchis a no-op.ebranchis a convenience macro for when you want the no-match case to raise an error.caseandecaseare convenience macros for the special case of checking equality against a single test value.
- macro(case key #* rest)¶
Like the Common Lisp macro of the same name. Evaluate the first argument, called the key, and compare it with
=to each of several case forms in turn. If the key is equal to a case, the associated result form is evaluated and returned; no later cases are evaluated. If no case matches, returnNone. The general syntax is:(case KEY CASE-1 RESULT-1 CASE-2 RESULT-2 …)
For example, you could translate direction names to vectors like this:
(case direction "north" [ 0 1] "south" [ 0 -1] "east" [ 1 0] "west" [-1 0])
Thus,
(case KEY …)is equivalent to(branch (= it KEY) …), exceptKEYis evaluated exactly once, regardless of the number of cases.Like
branch,casetreats the symbolelseas a default case, and it has an error-raising version,ecase.casecan't check for collection membership like the Common Lisp version; for that, use(branch (in KEY it) …). It also can't pattern-match; for that, seematch.
- macro(cfor f #* gfor-args)¶
Shorthand for
(f (gfor …)). Seegfor.(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__).argsis the function's lambda list, which will be matched againstsys.argv. Recall that the first element ofsys.argvis always the name of the script being invoked, whereas the rest are command-line arguments. Ifargsis[], 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
argparsein the usual way, becausedefmaindoesn't changesys.argv. See alsoparse-args.
- macro(do-n count-form #* body)¶
Execute
bodya number of times equal tocount-formand returnNone. (To collect return values, uselist-ninstead.)The macro is implemented as a
forloop over arangecall, with the attendent consequences for negative counts,break, etc. As an exception, if the count isInf, 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, raiseValueErrorinstead of returningNone. The name is an abbreviation for "error branch".
- macro(ecase key #* rest)¶
As
case, but if no case matches, raiseValueErrorinstead of returningNone.
- macro(lif #* args)¶
A "Lispy if" similar to
ifandcond. Its most notable property is that it tests the condition values againstNoneandFalsewithis-not, rather than callingbool. 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
recurobject holds the arguments for the next call. When the function returns arecur,loopcalls it again with the new arguments. Otherwise,loopends 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
loopuses the same syntax as ordinary function definitions for its lambda list, all parameters other than#* argsand#** kwargsmust have a default value, because the function will first be called with no arguments.
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:
:ortakes a dictionary of default values.:astakes a symbol, which is bound to the entire input dictionary.:keystakes a list of symbols, which are looked up as keywords in the data.:strsworks 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.:astakes 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.Symbolobjects. The syntax is the same as that ofsetv+.(dict=: {a "apple" b "banana"} {"apple" 1 "banana" 2}) ; => {'a 1 'b 2}
- macro(let+ args #* body)¶
A version of
letthat 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
collis empty, the new iterator will also be empty.
- (coll? x)¶
Return
Trueifxinherits fromcollections.abc.Iterablebut notstrorbytes.(coll? ["abc"]) ; True (coll? {"a" 1 "b" 2}) ; True (coll? "abc") ; False
- (distinct coll)¶
Return an iterator from the original iterable
collwith no duplicates. Duplicates are detected by callinginon aset. Elements are produced in order of their first appearance incoll.(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
nelements incoll.nmust 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
collisn'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
collexcepting the first.(list (rest (range 5))) ; => [1 2 3 4]
When
collis empty, the new iterator will also be empty.
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-paramsis used to match the arguments againstparams, 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 correspondingg!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-alleven 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
fto the contents of the sequential modelxgathered into a tuple.fshould return an iterable object. This result is then wrapped in the original model type, preserving attributes such as the brackets of anhy.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-hyseqisn't inherently recursive.If
xisn't a sequential Hy model, it's returned as-is, without callingf.
- (map-model x f)¶
Recursively apply a callback to some code. The unary function
fis called on the objectx, callinghy.as-modelfirst. If the return value isn'tNone, it's converted to a model and used as the result. But if the return value isNone, andxisn't a sequential model, thenxis 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
freturnsNoneandxis sequential. Thenmap-modelis called on all the elements ofxand the results are bound up in the same model type asx.(map-model '[a [b c] d] f) ; => '[a [B c] d]
The typical use of
map-modelis 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-modelare backwards compared tomap(): in user code,xis typically a symbol or other simple form whereasfis a multi-line anonymous function.
- (match-fn-params args params)¶
Match an iterable of arguments against a parameter list in the style of a
defnlambda 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#* argsother 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
Noneas 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
defnthat provides syntactic sugar forself. As the name suggests, it's most useful for defining methods. The parameter list is automatically prepended withself, and any reference to a symbol beginning with@is replaced such that@foobecomesself.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:@foois replaced with simplyfoo, 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 plainself. By contrast, the symbol@is left untouched, since it may refer to the Hy core macro@.
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:
isreadablebecomesreadable?.isrecursivebecomesrecursive?.
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 withsort-dictsdefaulting toFalse.
- (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.
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]
Sequencesupports 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
Sequencewith code to compute thenth element, wherenstarts at 0. The first argument is a literal list with a symbol naming the parameter, like the lambda list ofdefn, 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-sequencewhen 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
seqto 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
pathas a module withimportlib.util.spec_from_file_location(), per Python's documentation. Return the new module object.namedefaults 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)becomesT.(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.specis a list of arguments to pass in repeated calls toArgumentParser.add_argument.args, defaulting tosys.argv, will be used as the input arguments.parser-args, if provided, will be passed on to the constructor ofArgumentParser. The return value is that ofparse_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
bodywith 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 positivex, and 0 forxequal 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 namesmacrolet, 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
letbinding. Here, for example,ain 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.