.. Copyright 2019, 2020, 2021 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
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.
At some level you already know this.
Why not use assembly language for everything?
Or why not the binary machine code?
Because you want the highest-level language you can get your hands on.
Lisp has few rivals in this regard, but many dialects, Hissp among them.
You want access to 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 use it and participate in it as easily as Python can,
because compiled Hissp *is Python*.
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``.
This also works in reverse.
* 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()`,
so you're giving up a lot of Hissp's power.
Expressions are relatively safe 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__('operator').setitem(
... __import__('builtins').globals(),
... 'xPLUS_',
... __import__('operator').add)
#> (+ 1 1)
>>> xPLUS_(
... (1),
... (1))
2
Yes, ``+`` is a valid symbol. It gets munged to ``xPLUS_``. 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__('operator').setitem(
... __import__('builtins').globals(),
... 'xPLUS_',
... (lambda *args:
... __import__('functools').reduce(
... __import__('operator').add,
... args)))
#> (+ 1 2 3)
>>> xPLUS_(
... (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 that without breaking that rule is 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 should probably not be using the basic macros at all.
If you don't like Python's version,
then add a dependency to something else.
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.
Also consider using Hebigo_, which keeps all Python expressions, instead
of Lissp.
Also 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 complex formulas 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__('operator').setitem(
... __import__('builtins').globals(),
... '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()`.
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, 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.
Why aren't any escape sequences working in Lissp strings?
---------------------------------------------------------
Lissp's strings are raw by default.
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 read 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 their reader macros aren't so 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
#> (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 makes the same assumption, even for raw strings.
So raw strings in Python have the same limitation.
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
There's no ``macroexpand``. How do I look at expansions?
------------------------------------------------------------
Invoke the macro indirectly somehow so the compiler sees it as a normal 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.
There's no ``for``? What about loops?
-------------------------------------
Sometimes recursion is good enough. Try it. `list()`, `map()