-
Notifications
You must be signed in to change notification settings - Fork 372
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Give require
the same features as import
#1142
Changes from all commits
ce72216
a8440b7
bff9640
45c72ed
f23c97d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1210,16 +1210,84 @@ alternatively be written using the apostrophe (``'``) symbol. | |
require | ||
------- | ||
|
||
``require`` is used to import macros from a given module. It takes at least one | ||
parameter specifying the module which macros should be imported. Multiple | ||
modules can be imported with a single ``require``. | ||
``require`` is used to import macros from one or more given modules. It allows | ||
parameters in all the same formats as ``import``. The ``require`` form itself | ||
produces no code in the final program: its effect is purely at compile-time, for | ||
the benefit of macro expansion. Specifically, ``require`` imports each named | ||
module and then makes each requested macro available in the current module. | ||
|
||
The following example will import macros from ``module-1`` and ``module-2``: | ||
The following are all equivalent ways to call a macro named ``foo`` in the module ``mymodule``: | ||
|
||
.. code-block:: clj | ||
|
||
(require module-1 module-2) | ||
(require mymodule) | ||
(mymodule.foo 1) | ||
|
||
(require [mymodule :as M]) | ||
(M.foo 1) | ||
|
||
(require [mymodule [foo]]) | ||
(foo 1) | ||
|
||
(require [mymodule [*]]) | ||
(foo 1) | ||
|
||
(require [mymodule [foo :as bar]]) | ||
(bar 1) | ||
|
||
Macros that call macros | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is good addition. I got bitten by this multiple times, until I learned it hard way. |
||
~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
||
One aspect of ``require`` that may be surprising is what happens when one | ||
macro's expansion calls another macro. Suppose ``mymodule.hy`` looks like this: | ||
|
||
.. code-block:: clj | ||
|
||
(defmacro repexpr [n expr] | ||
; Evaluate the expression n times | ||
; and collect the results in a list. | ||
`(list (map (fn [_] ~expr) (range ~n)))) | ||
|
||
(defmacro foo [n] | ||
`(repexpr ~n (input "Gimme some input: "))) | ||
|
||
And then, in your main program, you write: | ||
|
||
.. code-block:: clj | ||
|
||
(require [mymodule [foo]]) | ||
|
||
(print (mymodule.foo 3)) | ||
|
||
Running this raises ``NameError: name 'repexpr' is not defined``, even though | ||
writing ``(print (foo 3))`` in ``mymodule`` works fine. The trouble is that your | ||
main program doesn't have the macro ``repexpr`` available, since it wasn't | ||
imported (and imported under exactly that name, as opposed to a qualified name). | ||
You could do ``(require [mymodule [*]])`` or ``(require [mymodule [foo | ||
repexpr]])``, but a less error-prone approach is to change the definition of | ||
``foo`` to require whatever sub-macros it needs: | ||
|
||
.. code-block:: clj | ||
|
||
(defmacro foo [n] | ||
`(do | ||
(require mymodule) | ||
(mymodule.repexpr ~n (raw-input "Gimme some input: ")))) | ||
|
||
It's wise to use ``(require mymodule)`` here rather than ``(require [mymodule | ||
[repexpr]])`` to avoid accidentally shadowing a function named ``repexpr`` in | ||
the main program. | ||
|
||
Qualified macro names | ||
~~~~~~~~~~~~~~~~~~~~~ | ||
|
||
Note that in the current implementation, there's a trick in qualified macro | ||
names, like ``mymodule.foo`` and ``M.foo`` in the above example. These names | ||
aren't actually attributes of module objects; they're just identifiers with | ||
periods in them. In fact, ``mymodule`` and ``M`` aren't defined by these | ||
``require`` forms, even at compile-time. None of this will hurt you unless try | ||
to do introspection of the current module's set of defined macros, which isn't | ||
really supported anyway. | ||
|
||
rest / cdr | ||
---------- | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1737,10 +1737,45 @@ def compile_require(self, expression): | |
"unimport" it after we've completed `thing' so that we don't pollute | ||
other envs. | ||
""" | ||
expression.pop(0) | ||
for entry in expression: | ||
__import__(entry) # Import it fo' them macros. | ||
require(entry, self.module_name) | ||
for entry in expression[1:]: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems kind of odd that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. They use the same syntax but do pretty different things because |
||
if isinstance(entry, HySymbol): | ||
# e.g., (require foo) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. REALLY minor nitpick, but this would be a little easier to read without the |
||
__import__(entry) | ||
require(entry, self.module_name, all_macros=True, | ||
prefix=entry) | ||
elif isinstance(entry, HyList) and len(entry) == 2: | ||
# e.g., (require [foo [bar baz :as MyBaz bing]]) | ||
# or (require [foo [*]]) | ||
module, names = entry | ||
if not isinstance(names, HyList): | ||
raise HyTypeError(names, | ||
"(require) name lists should be HyLists") | ||
__import__(module) | ||
if '*' in names: | ||
if len(names) != 1: | ||
raise HyTypeError(names, "* in a (require) name list " | ||
"must be on its own") | ||
require(module, self.module_name, all_macros=True) | ||
else: | ||
assignments = {} | ||
while names: | ||
if len(names) > 1 and names[1] == HyKeyword(":as"): | ||
k, _, v = names[:3] | ||
del names[:3] | ||
assignments[k] = v | ||
else: | ||
symbol = names.pop(0) | ||
assignments[symbol] = symbol | ||
require(module, self.module_name, assignments=assignments) | ||
elif (isinstance(entry, HyList) and len(entry) == 3 | ||
and entry[1] == HyKeyword(":as")): | ||
# e.g., (require [foo :as bar]) | ||
module, _, prefix = entry | ||
__import__(module) | ||
require(module, self.module_name, all_macros=True, | ||
prefix=prefix) | ||
else: | ||
raise HyTypeError(entry, "unrecognized (require) syntax") | ||
return Result() | ||
|
||
@builds("and") | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,4 @@ | ||
(import [hy.contrib.botsbuildbots [*]]) | ||
(require hy.contrib.botsbuildbots) | ||
(require [hy.contrib.botsbuildbots [Botsbuildbots]]) | ||
|
||
(defn test-botsbuildbots [] | ||
(assert (> (len (first (Botsbuildbots))) 50))) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
(require hy.contrib.curry) | ||
(require [hy.contrib.curry [defnc]]) | ||
|
||
|
||
(defnc s [x y z] ((x z) (y z))) ; λxyz.xz(yz) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
(require hy.contrib.loop) | ||
(require [hy.contrib.loop [loop]]) | ||
(import sys) | ||
|
||
(defn tco-sum [x y] | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I read the code correctly, following doesn't work:
but similar does work for
import
thoughThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Embarassingly, I had no idea that
from foo import bar as baz
is legal Python, let alone supported in Hy. I'll add that form forrequire
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.