Hyrule v0.7.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 namedit
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
ofxs
and returnNone
.(ap-each [1 2 3] (print it))
- macro(ap-each-while xs form #* body)¶
As
ap-each
, but the formpred
is run before the body forms on each iteration, and the loop ends ifpred
is false.(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 ofform
evaluated withit
bound 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 formrep
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 ofap-map
.(list (ap-filter (> (* it 2) 6) [1 2 3 4 5])) ; => [4 5]
- macro(ap-first form xs)¶
Evaluate the predicate
form
for each elementit
ofxs
. When the predicate is true, stop and returnit
. If the predicate is never true, returnNone
.(ap-first (> it 5) (range 10)) ; => 6
- macro(ap-last form xs)¶
Evaluate the predicate
form
for every elementit
ofxs
. Return the last element for which the predicate is true, orNone
if there is no such element.(ap-last (> it 5) (range 10)) ; => 9
- macro(ap-reduce form o!xs initial-value)¶
This macro is an anaphoric version of
functools.reduce()
. It works as follows:Bind
acc
to the first element ofxs
, bindit
to the second, and evaluateform
.Bind
acc
to the result, bindit
to the third value ofxs
, and evaluateform
again.Bind
acc
to the result, and continue untilxs
is exhausted.
If
initial-value
is supplied, the process instead begins withacc
set toinitial-value
andit
set to the first element ofxs
.(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
args
withhead
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 symbolname
and 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,
doto
is useful whenvalue
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 isNone
, notcoll
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. Thusncut
provides 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
slice
of 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
- (postwalk f form)¶
Performs depth-first, post-order traversal of
form
. Callsf
on each sub-form, usesf
's return value in place of the original.Examples
=> (import hyrule.contrib.walk [postwalk]) => (setv trail '([1 2 3] [4 [5 6 [7]]])) => (defn walking [x] ... (print "Walking" x :sep "\n") ... x) => (postwalk walking trail) Walking 1 Walking 2 Walking 3 Walking hy.models.Expression([ hy.models.Integer(1), hy.models.Integer(2), hy.models.Integer(3)]) Walking 4 Walking 5 Walking 6 Walking 7 Walking hy.models.Expression([ hy.models.Integer(7)]) Walking hy.models.Expression([ hy.models.Integer(5), hy.models.Integer(6), hy.models.List([ hy.models.Integer(7)])]) Walking hy.models.Expression([ hy.models.Integer(4), hy.models.List([ hy.models.Integer(5), hy.models.Integer(6), hy.models.List([ hy.models.Integer(7)])])]) Walking hy.models.Expression([ hy.models.List([ hy.models.Integer(1), hy.models.Integer(2), hy.models.Integer(3)]), hy.models.List([ hy.models.Integer(4), hy.models.List([ hy.models.Integer(5), hy.models.Integer(6), hy.models.List([ hy.models.Integer(7)])])])]) '([1 2 3] [4 [5 6 [7]]]))
- (prewalk f form)¶
Performs depth-first, pre-order traversal of
form
. Callsf
on each sub-form, usesf
's return value in place of the original.Examples
=> (import hyrule.contrib.walk [prewalk]) => (setv trail '([1 2 3] [4 [5 6 [7]]])) => (defn walking [x] ... (print "Walking" x :sep "\n") ... x) => (prewalk walking trail) Walking hy.models.Expression([ hy.models.List([ hy.models.Integer(1), hy.models.Integer(2), hy.models.Integer(3)]), hy.models.List([ hy.models.Integer(4), hy.models.List([ hy.models.Integer(5), hy.models.Integer(6), hy.models.List([ hy.models.Integer(7)])])])]) Walking hy.models.List([ hy.models.Integer(1), hy.models.Integer(2), hy.models.Integer(3)]) Walking 1 Walking 2 Walking 3 Walking hy.models.List([ hy.models.Integer(4), hy.models.List([ hy.models.Integer(5), hy.models.Integer(6), hy.models.List([ hy.models.Integer(7)])])]) Walking 4 Walking hy.models.List([ hy.models.Integer(5), hy.models.Integer(6), hy.models.List([ hy.models.Integer(7)])]) Walking 5 Walking 6 Walking hy.models.List([ hy.models.Integer(7)]) Walking 7 '([1 2 3] [4 [5 6 [7]]])
- reader macro(s)¶
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
traversesform
, an arbitrary data structure. Appliesinner
to each element of form, building up a data structure of the same type. Appliesouter
to the result.Examples
=> (import hyrule.contrib.walk [walk]) => (setv a '(a b c d e f)) => (walk ord (fn [x] x) a) '(97 98 99 100 101 102)
=> (walk ord (fn [x] (get x 0)) a) 97
control
— Control structures¶
- macro(block #* body)¶
A macro that allows you to jump outside of a list of forms, like the Common Lisp special operator of the same name. The body forms are executed until
(block-ret VALUE)
is reached. The block returnsVALUE
, or the value of the last form, if execution reached the end instead of being terminated byblock-ret
.VALUE
is optional and defaults toNone
.One use 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, soblock-ret
will apply to the innermost of a series of nested anonymous blocks.There are no macros or functions named
block-ret
orblock-ret-from
, since these forms are processed entirely byblock
.block-ret
andblock-ret-from
should not be confused with Hy's built-inreturn
, which produces a true Python return statement.block
is implemented with exception-handling rather than functions, so it doesn't create a new scope asfn
anddefn
do.
- macro(branch tester #* rest)¶
Evaluate a test form with the symbol
it
bound to each of several case forms in turn. If the result is true, the result form associated with the matching case is evaluated and returned; no later cases are evaluated. If no case matches, returnNone
. The general syntax is:(branch TEST CASE-1 RESULT-1 CASE-2 RESULT-2 …)
For example,
(branch (in (.lower my-char) it) "aeiou" "it's a vowel" "xyz" "it's one of those last few")
is equivalent to
(cond (in (.lower my-char) "aeiou") "it's a vowel" (in (.lower my-char) "xyz") "it's one of those last few")
If you miss Common Lisp's
typecase
macro, here's how you can usebranch
to branch by type in the same way:(branch (isinstance my-value it) str "It's a string" bytes "It's a bytes object" #(int float) "It's numeric")
A case form that is exactly the symbol
else
is treated specially. In this case, the test form isn't evaluated, and is treated as if it returned true.(branch (= it my-value) True "Correct" False "Wrong" else "Not actually Boolean")
branch
won't complain if you add more cases after theelse
case, even additionalelse
cases, but they'll all be unreachable.If there are no case forms, the test form won't be evaluated, so the whole
branch
is a no-op.ebranch
is a convenience macro for when you want the no-match case to raise an error.case
andecase
are convenience macros for the special case of checking equality against a single test value.
- macro(case key #* rest)¶
Like the Common Lisp macro of the same name. Evaluate the first argument, called the key, and compare it with
=
to each of several case forms in turn. If the key is equal to a case, the associated result form is evaluated and returned; no later cases are evaluated. If no case matches, returnNone
. The general syntax is:(case KEY CASE-1 RESULT-1 CASE-2 RESULT-2 …)
For example, you could translate direction names to vectors like this:
(case direction "north" [ 0 1] "south" [ 0 -1] "east" [ 1 0] "west" [-1 0])
Thus,
(case KEY …)
is equivalent to(branch (= it KEY) …)
, exceptKEY
is evaluated exactly once, regardless of the number of cases.Like
branch
,case
treats the symbolelse
as a default case, and it has an error-raising version,ecase
.case
can't check for collection membership like the Common Lisp version; for that, use(branch (in KEY it) …)
. It also can't pattern-match; for that, seematch
.
- macro(cfor f #* 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
__name__
equals"__main__"
(see__main__
).args
is the function's lambda list, which will be matched againstsys.argv
. Recall that the first element ofsys.argv
is always the name of the script being invoked, whereas the rest are command-line arguments. Ifargs
is[]
, this will be treated like[#* _]
, so any command-line arguments (and the script name) will be allowed, but ignored.If the defined function returns an
int
,sys.exit()
is called with that integer as the return code.If you want fancy command-line arguments, you can use the standard Python module
argparse
in the usual way, becausedefmain
doesn't changesys.argv
. See alsoparse-args
.(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 tocount-form
and returnNone
. (To collect return values, uselist-n
instead.)The macro is implemented as a
for
loop over arange
call, with the attendent consequences for negative counts,break
, etc. As an exception, if the count 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, raiseValueError
instead of returningNone
. The name is an abbreviation for "error branch".
- macro(ecase key #* rest)¶
As
case
, but if no case matches, raiseValueError
instead of returningNone
.
- macro(lif #* args)¶
A "Lispy if" similar to
if
andcond
. Its most notable property is that it tests the condition with(is-not condition None)
instead of(bool condition)
, so values such as the integer 0, the empty string, andFalse
are considered true, not false. The general syntax is(lif condition1 result1 condition2 result2 … else-value)
which is equivalent to
(cond (is-not condition1 None) result1 (is-not condition2 None) result2 … True else-value)
When no condition matches and there is no else-value, the result is
None
.
- macro(list-n count-form #* body)¶
Like
do-n
, but the results are collected into a list.(setv counter 0) (list-n 5 (+= counter 1) counter) ; => [1 2 3 4 5]
- macro(loop bindings #* body)¶
The loop/recur macro allows you to construct functions that use tail-call optimization to allow arbitrary levels of recursion.
loop
establishes a recursion point. Withloop
,recur
rebinds the variables set in the recursion point and sends code execution back to that recursion point. Ifrecur
is used in a non-tail position, an exception is raised. which causes chaos.Usage:
(loop bindings #* body)
Examples
=> (require hyrule.contrib.loop [loop]) => (defn factorial [n] ... (loop [[i n] [acc 1]] ... (if (= i 0) ... acc ... (recur (dec i) (* acc i))))) => (factorial 1000)
destructure
— Macros for destructuring collections¶
This module is heavily inspired by destructuring from Clojure and provides very similar semantics. It provides several macros that allow for destructuring within their arguments.
Destructuring allows one to easily peek inside a data structure and assign names to values within. For example,
(setv+ {[{name :name [weapon1 weapon2] :weapons} :as all-players] :players
map-name :map
:keys [tasks-remaining tasks-completed]}
data)
would be equivalent to
(setv map-name (.get data ':map)
tasks-remaining (.get data ':tasks-remaining)
tasks-completed (.get data ':tasks-completed)
all-players (.get data ':players)
name (.get (get all-players 0) ':name)
weapon1 (get (.get (get all-players 0) ':weapons) 0)
weapon2 (get (.get (get all-players 0) ':weapons) 1))
where data
might be defined by
(setv data {:players [{:name Joe :weapons [:sword :dagger]}
{:name Max :weapons [:axe :crossbow]}]
:map "Dungeon"
:tasks-remaining 4})
This is similar to unpacking iterables in Python, such as a, *b, c = range(10)
, however it also works on dictionaries, and has several special options.
Warning
Variables which are not found in the expression are silently set to None
if no default value is specified. This is particularly important with defn+
and fn+
.
(defn+ some-function [arg1
{subarg2-1 "key"
:or {subarg2-1 20}
:as arg2}
[subarg3-1
:& subargs3-2+
:as arg3]]
{"arg1" arg1 "arg2" arg2 "arg3" arg3
"subarg2-1" subarg2-1 "subarg3-1" subarg3-1 "subargs3-2+" subargs3-2+})
(some-function 1 {"key" 2} [3 4 5])
; => {"arg1" 1 "arg2" {"key" 2} "arg3" [3 4 5]
; "subarg2-1" 2 "subarg3-1" 3 "subargs3-2+" [4 5]}
(some-function 1 2 [])
; => {"arg1" 1 "arg2" None "arg3" []
; "subarg2-1" 20 "subarg3-1" None "subargs3-2+" []}
(some-function)
; => {"arg1" None "arg2" None "arg3" None
; "subarg2-1" 20 "subarg3-1" None "subargs3-2+" None}
Note that variables with a default value from an :or
special option will fallback to their default value instead of being silently set to None
.
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.
API¶
- macro(defn+ fn-name args #* doc+body)¶
Define function fn-name with destructuring within args.
Note that #* etc have no special meaning and are intepretted as any other argument.
- macro(defn/a+ fn-name args #* doc+body)¶
Async variant of
defn+
.
- macro(dict=: #* pairs)¶
Destructure into dict
Same as
setv+
, except returns a dictionary with symbols to be defined, instead of actually declaring them.
- macro(fn+ args #* body)¶
Return anonymous function with destructuring within args
Note that *, /, etc have no special meaning and are intepretted as any other argument.
- macro(fn/a+ args #* body)¶
Async variant of
fn+
.
- macro(let+ args #* body)¶
let macro with full destructuring with args
- macro(setv+ #* pairs)¶
Assignment with destructuring for both mappings and iterables.
Destructuring equivalent of
setv
. Binds symbols found in a pattern using the corresponding expression.Examples
(setv+ pattern_1 expression_1 ... pattern_n expression_n)
iterables
— Tools for iterable objects¶
- (butlast coll)¶
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
ifx
inherits fromcollections.abc.Iterable
but notstr
orbytes
.(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 callingin
on aset
. Elements will be 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
n
elements incoll
.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.
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 againstparams
, and the parameters are assigned to local variables that can be used in the macro body.(defmacro-kwargs do10times [form [print-iteration 'False]] (setv i (hy.gensym)) `(for [~i (range 10)] (when ~print-iteration (print "Now on iteration:" ~i)) ~form)) (setv x []) (do10times (.append x 1)) ; Nothing is printed. (do10times :print-iteration (> (len x) 17) (.append x 1)) ; Iterations 8 and 9 are printed.
- macro(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 form ast-compiler)¶
Recursively performs all possible macroexpansions in form, using the
require
context ofmodule-name
. macroexpand-all assumes the calling module's context if unspecified.
- (map-model x f)¶
Recursively apply a callback to some code. The unary function
f
is called on the objectx
, converting it to a model first if it isn't one already. If the return value isn'tNone
, it's converted to a model and used as the result. But if the return value isNone
, andx
isn't a sequential model, thenx
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
returnsNone
andx
is sequential. Thenmap-model
is called on all the elements ofx
and 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-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 tomap
: in user code,x
is typically a symbol or other simple form whereasf
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.(match-fn-params [1 :foo "x"] '[a [b 2] [c 3] #* args #** kwargs]) ; => {"a" 1 "b" 2 "c" 3 "args" #() "kwargs" {"foo" "x"}}
If a default argument is a model, it's evaluated. The evaluation occurs in a minimal environment, with no access to surrounding global or local Python-level objects or macros. If this is too restrictive, use
None
as the default value and compute the real default value in other code.This function exists mostly to implement
defmacro-kwargs
.
- macro(with-gensyms args #* body)¶
Evaluate
body
with each name inargs
(a list of symbols) bound to a gensym. The syntax(with-gensyms [a b c] …)
is equivalent to
(do (setv a (hy.gensym 'a)) (setv b (hy.gensym 'b)) (setv c (hy.gensym 'c)) …)
oop
— Tools for object-oriented programming¶
- macro(meth #* args)¶
A replacement for
defn
that 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@
, such as@foo
, is replaced byself.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 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:
isreadable
becomesreadable?
.isrecursive
becomesrecursive?
.
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-dicts
defaulting 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]
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 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-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 withimportlib.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)
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
.spec
is 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(profile/calls #* body)¶
profile/calls
allows you to create a call graph visualization. Note: You must have Graphviz installed for this to work.Examples
=> (require hyrule.contrib.profile [profile/calls]) => (profile/calls (print "hey there"))
- macro(profile/cpu #* body)¶
Profile a bit of code
Examples
=> (require hyrule.contrib.profile [profile/cpu]) => (profile/cpu (print "hey there"))
hey there <pstats.Stats instance at 0x14ff320> 2 function calls in 0.000 seconds Random listing order was used ncalls tottime percall cumtime percall filename:lineno(function) 1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects} 1 0.000 0.000 0.000 0.000 {print}
- (sign x)¶
Return -1 for negative
x
, 1 for positivex
, and 0 forx
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)¶
symbol macro let.
Replaces symbols in body, but only where it would be a valid let binding. The bindings pairs the target symbol and the expansion form for that symbol
Examples
(smacrolet [b c] (defn foo [a [b 1]] (* b (+ a 1))) (* b (foo 7)))
Would compile to:
(defn foo [a [b 1]] (* b (+ a 1))) (* c (foo 7))
Notice that the
b
symbol defined by thedefn
remains unchanged as it is not a validlet
binding. Only the top levelb
sym has been replaced withc
- (xor a b)¶
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.