diff --git a/docs/macro_tutorial.rst b/docs/macro_tutorial.rst index c8b38b3f..8a19df68 100644 --- a/docs/macro_tutorial.rst +++ b/docs/macro_tutorial.rst @@ -3466,7 +3466,7 @@ Say you want to find the first word containing a lowercase "z" in some strings: ... ('The quick brown fox jumps over the lazy dog!')) found: lazy -A simple regex worked. +A simple regex worked. Not. .. code-block:: REPL @@ -3477,13 +3477,13 @@ A simple regex worked. ... AttributeError: 'NoneType' object has no attribute 'group' -And we've found a problem. +We've already found a problem. Python's regex functions return ``None`` instead of a useful empty match object when no match was found, and a ``NoneType`` has no such method. Some questionable design decisions there. On several levels. We need to check if a match exists before we know it's safe to print what it found. -Let's make a fix: +Let's fix that: .. Lissp:: @@ -3538,12 +3538,13 @@ Let's make a fix: Well, at least it's not an error this time. But this definition duplicates the code for the search; it's not DRY. -It's also duplicating work of searching when run. +It's also duplicating the work of searching when run. If the search function was pure and memoized calling it again would actually be OK, performance-wise. That would be the norm in Haskell, -but in Python you'd have to ask for memoization explicitly. -(Using `functools.cache`, say.) +but in Python you'd have to ask for memoization explicitly +(using `functools.cache`, say). + Performance often isn't that big of a deal. Unless you're being really egregiously wasteful, it usually only matters in bottlenecks, @@ -3551,14 +3552,15 @@ which usually means inside nested loops. One can get a sense for these things, but it's easy to waste a lot of programmer time on pointless micro-optimizations not on the critical path. Programmer time is a lot more expensive than CPU time. -(This wasn't always the case, but modern computers are pretty fast.) +This wasn't always the case, but modern computers are pretty fast. When it matters, profile first. The more important consideration here is *readability*. Sometimes a terse implementation is the clearest name, but in this case, it's hard to tell if both expressions really are the same. It's easy to gloss over the regex pattern. -It's not too bad when they're on adjacent lines like this, +These are fairly short and +it's not too bad when they're on adjacent lines like this, but if you extract it to a local, you won't have to check: .. Lissp:: @@ -3603,22 +3605,21 @@ but if you extract it to a local, you won't have to check: .. code-block:: REPL - #> (find-z-word "The quick brown fox jumps over the lazy dog!") - >>> findQzH_zQzH_word( - ... ('The quick brown fox jumps over the lazy dog!')) + #> (progn (find-z-word "The lazy dog.") (find-z-word "The sleeping dog.")) + >>> # progn + ... (findQzH_zQzH_word( + ... ('The lazy dog.')), + ... findQzH_zQzH_word( + ... ('The sleeping dog.'))) [-1] found: lazy - - #> (find-z-word "The quick brown fox jumps over the sleeping dog!") - >>> findQzH_zQzH_word( - ... ('The quick brown fox jumps over the sleeping dog!')) () Sometimes you want to check if something exists, and only act in that case. -Short examples may feel contrived, +Short examples like these may feel contrived, but this pattern does come up enough that languages have special ways of dealing with it. -Hissp, of course, can copy such ways with macros. +Hissp, of course, can copy such ways with metaprogramming. ``let-when`` :::::::::::: @@ -3665,7 +3666,7 @@ We want a macro to expand to the previous code. The Lissp definition of ``find-z-word`` will be a bit nicer this way than before, but just a bit. Clojure's equivalent is called ``when-let``, -which is, of course, obviously backwards once you see the implementation. +which is, of course, obviously backwards now that we've seen the implementation. But it does perhaps roll of the tongue a little better, and may be more consistent with the names of other macros that aren't so simple. @@ -3748,7 +3749,7 @@ We can do this using `macroexpand1`: The pretty-printing makes it a lot easier to read. This is what we want: a `let` containing a `when`. It's close to what we wrote ourselves, -plus the :term:`fully-qualified identifiers` for extra robustness. +plus the :term:`fully-qualified identifier`\ s for extra robustness. If you're still in a subREPL of some other module, its `__name__` will appear as the qualifier here instead of ``__main__``. @@ -3781,7 +3782,7 @@ so the `let` would get expanded as well: ('print', "('found:')", ('.group', 'match', 0)))),) The resulting form is no longer a :term:`macro form`, -but it does contain one as a subform. +but it does contain one (the `when`) as a subform. `macroexpand_all` will expand subforms as well: .. code-block:: REPL @@ -3818,14 +3819,13 @@ We can confirm the new function behaves as before: .. code-block:: REPL - #> (find-z-word "The quick brown fox jumps over the lazy dog!") - >>> findQzH_zQzH_word( - ... ('The quick brown fox jumps over the lazy dog!')) + #> (progn (find-z-word "The lazy dog.") (find-z-word "The sleeping dog.")) + >>> # progn + ... (findQzH_zQzH_word( + ... ('The lazy dog.')), + ... findQzH_zQzH_word( + ... ('The sleeping dog.'))) [-1] found: lazy - - #> (find-z-word "The quick brown fox jumps over the sleeping dog!") - >>> findQzH_zQzH_word( - ... ('The quick brown fox jumps over the sleeping dog!')) () Anaphors @@ -3914,103 +3914,826 @@ An anaphoric macro can make this even more concise: .. code-block:: REPL - #> (find-z-word "The quick brown fox jumps over the lazy dog!") - >>> findQzH_zQzH_word( - ... ('The quick brown fox jumps over the lazy dog!')) + #> (progn (find-z-word "The lazy dog.") (find-z-word "The sleeping dog.")) + >>> # progn + ... (findQzH_zQzH_word( + ... ('The lazy dog.')), + ... findQzH_zQzH_word( + ... ('The sleeping dog.'))) [-1] found: lazy - - #> (find-z-word "The quick brown fox jumps over the sleeping dog!") - >>> findQzH_zQzH_word( - ... ('The quick brown fox jumps over the sleeping dog!')) () But now you have no choice about the name. What if you already had an ``it`` in scope? The way lexical scoping works, the innermost one will shadow the outer, making the outer one inaccessible. -Rename the outer ``it``? + +Can we rename the outer ``it``? If the outer ``it`` came from another anaphoric macro (like another ``awhen``), then it's not as simple as changing a symbol. Being insulated from the details isn't always a good thing! You'd have to use a ``let`` or something like that to rename the outer ``it`` and avoid the conflict, but at that point, you might as well use ``let-when`` instead. -Explicit Namespace -:::::::::::::::::: +Explicit Scoping +:::::::::::::::: + +Suppose we want to do something else if a match isn't found. +We'd want to use `if-else` instead of `when`. +But we don't have a ``let-if-else`` or an ``aif-else``. +They're not too hard to implement, +but there are many other macros that could use a ``let-`` or anaphoric variant. +Python has a more general solution: the "walrus" operator `:= `. + +While it's possible to use that in Hissp (like any Python expression), +it would require a :term:`Python injection`, +which is not recommended. +In :term:`standard` Hissp, locals can be considered single assignment; +you can shadow them, but can't reassign. +A walrus operator used inside a lambda creates a local lexically scoped to that lambda. +Nonlocal reads can work, but not nonlocal assignments. +Lambdas are common in macroexpansions, +which makes the walrus hard to use in Hissp. + +No matter. +Python didn't have `nonlocal` until version 3.0, +and didn't have the walrus until 3.8. +If you needed nonlocal semantics in Python 2, +the usual workaround would be to use an explicit scope. +We can do the same thing in Hissp: -Suppose we want an additional constraint that is difficult to express in pure regex. +.. Lissp:: + + #> (defun find-z-word (text) + #.. (let (scope (types..SimpleNamespace)) + #.. (if-else (set@ scope.match (re..search '|\b\w*z\w*\b| text)) + #.. (print "found:" (.group scope.match 0)) + #.. (print "not found")))) + >>> # defun + ... # hissp.macros.._macro_.define + ... __import__('builtins').globals().update( + ... findQzH_zQzH_word=# hissp.macros.._macro_.fun + ... # hissp.macros.._macro_.let + ... ( + ... lambda _Qztbhvvkna__lambda=(lambda text: + ... # let + ... (lambda scope=__import__('types').SimpleNamespace(): + ... # ifQzH_else + ... (lambda b, c, a: c()if b else a())( + ... # setQzAT_ + ... # hissp.macros.._macro_.let + ... ( + ... lambda _Qzald6dagb__value=__import__('re').search( + ... '\\b\\w*z\\w*\\b', + ... text): + ... (# hissp.macros.._macro_.define + ... __import__('builtins').setattr( + ... scope, + ... 'match', + ... _Qzald6dagb__value), + ... _Qzald6dagb__value) [-1] + ... )(), + ... (lambda : + ... print( + ... ('found:'), + ... scope.match.group( + ... (0))) + ... ), + ... (lambda : + ... print( + ... ('not found')) + ... )) + ... )() + ... ): + ... (( + ... *__import__('itertools').starmap( + ... _Qztbhvvkna__lambda.__setattr__, + ... __import__('builtins').dict( + ... __name__='findQzH_zQzH_word', + ... __qualname__='findQzH_zQzH_word', + ... __code__=_Qztbhvvkna__lambda.__code__.replace( + ... co_name='findQzH_zQzH_word')).items()), + ... ), + ... _Qztbhvvkna__lambda) [-1] + ... )()) + +.. code-block:: REPL + + #> (progn (find-z-word "The lazy dog.") (find-z-word "The sleeping dog.")) + >>> # progn + ... (findQzH_zQzH_word( + ... ('The lazy dog.')), + ... findQzH_zQzH_word( + ... ('The sleeping dog.'))) [-1] + found: lazy + not found + +The ``scope`` variable is a normal local with lexical scope, +but its ``.match`` attribute lives in a `types.SimpleNamespace` object, +which is an explicit scope. +Assignments can be written anywhere that has access to that namespace +(including any nested lexical scopes that might appear in a macroexpansion) +and reassignment is possible, unlike locals in standard Hissp. +This is more powerful, +but also potentially more confusing. +Even the Python community discourages the overuse of its walrus operator. +The explicit scope isn't really better than using a local directly here, +but it gives us a more general pattern which we can expand to. + +For example: .. Lissp:: -(defun try-patterns (text : :* patterns) - (-<>> patterns (map X#(re..search X text) :<>) (filter X#X) (next :<> None)) - (next (filter X#X (map X#(re..search X text) patterns) _#/filter) None)) + #> (defmacro it-is\# x `(set@ ,'scope.it ,x)) + >>> # defmacro + ... __import__('builtins').setattr( + ... __import__('builtins').globals().get( + ... ('_macro_')), + ... 'itQzH_isQzHASH_', + ... # hissp.macros.._macro_.fun + ... # hissp.macros.._macro_.let + ... ( + ... lambda _Qztbhvvkna__lambda=(lambda x: + ... ( + ... '__main__.._macro_.setQzAT_', + ... 'scope.it', + ... x, + ... ) + ... ): + ... (( + ... *__import__('itertools').starmap( + ... _Qztbhvvkna__lambda.__setattr__, + ... __import__('builtins').dict( + ... __name__='itQzH_isQzHASH_', + ... __qualname__='_macro_.itQzH_isQzHASH_', + ... __code__=_Qztbhvvkna__lambda.__code__.replace( + ... co_name='itQzH_isQzHASH_')).items()), + ... ), + ... _Qztbhvvkna__lambda) [-1] + ... )()) + -Types..SimpleNamespace + #> (defun find-z-word (text) + #.. (let (scope (types..SimpleNamespace)) + #.. (if-else it-is#(re..search '|\b\w*z\w*\b| text) + #.. (print "found:" (.group scope.it 0)) + #.. (print "not found")))) + >>> # defun + ... # hissp.macros.._macro_.define + ... __import__('builtins').globals().update( + ... findQzH_zQzH_word=# hissp.macros.._macro_.fun + ... # hissp.macros.._macro_.let + ... ( + ... lambda _Qztbhvvkna__lambda=(lambda text: + ... # let + ... (lambda scope=__import__('types').SimpleNamespace(): + ... # ifQzH_else + ... (lambda b, c, a: c()if b else a())( + ... # __main__.._macro_.setQzAT_ + ... # hissp.macros.._macro_.let + ... ( + ... lambda _Qzald6dagb__value=__import__('re').search( + ... '\\b\\w*z\\w*\\b', + ... text): + ... (# hissp.macros.._macro_.define + ... __import__('builtins').setattr( + ... scope, + ... 'it', + ... _Qzald6dagb__value), + ... _Qzald6dagb__value) [-1] + ... )(), + ... (lambda : + ... print( + ... ('found:'), + ... scope.it.group( + ... (0))) + ... ), + ... (lambda : + ... print( + ... ('not found')) + ... )) + ... )() + ... ): + ... (( + ... *__import__('itertools').starmap( + ... _Qztbhvvkna__lambda.__setattr__, + ... __import__('builtins').dict( + ... __name__='findQzH_zQzH_word', + ... __qualname__='findQzH_zQzH_word', + ... __code__=_Qztbhvvkna__lambda.__code__.replace( + ... co_name='findQzH_zQzH_word')).items()), + ... ), + ... _Qztbhvvkna__lambda) [-1] + ... )()) -the# -:::: +.. code-block:: REPL -(defmacro the\# (expr) - (let (,'the (types..SimpleNamespace)) - ,expr)) + #> (progn (find-z-word "The lazy dog.") (find-z-word "The sleeping dog.")) + >>> # progn + ... (findQzH_zQzH_word( + ... ('The lazy dog.')), + ... findQzH_zQzH_word( + ... ('The sleeping dog.'))) [-1] + found: lazy + not found -(defmacro it\# (expr) - `(set@ ,'the.it ,expr)) +We hardcoded the ``scope`` anaphor in the ``it-is#`` definition above. -(defmacro the\# (expr) - (let (,'the (types..SimpleNamespace)) - ,(kwarg->set@ expr))) -Wishful thinking again. +Because the name ``scope`` is always the same, +we could also reduce the `let` form to a tag with a single argument (its body): -(defun kwarg->set@ (expr) - (cond (isinstance expr hissp.reader..Kwarg) `(set@ ,|f"my.{expr.k}"| ,expr.v) - (H#is_node expr) `(,@(map kwarg->set@ expr)) - :else expr)) +.. Lissp:: + #> (defmacro scope\# (expr) + #.. `(let (,'scope (types..SimpleNamespace)) + #.. ,expr)) + >>> # defmacro + ... __import__('builtins').setattr( + ... __import__('builtins').globals().get( + ... ('_macro_')), + ... 'scopeQzHASH_', + ... # hissp.macros.._macro_.fun + ... # hissp.macros.._macro_.let + ... ( + ... lambda _Qztbhvvkna__lambda=(lambda expr: + ... ( + ... '__main__.._macro_.let', + ... ( + ... 'scope', + ... ( + ... 'types..SimpleNamespace', + ... ), + ... ), + ... expr, + ... ) + ... ): + ... (( + ... *__import__('itertools').starmap( + ... _Qztbhvvkna__lambda.__setattr__, + ... __import__('builtins').dict( + ... __name__='scopeQzHASH_', + ... __qualname__='_macro_.scopeQzHASH_', + ... __code__=_Qztbhvvkna__lambda.__code__.replace( + ... co_name='scopeQzHASH_')).items()), + ... ), + ... _Qztbhvvkna__lambda) [-1] + ... )()) + + + #> (defun find-z-word (text) + #.. scope#(if-else it-is#(re..search '|\b\w*z\w*\b| text) + #.. (print "found:" (.group scope.it 0)) + #.. (print "not found"))) + >>> # defun + ... # hissp.macros.._macro_.define + ... __import__('builtins').globals().update( + ... findQzH_zQzH_word=# hissp.macros.._macro_.fun + ... # hissp.macros.._macro_.let + ... ( + ... lambda _Qztbhvvkna__lambda=(lambda text: + ... # __main__.._macro_.let + ... (lambda scope=__import__('types').SimpleNamespace(): + ... # ifQzH_else + ... (lambda b, c, a: c()if b else a())( + ... # __main__.._macro_.setQzAT_ + ... # hissp.macros.._macro_.let + ... ( + ... lambda _Qzald6dagb__value=__import__('re').search( + ... '\\b\\w*z\\w*\\b', + ... text): + ... (# hissp.macros.._macro_.define + ... __import__('builtins').setattr( + ... scope, + ... 'it', + ... _Qzald6dagb__value), + ... _Qzald6dagb__value) [-1] + ... )(), + ... (lambda : + ... print( + ... ('found:'), + ... scope.it.group( + ... (0))) + ... ), + ... (lambda : + ... print( + ... ('not found')) + ... )) + ... )() + ... ): + ... (( + ... *__import__('itertools').starmap( + ... _Qztbhvvkna__lambda.__setattr__, + ... __import__('builtins').dict( + ... __name__='findQzH_zQzH_word', + ... __qualname__='findQzH_zQzH_word', + ... __code__=_Qztbhvvkna__lambda.__code__.replace( + ... co_name='findQzH_zQzH_word')).items()), + ... ), + ... _Qztbhvvkna__lambda) [-1] + ... )()) + +.. code-block:: REPL + + #> (progn (find-z-word "The lazy dog.") (find-z-word "The sleeping dog.")) + >>> # progn + ... (findQzH_zQzH_word( + ... ('The lazy dog.')), + ... findQzH_zQzH_word( + ... ('The sleeping dog.'))) [-1] + found: lazy + not found + +This pair of tags can function as many common anaphoric-variant macros +that only need a single anaphor, +including ``awhen``, ``acond``, ``aand``, etc. + +``the#`` +:::::::: + +The ``it-is#`` tag above only assigns to ``scope.it``, +which is still not as general as Python's walrus. +Rather than creating a new tag for each name we might want, +we could generalize this to any name with a binary tag that takes the identifier as its first argument. + +But we have an even better option. +``it-is#`` only makes sense inside of ``scope#``'s first argument, +which means we can use a code-walking metaprogram to rewrite the expression. +We could use control words instead of tags, for example. +Many other other macros use control words (not to mention lambdas and normal call syntax), +and so we'd want to avoid interfering with those uses. +Perhaps by using a naming convention +(ending in an ``=`` character, say). + +This suggests an even better alternative, +at least in Lissp: :term:`kwarg token`\ s. +They're already paired with an argument, +so we won't have to figure that part out while code walking. +They have a name we can use for the assignment. +They won't interfere with control words. +`Kwarg` objects are really only meant for use at :term:`read time`, +but we can write tag metaprograms, which run at read time. +Nested tags using them directly will be evaluated first, +so those won't interfere either. +Let's try that. + +.. Lissp:: + + #> (defmacro the\# (expr) + #.. `(let (,'the (types..SimpleNamespace)) + #.. ,(kwarg->set@ expr))) + >>> # defmacro + ... __import__('builtins').setattr( + ... __import__('builtins').globals().get( + ... ('_macro_')), + ... 'theQzHASH_', + ... # hissp.macros.._macro_.fun + ... # hissp.macros.._macro_.let + ... ( + ... lambda _Qztbhvvkna__lambda=(lambda expr: + ... ( + ... '__main__.._macro_.let', + ... ( + ... 'the', + ... ( + ... 'types..SimpleNamespace', + ... ), + ... ), + ... kwargQzH_QzGT_setQzAT_( + ... expr), + ... ) + ... ): + ... (( + ... *__import__('itertools').starmap( + ... _Qztbhvvkna__lambda.__setattr__, + ... __import__('builtins').dict( + ... __name__='theQzHASH_', + ... __qualname__='_macro_.theQzHASH_', + ... __code__=_Qztbhvvkna__lambda.__code__.replace( + ... co_name='theQzHASH_')).items()), + ... ), + ... _Qztbhvvkna__lambda) [-1] + ... )()) + +This is basically our ``scope#`` tag, +plus some design by wishful thinking again. +We still need to define the helper function to do the actual rewrite: + +.. Lissp:: + + #> (defun kwarg->set@ (expr) + #.. (cond (isinstance expr hissp.reader..Kwarg) `(set@ ,(.format "the.{}" (H#munge expr.k)) + #.. ,expr.v) + #.. (H#is_node expr) `(,@(map kwarg->set@ expr)) + #.. :else expr)) + >>> # defun + ... # hissp.macros.._macro_.define + ... __import__('builtins').globals().update( + ... kwargQzH_QzGT_setQzAT_=# hissp.macros.._macro_.fun + ... # hissp.macros.._macro_.let + ... ( + ... lambda _Qztbhvvkna__lambda=(lambda expr: + ... # cond + ... (lambda x0, x1, x2, x3, x4, x5: + ... x1() if x0 + ... else x3() if x2() + ... else x5() if x4() + ... else () + ... )( + ... isinstance( + ... expr, + ... __import__('hissp.reader',fromlist='*').Kwarg), + ... (lambda : + ... ( + ... '__main__.._macro_.setQzAT_', + ... ('the.{}').format( + ... __import__('hissp').munge( + ... expr.k)), + ... expr.v, + ... ) + ... ), + ... (lambda : + ... __import__('hissp').is_node( + ... expr) + ... ), + ... (lambda : + ... ( + ... *map( + ... kwargQzH_QzGT_setQzAT_, + ... expr), + ... ) + ... ), + ... (lambda : ':else'), + ... (lambda : expr)) + ... ): + ... (( + ... *__import__('itertools').starmap( + ... _Qztbhvvkna__lambda.__setattr__, + ... __import__('builtins').dict( + ... __name__='kwargQzH_QzGT_setQzAT_', + ... __qualname__='kwargQzH_QzGT_setQzAT_', + ... __code__=_Qztbhvvkna__lambda.__code__.replace( + ... co_name='kwargQzH_QzGT_setQzAT_')).items()), + ... ), + ... _Qztbhvvkna__lambda) [-1] + ... )()) + +Syntax trees are recursive data structures. We saw this kind of recursive approach before with ``flatten``. But this isn't just for reading the tree. It rebuilds it. +There are only three cases to worry about: +if it's a `Kwarg` object, we substitute the `set@` expression; +if it `is_node`, we recurse and reconstruct the tuple; +else it's just an atom and we give it back. -[example use] +.. Lissp:: + + #> (defun find-z-word (text) + #.. the#(if-else match=(re..search '|\b\w*z\w*\b| text) + #.. (print "found:" (.group the.match 0)) + #.. (print "not found"))) + >>> # defun + ... # hissp.macros.._macro_.define + ... __import__('builtins').globals().update( + ... findQzH_zQzH_word=# hissp.macros.._macro_.fun + ... # hissp.macros.._macro_.let + ... ( + ... lambda _Qztbhvvkna__lambda=(lambda text: + ... # __main__.._macro_.let + ... (lambda the=__import__('types').SimpleNamespace(): + ... # ifQzH_else + ... (lambda b, c, a: c()if b else a())( + ... # __main__.._macro_.setQzAT_ + ... # hissp.macros.._macro_.let + ... ( + ... lambda _Qzald6dagb__value=__import__('re').search( + ... '\\b\\w*z\\w*\\b', + ... text): + ... (# hissp.macros.._macro_.define + ... __import__('builtins').setattr( + ... the, + ... 'match', + ... _Qzald6dagb__value), + ... _Qzald6dagb__value) [-1] + ... )(), + ... (lambda : + ... print( + ... ('found:'), + ... the.match.group( + ... (0))) + ... ), + ... (lambda : + ... print( + ... ('not found')) + ... )) + ... )() + ... ): + ... (( + ... *__import__('itertools').starmap( + ... _Qztbhvvkna__lambda.__setattr__, + ... __import__('builtins').dict( + ... __name__='findQzH_zQzH_word', + ... __qualname__='findQzH_zQzH_word', + ... __code__=_Qztbhvvkna__lambda.__code__.replace( + ... co_name='findQzH_zQzH_word')).items()), + ... ), + ... _Qztbhvvkna__lambda) [-1] + ... )()) + +.. code-block:: REPL + + #> (progn (find-z-word "The lazy dog.") (find-z-word "The sleeping dog.")) + >>> # progn + ... (findQzH_zQzH_word( + ... ('The lazy dog.')), + ... findQzH_zQzH_word( + ... ('The sleeping dog.'))) [-1] + found: lazy + not found Very powerful. -Also easy to abuse. +Also easy to (ab)use. +You can save the result of any subexpression to the namespace. +You can reassign names you've already used. +It's a lot like the walrus, but the tag (``the#``) explicitly delimits the scope. + There was some reluctance around adding the walrus to Python. But it obviates the need for many anaphoric macros by itself. +And now Hissp has that capability too. Actually, the bundled `my#` tag does what ``the#`` can and more, -but the implementation is a bit more involved because it has more features. +but the implementation is a bit more involved because of the additional features. lazy polar :::::::::: -Yeah a separate function could work (or a classmethod constructor) but that's no fun. +Yeah a separate function could work (or a `classmethod` constructor) but that's no fun. but there is a fairly straightforward approach that can work in general and that's laziness. -(deftupleonce polar '(x y r θ)) +.. Lissp:: -(defun C (: x None y None :* :? r None theta None) - my#(progn - x=(if-else (is_not None x) O#x O#(mul r (math..cos theta))) - y=(if-else (is_not None y) O#y O#(mul r (math..sin theta))) - (complex (my.x) (my.y)))) + #> (deftupleonce polar '(x y r θ)) + >>> # deftupleonce + ... # hissp.macros.._macro_.defonce + ... # hissp.macros.._macro_.unless + ... (lambda b, a: ()if b else a())( + ... __import__('operator').contains( + ... __import__('builtins').globals(), + ... 'polar'), + ... (lambda : + ... # hissp.macros.._macro_.define + ... __import__('builtins').globals().update( + ... polar=__import__('collections').namedtuple( + ... 'polar', + ... ('x', + ... 'y', + ... 'r', + ... 'θ',))) + ... )) + + + #> (defun C (: x None y None :* :? r None theta None) + #.. my#(progn + #.. x=(if-else (is_not None x) O#x O#(mul r (math..cos theta))) + #.. y=(if-else (is_not None y) O#y O#(mul r (math..sin theta))) + #.. (complex (my.x) (my.y)))) + >>> # defun + ... # hissp.macros.._macro_.define + ... __import__('builtins').globals().update( + ... C=# hissp.macros.._macro_.fun + ... # hissp.macros.._macro_.let + ... ( + ... lambda _Qztbhvvkna__lambda=( + ... lambda x=None, + ... y=None, + ... *, + ... r=None, + ... theta=None: + ... # hissp.macros.._macro_.let + ... (lambda my=__import__('types').SimpleNamespace(): + ... # progn + ... (# hissp.macros.._macro_.setQzAT_ + ... # hissp.macros.._macro_.let + ... ( + ... lambda _Qzald6dagb__value=# ifQzH_else + ... (lambda b, c, a: c()if b else a())( + ... is_not( + ... None, + ... x), + ... (lambda : (lambda : x)), + ... (lambda : + ... (lambda : + ... mul( + ... r, + ... __import__('math').cos( + ... theta)) + ... ) + ... )): + ... (# hissp.macros.._macro_.define + ... __import__('builtins').setattr( + ... my, + ... 'x', + ... _Qzald6dagb__value), + ... _Qzald6dagb__value) [-1] + ... )(), + ... # hissp.macros.._macro_.setQzAT_ + ... # hissp.macros.._macro_.let + ... ( + ... lambda _Qzald6dagb__value=# ifQzH_else + ... (lambda b, c, a: c()if b else a())( + ... is_not( + ... None, + ... y), + ... (lambda : (lambda : y)), + ... (lambda : + ... (lambda : + ... mul( + ... r, + ... __import__('math').sin( + ... theta)) + ... ) + ... )): + ... (# hissp.macros.._macro_.define + ... __import__('builtins').setattr( + ... my, + ... 'y', + ... _Qzald6dagb__value), + ... _Qzald6dagb__value) [-1] + ... )(), + ... complex( + ... my.x(), + ... my.y())) [-1] + ... )() + ... ): + ... (( + ... *__import__('itertools').starmap( + ... _Qztbhvvkna__lambda.__setattr__, + ... __import__('builtins').dict( + ... __name__='C', + ... __qualname__='C', + ... __code__=_Qztbhvvkna__lambda.__code__.replace( + ... co_name='C')).items()), + ... ), + ... _Qztbhvvkna__lambda) [-1] + ... )()) We can generalize this approach to all the arguments. Notice that ``θ`` and ``theta`` refer to the same thing. You can pass it in either way. -(defun C (:* : x None y None r None θ None theta None) - my#(progn - x=(if-else (is_not None x) O#x O#(mul (my.r) (math..cos (my.theta)))) - y=(if-else (is_not None y) O#y O#(mul (my.r) (math..sin (my.theta)))) - r=(if-else (is_not None r) O#r O#|(my.x()**2 + my.y()**2)**.5|) - θ=(if-else (is_not None θ) O#θ O#(math..atan2 (my.y) (my.x))) - theta=(if-else (is_not None theta) O#theta O#(my.θ)) - (polar (my.x) (my.y) (my.r) (my.theta)))) +.. Lissp:: -Adding lazyness like that to a language that's not built for it gets pretty verbose, doesn't it? -It would be even worse in Python where we don't even have the `O#` tag for thunks. + #> (defun C (:* : x None y None r None θ None theta None) + #.. my#(progn + #.. x=(if-else (is_not None x) O#x O#(mul (my.r) (math..cos (my.theta)))) + #.. y=(if-else (is_not None y) O#y O#(mul (my.r) (math..sin (my.theta)))) + #.. r=(if-else (is_not None r) O#r O#|(my.x()**2 + my.y()**2)**.5|) + #.. θ=(if-else (is_not None θ) O#θ O#(math..atan2 (my.y) (my.x))) + #.. theta=(if-else (is_not None theta) O#theta O#(my.θ)) + #.. (polar (my.x) (my.y) (my.r) (my.theta)))) + >>> # defun + ... # hissp.macros.._macro_.define + ... __import__('builtins').globals().update( + ... C=# hissp.macros.._macro_.fun + ... # hissp.macros.._macro_.let + ... ( + ... lambda _Qztbhvvkna__lambda=( + ... lambda *, + ... x=None, + ... y=None, + ... r=None, + ... θ=None, + ... theta=None: + ... # hissp.macros.._macro_.let + ... (lambda my=__import__('types').SimpleNamespace(): + ... # progn + ... (# hissp.macros.._macro_.setQzAT_ + ... # hissp.macros.._macro_.let + ... ( + ... lambda _Qzald6dagb__value=# ifQzH_else + ... (lambda b, c, a: c()if b else a())( + ... is_not( + ... None, + ... x), + ... (lambda : (lambda : x)), + ... (lambda : + ... (lambda : + ... mul( + ... my.r(), + ... __import__('math').cos( + ... my.theta())) + ... ) + ... )): + ... (# hissp.macros.._macro_.define + ... __import__('builtins').setattr( + ... my, + ... 'x', + ... _Qzald6dagb__value), + ... _Qzald6dagb__value) [-1] + ... )(), + ... # hissp.macros.._macro_.setQzAT_ + ... # hissp.macros.._macro_.let + ... ( + ... lambda _Qzald6dagb__value=# ifQzH_else + ... (lambda b, c, a: c()if b else a())( + ... is_not( + ... None, + ... y), + ... (lambda : (lambda : y)), + ... (lambda : + ... (lambda : + ... mul( + ... my.r(), + ... __import__('math').sin( + ... my.theta())) + ... ) + ... )): + ... (# hissp.macros.._macro_.define + ... __import__('builtins').setattr( + ... my, + ... 'y', + ... _Qzald6dagb__value), + ... _Qzald6dagb__value) [-1] + ... )(), + ... # hissp.macros.._macro_.setQzAT_ + ... # hissp.macros.._macro_.let + ... ( + ... lambda _Qzald6dagb__value=# ifQzH_else + ... (lambda b, c, a: c()if b else a())( + ... is_not( + ... None, + ... r), + ... (lambda : (lambda : r)), + ... (lambda : (lambda : (my.x()**2 + my.y()**2)**.5))): + ... (# hissp.macros.._macro_.define + ... __import__('builtins').setattr( + ... my, + ... 'r', + ... _Qzald6dagb__value), + ... _Qzald6dagb__value) [-1] + ... )(), + ... # hissp.macros.._macro_.setQzAT_ + ... # hissp.macros.._macro_.let + ... ( + ... lambda _Qzald6dagb__value=# ifQzH_else + ... (lambda b, c, a: c()if b else a())( + ... is_not( + ... None, + ... θ), + ... (lambda : (lambda : θ)), + ... (lambda : + ... (lambda : + ... __import__('math').atan2( + ... my.y(), + ... my.x()) + ... ) + ... )): + ... (# hissp.macros.._macro_.define + ... __import__('builtins').setattr( + ... my, + ... 'θ', + ... _Qzald6dagb__value), + ... _Qzald6dagb__value) [-1] + ... )(), + ... # hissp.macros.._macro_.setQzAT_ + ... # hissp.macros.._macro_.let + ... ( + ... lambda _Qzald6dagb__value=# ifQzH_else + ... (lambda b, c, a: c()if b else a())( + ... is_not( + ... None, + ... theta), + ... (lambda : (lambda : theta)), + ... (lambda : (lambda : my.θ()))): + ... (# hissp.macros.._macro_.define + ... __import__('builtins').setattr( + ... my, + ... 'theta', + ... _Qzald6dagb__value), + ... _Qzald6dagb__value) [-1] + ... )(), + ... polar( + ... my.x(), + ... my.y(), + ... my.r(), + ... my.theta())) [-1] + ... )() + ... ): + ... (( + ... *__import__('itertools').starmap( + ... _Qztbhvvkna__lambda.__setattr__, + ... __import__('builtins').dict( + ... __name__='C', + ... __qualname__='C', + ... __code__=_Qztbhvvkna__lambda.__code__.replace( + ... co_name='C')).items()), + ... ), + ... _Qztbhvvkna__lambda) [-1] + ... )()) + +Adding laziness like that to a language that's not built for it gets pretty verbose, doesn't it? +It would be even worse in Python where we don't even have the +`O#` tag for thunks. Keyboard interrupt, right? @@ -4026,12 +4749,49 @@ It might be nice This is a recursive find-and-replace task again by using macroexpand -(defmacro smacrolet (name expansion : :* body) - (H#macroexpand_all - `(progn ,@body) - : postprocess X#(if-else (op#eq X name) - expansion - X))) +.. Lissp:: + + #> (defmacro smacrolet (name expansion : :* body) + #.. (H#macroexpand_all + #.. `(progn ,@body) + #.. : postprocess X#(if-else (op#eq X name) + #.. expansion + #.. X))) + >>> # defmacro + ... __import__('builtins').setattr( + ... __import__('builtins').globals().get( + ... ('_macro_')), + ... 'smacrolet', + ... # hissp.macros.._macro_.fun + ... # hissp.macros.._macro_.let + ... ( + ... lambda _Qztbhvvkna__lambda=(lambda name, expansion, *body: + ... __import__('hissp').macroexpand_all( + ... ( + ... '__main__.._macro_.progn', + ... *body, + ... ), + ... postprocess=(lambda X: + ... # ifQzH_else + ... (lambda b, c, a: c()if b else a())( + ... __import__('operator').eq( + ... X, + ... name), + ... (lambda : expansion), + ... (lambda : X)) + ... )) + ... ): + ... (( + ... *__import__('itertools').starmap( + ... _Qztbhvvkna__lambda.__setattr__, + ... __import__('builtins').dict( + ... __name__='smacrolet', + ... __qualname__='_macro_.smacrolet', + ... __code__=_Qztbhvvkna__lambda.__code__.replace( + ... co_name='smacrolet')).items()), + ... ), + ... _Qztbhvvkna__lambda) [-1] + ... )()) The problem is attribute access. That's the problem with injecting Python. @@ -4045,17 +4805,92 @@ even if it's unreasonable to expect it to handle Python expressions in general. We can check for exactly that case, and rewrite it to a let expression. -(defmacro smacrolet (name expansion : :* body) - (H#macroexpand_all - `(progn ,@body) - : postprocess X#(cond (eq X name) expansion - ;; else if - (ands (H#is_symbol X) - (.startswith X (concat name ".")) - (not (H#is_import X))) - `(let ($#name ,expansion) - ,(.format "{}.{}" '$#name !##-1(.partition name "."))) - :else X))) +.. Lissp:: + + #> (defmacro smacrolet (name expansion : :* body) + #.. (H#macroexpand_all + #.. `(progn ,@body) + #.. : postprocess X#(cond (eq X name) expansion + #.. ;; else if + #.. (ands (H#is_symbol X) + #.. (.startswith X (concat name ".")) + #.. (not (H#is_import X))) + #.. `(let ($#name ,expansion) + #.. ,(.format "{}.{}" '$#name !##-1(.partition name "."))) + #.. :else X))) + >>> # defmacro + ... __import__('builtins').setattr( + ... __import__('builtins').globals().get( + ... ('_macro_')), + ... 'smacrolet', + ... # hissp.macros.._macro_.fun + ... # hissp.macros.._macro_.let + ... ( + ... lambda _Qztbhvvkna__lambda=(lambda name, expansion, *body: + ... __import__('hissp').macroexpand_all( + ... ( + ... '__main__.._macro_.progn', + ... *body, + ... ), + ... postprocess=(lambda X: + ... # cond + ... (lambda x0, x1, x2, x3, x4, x5: + ... x1() if x0 + ... else x3() if x2() + ... else x5() if x4() + ... else () + ... )( + ... eq( + ... X, + ... name), + ... (lambda : expansion), + ... (lambda : + ... # ands + ... (lambda x0, x1, x2: x0 and x1()and x2())( + ... __import__('hissp').is_symbol( + ... X), + ... (lambda : + ... X.startswith( + ... concat( + ... name, + ... ('.'))) + ... ), + ... (lambda : + ... not( + ... __import__('hissp').is_import( + ... X)) + ... )) + ... ), + ... (lambda : + ... ( + ... '__main__.._macro_.let', + ... ( + ... '_Qzapo453kp__name', + ... expansion, + ... ), + ... ('{}.{}').format( + ... '_Qzapo453kp__name', + ... __import__('operator').itemgetter( + ... (-1))( + ... name.partition( + ... ('.')))), + ... ) + ... ), + ... (lambda : ':else'), + ... (lambda : X)) + ... )) + ... ): + ... (( + ... *__import__('itertools').starmap( + ... _Qztbhvvkna__lambda.__setattr__, + ... __import__('builtins').dict( + ... __name__='smacrolet', + ... __qualname__='_macro_.smacrolet', + ... __code__=_Qztbhvvkna__lambda.__code__.replace( + ... co_name='smacrolet')).items()), + ... ), + ... _Qztbhvvkna__lambda) [-1] + ... )()) deflazyfun