.. Copyright 2019, 2020, 2021, 2022 Matthew Egan Odendahl
SPDX-License-Identifier: CC-BY-SA-4.0
.. Hidden doctest requires basic macros for REPL-consistent behavior.
#> (operator..setitem (globals) '_macro_ (types..SimpleNamespace : :** (vars hissp.basic.._macro_)))
>>> __import__('operator').setitem(
... globals(),
... '_macro_',
... __import__('types').SimpleNamespace(
... **vars(
... __import__('hissp.basic',fromlist='?')._macro_)))
.. TODO: Sphinx update messed up my sidebars! Is there a better fix?
.. raw:: html
FAQ
===
(Frequently Anticipated Questions (and complaints))
Anticipated? Didn't you mean "asked"?
-------------------------------------
Well, this project is still pretty new.
Why should I use Hissp?
-----------------------
Any sufficiently complicated C or Fortran program contains an ad hoc,
informally-specified, bug-ridden, slow implementation of half of Common Lisp.
— Greenspun's Tenth Rule
Because you want the highest-level language you can get your hands on,
but don't want to write everything yourself.
Hissp brings together the expressiveness of Lisp metaprogramming
and the richness of the Python ecosystem.
Python has had slow and steady growth for decades,
and now it's a top industry language.
Its ecosystem is very mature and widely used,
and Hissp can participate in it as easily as Python can,
because compiled Hissp *is Python*.
If the only programming languages you've tried are those designed to feel familiar to C programmers,
you might think they're all the same.
I assure you, they are not.
Languages can vary a great deal in expressiveness.
At some level you already know this.
Why not use assembly language for everything?
Or why not the binary machine code?
In terms of expressiveness,
Lisp has few rivals.
It's the programmable programming language:
the compiler itself is readily extensible in the course of everyday coding.
But there are many dialects of Lisp, and Hissp is one of them.
But, Python's is not the only mature ecosystem.
If you're attached to a different one,
perhaps Hissp is not the Lisp for you.
Is Hissp a replacement for Python?
----------------------------------
Hissp is a modular metaprogramming *supplement* to Python.
How much Python you replace is up to you:
* Use small amounts of readerless-mode Hissp directly in your Python code.
* Compile individual Lissp modules to Python and import them in your Python projects.
* Write your entire project in Lissp, including the main module, and launch it with ``lissp``.
Or start with Lissp and supplement with Python:
* Use small amounts of Python code directly in your Lissp code via the inject macro ``.#``.
* Import Python modules and objects with qualified identifiers.
* Write the main module in Python (or compile it) and launch it with ``python``.
While the Hissp language can do anything Python can,
Hissp is compiled to Python,
so it requires Python to work.
You must be able to read Python code to understand the output of the Lissp REPL,
which shows the Python compilation, the normal Python reprs for objects,
and the same kind of tracebacks and error messages Python would.
Can Hissp really do anything Python can when it only compiles to a subset of it?
--------------------------------------------------------------------------------
Yes.
Short proof: Hissp has strings and can call `exec()`.
But you usually won't need it because you can import anything written in
Python by using `qualified identifiers`.
Strings at the Hissp level represent raw Python code,
although the compiler does do some minimal preprocessing for the qualifiers,
it pretty much injects the string contents verbatim.
This is usually used for identifiers, but could be anything.
Hissp macros and Lissp reader macros can return any type of object,
including strings.
However, the main use of Hissp is metaprogramming with syntactically-aware macros.
If you wanted to do string metaprogramming you could have just used `exec()`,
with Python string manipulation,
so you're giving up a lot of Hissp's power.
Python expressions are relatively safe to inject in Hissp if you're careful,
but note that statements would only work at the top level.
What's 1 + 1?
-------------
Two.
I mean how do you write it in Hissp without operators? Please don't say ``eval()``.
-----------------------------------------------------------------------------------
We have all the operators because we have all the standard library
functions.
.. code-block:: Lissp
(operator..add 1 1)
That's really verbose though.
-----------------------------
Hmm, is this better?
Top-level imports are a good use of inject.
.. sidebar:: Star imports are generally bad, but
I daresay ``from operator import *`` would be even less verbose.
This has the effect of making the origin of many identifiers mysterious.
Because it dumps the entire contents into globals,
they almost act like builtins.
It increases the chances of name collisions,
and of importing things from the wrong place,
which can also cause weird behavior when you re-order imports.
Like builtins, you really need to be familiar with the whole module,
not just the parts you are using.
Star imports are usually not worth it.
But sometimes they are.
The `operator` module *is* a good candidate for it.
Also consider `itertools`.
Use responsibly.
.. code-block:: REPL
#> .#"import operator as op"
>>> import operator as op
#> (op.add 1 1)
>>> op.add(
... (1),
... (1))
2
The result is a bit less desirable in templates.
But it's not technically wrong.
.. code-block:: REPL
#> `op.add
>>> '__main__..op.add'
'__main__..op.add'
And you can still qualify it yourself instead of letting the reader do it for you:
.. code-block:: REPL
#> `operator..add
>>> 'operator..add'
'operator..add'
Yeah, that's better, but in Python, it's just ``+``.
----------------------------------------------------
You can, of course, abbreviate these.
.. code-block:: REPL
#> (define + operator..add)
>>> # define
... __import__('builtins').globals().update(
... QzPLUS_=__import__('operator').add)
#> (+ 1 1)
>>> QzPLUS_(
... (1),
... (1))
2
Yes, ``+`` is a valid symbol in Lissp.
It gets munged to ``QzPLUS_``.
The result is all of the operators you might want, using the same prefix notation used by all the calls.
You can define these however you want,
like upgrading them to use a reduce so they're multiary like other Lisps:
.. code-block:: REPL
#> (define +
#.. (lambda (: :* args)
#.. (functools..reduce operator..add args)))
>>> # define
... __import__('builtins').globals().update(
... QzPLUS_=(lambda *args:
... __import__('functools').reduce(
... __import__('operator').add,
... args)))
#> (+ 1 2 3)
>>> QzPLUS_(
... (1),
... (2),
... (3))
6
You mean I have to do this one by one for each operator every time?
-------------------------------------------------------------------
Write it once,
then you just import it.
That's called a "library".
And no, you don't copy/paste the implementation.
That would violate the DRY principle.
Implement it once and map the names.
Why isn't that in the Hissp library already?
--------------------------------------------
It **is** in the library already!
It's called `operator`.
Hissp is a modular system.
Hissp's output is *guaranteed* to have no dependencies you don't introduce yourself.
That means Hissp's standard library *is Python's*.
All I can add to it without breaking that rule
are some basic macros that have no dependencies in their expansions,
which is arguably not the right way to write macros.
So I really don't want that collection to get bloated.
But I needed a minimal set to test and demonstrate Hissp.
A larger application with better alternatives need not use the basic macros at all.
If you don't like Python's version,
then add a dependency to something else.
Maybe write your own prelude.
If some open-source Hissp libraries pop up,
I'd be happy to recommend the good ones in Hissp's documentation,
but they will remain separate packages.
I want infix notation!
----------------------
Hissp is a Lisp. It's all calls! Get used to it.
Fully parenthesized prefix notation is explicit and consistent. It's
very readable if properly indented. Don't confuse "easy" with
"familiar". Also, you don't have to be restricted to one or two
arguments.
...
---
Fine. You can write macros for any syntax you please.
Recall that both reader and compiler macros can return arbitrary
Python snippets and the compiler will emit them verbatim.
You should generally avoid doing this,
because then you're metaprogramming with strings instead of AST.
You're giving up a lot of Hissp's power.
But optimizing a complex formula is maybe one of the few times it's OK to do that.
Recall the inject ``.#`` reader macro executes a form and embeds its result
into the Hissp.
.. code-block:: REPL
#> (define quadratic
#.. (lambda (a b c)
#.. .#"(-b + (b**2 - 4*a*c)**0.5)/(2*a)"))
>>> # define
... __import__('builtins').globals().update(
... quadratic=(lambda a,b,c:(-b + (b**2 - 4*a*c)**0.5)/(2*a)))
But for a top-level `define` like this, you could have just used
`exec()` on a ``def`` statement.
At that point, why not import it from Python?
Instead of Lissp,
consider using Hebigo_,
where bracketed Python expressions are idiomatic.
How do I make bytes objects in Lissp?
-------------------------------------
.. code-block:: REPL
#> (bytes '(1 2 3))
>>> bytes(
... ((1),
... (2),
... (3),))
b'\x01\x02\x03'
Or, if you prefer hexadecimal,
.. code-block:: REPL
#> (bytes.fromhex "010203")
>>> bytes.fromhex(
... ('010203'))
b'\x01\x02\x03'
But that's just numbers. I want ASCII text.
-------------------------------------------
You do know about the `str.encode` method, don't you?
There's really no bytes literal in Lissp?
-----------------------------------------
Technically? No.
However, they do work in Python injections:
.. code-block:: REPL
#> [b'bytes',b'in',b'collection',b'atoms']
>>> [b'bytes', b'in', b'collection', b'atoms']
[b'bytes', b'in', b'collection', b'atoms']
#> .#"b'injected bytes literal'"
>>> b'injected bytes literal'
b'injected bytes literal'
And you can invoke bytes constructors at read time.
.. code-block:: REPL
#> builtins..bytes.fromhex#.#"6279746573"
>>> b'bytes'
b'bytes'
#> builtins..bytes#(98 121 116 101 115)
>>> b'bytes'
b'bytes'
And, if you have the basic macros loaded,
you can use the `b# ` reader macro.
.. code-block:: REPL
#> b#"bytes from reader macro"
>>> b'bytes from reader macro'
b'bytes from reader macro'
Bytes literals can be implemented fairly easily in terms of a raw string and reader macro.
That's close enough, right? You can make all sorts of "literals" the same way.
See the `Macro Tutorial ` for more ideas.
Why aren't any escape sequences working in Lissp strings?
---------------------------------------------------------
Lissp's strings are raw by default, like Python's r-strings.
Lissp doesn't force you into any particular set of escapes.
Some kinds of metaprogramming are easier if you don't have to fight Python.
You're free to implement your own.
I like Python's, thanks. That sounds like too much work!
--------------------------------------------------------
Python's are still available in injections:
.. code-block:: REPL
#> .#"'\u263a'"
>>> '\u263a'
'☺'
Or use the hash-string reader syntax for short:
.. code-block:: REPL
#> #"\u263a"
>>> ('☺')
'☺'
Wait, hash strings take escapes? Why are raw strings the default? In Clojure it's the other way around.
-------------------------------------------------------------------------------------------------------
Then we'd have to write byte strings like ``b##"spam"``.
Python has various other prefixes for string types.
Raw, bytes, format, unicode, and various combinations of these.
Reader macros let us handle these in a unified way in Lissp and create more as needed,
such as regex patterns, among many other types that can be initialized with a single string,
and that makes raw strings the most sensible default.
With a supporting reader macro,
all of these are practically literals.
It's easy to process escapes in reader macros;
it isn't easy to unprocess them.
Not to mention Python code injections,
which can contain their own strings with escapes.
Clojure's hash strings are already regexes, not raws,
and its "reader macros" (tagged literals) aren't as easy to use,
so it doesn't come up as much.
Look at your strings in Python and you'll find that
most of the time you don't need the escapes.
Why can't I make a backslash character string?
----------------------------------------------
You can.
.. code-block:: REPL
#> (print #"\\")
>>> print(
... ('\\'))
\
#> (len #"\\")
>>> len(
... ('\\'))
1
The Lissp tokenizer assumes backslashes are paired in strings,
so you can't do it with a raw string:
.. code-block:: REPL
#> (len "\\")
>>> len(
... ('\\\\'))
2
#> "\"
#..\\"
>>> ('\\"\n\\\\')
'\\"\n\\\\'
Python's tokenizer makes the same assumption, even for raw strings.
How do I start the REPL again?
------------------------------
If you installed the distribution using pip, you can use the provided
``lissp`` console script.
::
$ lissp
You can also launch the Hissp package directly using an appropriate
Python interpreter from the command line
::
$ python3 -m hissp
.. TODO: How do I start the REPL programmatically?
There's no ``macroexpand``. How do I look at expansions?
------------------------------------------------------------
Invoke the macro indirectly somehow so the compiler sees it as a run-time function,
and pass all arguments quoted.
.. code-block:: Lissp
((getattr hissp.basic.._macro_ 'define) 'foo '"bar")
One could, of course, write a function or macro to automate this.
You can also use the method call syntax for this purpose, which is never
interpreted as a macro invocation. This syntax isn't restricted solely
to methods on objects. Due to certain regularities in Python syntax, it
also works on callable attributes in any kind of namespace.
.. code-block:: Lissp
(.define hissp.basic.._macro_ : :* '(foo "bar"))
You'll find that you often don't bother macroexpanding
because you can instead look at the compiled Python output,
which is also presented in the REPL.
It's indented,
so it's not that hard to read, once you get used to Hissp.
The compiler also helpfully includes a comment in the compiled output whenever it expands a macro.
Is Hissp a Scheme, Common Lisp, or Clojure implementation?
----------------------------------------------------------
No, but if you're comfortable with any Lisp,
Lissp will feel familiar.
Of these, ClojureScript may be the most similar,
in that it transpiles to another high-level language.
But unlike JavaScript,
Python already comes with batteries included.
Hissp doesn't include a standard library.
Because Python already provides so much,
in many ways Hissp can be even more minimal than Scheme.
Hissp draws inspiration from previous Lisps,
including Scheme, Common Lisp, ClojureScript, Emacs Lisp, Arc, and Hy.
Does Hissp have tail-call optimization?
---------------------------------------
No, because CPython doesn't. If a Python implementation has it, Hissp
will too, when run on that implementation.
The performance and complexity overhead of shoehorning TCO into a Python
compilation target is not worth it.
You can increase the recursion limit with `sys.setrecursionlimit`.
Better not increase it too much if you don't like segfaults, but you can
trampoline instead. See Drython_'s ``loop()`` function. Or use it. Or
Hebigo_'s equivalent macro. Clojure does it about the same way.
See also, `fn.py `_'s ``@recur.tco`` decorator.
Isn't that required for Lisp?
-----------------------------
No, you're thinking Scheme.
The Common Lisp standard does not require TCO
(though many popular implementations have it).
Clojure and ClojureScript don't have it either.
There's no ``for``? What about loops?
-------------------------------------
Sometimes recursion is good enough even without tail-call optimization.
Try it.
`list()`, `map()