27 comments

[ 1.9 ms ] story [ 82.6 ms ] thread
Does your implementation has some concept of pairs with car/cdr functions? I am not a true Lisper, just I have started tinkering with it recently.
I played with python-lisp for some time and decided that python-array is not a LIST but CONS. And if the python-array has more than two pieces, it can be pretty-printed as an ARRAY. Haters will hate, but this is the Ultimate Solution.

    > '[1, 2, 3]
    [1, 2, 3]
    > '(1 2 3)
    (1 2 3)
    > '[1, 2]
    (1  .  2 )
    > '[1,(2 3)]
    (1 2 3)
https://github.com/timonoko/nokolis.py
> Nokolisp had no strings, but I invented new practise: a flat list of numbers with first number as 34, is prettyprinted as string in the editor. Then you can edit the numberlist normally and the editor shows range(32,256) as chracters alongsside.

Interesting solution! I wasn't sure if you were joking, but I see this syntax/convention is actually used.

   (print-nice (34 100 101 102 113 40 39))
Is there a way to escape the first number if there's a need to create a list with a literal 34?
Not much need for that. But I would now change the rule so that the number-list must have 34 at both ends. It is little illogical and ugly that there is only one 34 but two quotation marks is prettyprinted.

    > '(34 72 101 108 108 111)
    "Hello"
Cool, I bet you had fun with that.

Two random comments: if you haven’t played with the Hy language (hylang) it is pretty cool, a surface Clojure like syntax on top of Python. Also, have you tried the Python version of the book?

Side note - it's been a while since i've seen a Docco-style annotated-source-style documentation! http://ashkenas.com/docco/

Backbone.js was the first time i saw it, and I loved it! https://backbonejs.org/docs/backbone.html It demonstrated to me that the libraries I use are just normal code that other people write, and i myself can read it to understand a problem.

I dislike this style. First off, it’s just a block comment. Secondly, although raw markdown isn’t terrible to read, it makes it slightly hard to read the actual code as text, say in emacs. But the worst thing is that folks who code like this tend away from inline comments, which I find usually more useful in complex code.
IIRC Docco uses line comments, I'm not sure what your complaint is about. Besides, Emacs has two-column editing mode, making it possible to have side-by-side comments without any external tools. Also, third-party packages like separedit allow comment editing in an indirect buffer. My point is - with little regard for how it's written originally, Emacs (more than any other tool) grants ways of making it suitable for you, personally. You can make things more readable, or optimize them for writing.
How does one Lisp without TCO?

(Yes, I know some do without, but usually not without a great reason...for example Clojure needing to maintain JVM calling conventions)

One focuses on the learning aspects rather than creating a production lisp. If you want a production lisp, there are plenty out there.

And regardless, adding TCO is relatively simple as well, so not exactly a hard problem to figure out after you've written the first version of your toy lisp.

We're talking about Lisp in Python here. Python itself famously[1,2] refuses to have TCO.

1: http://neopythonic.blogspot.com/2009/04/tail-recursion-elimi... 2: http://neopythonic.blogspot.com/2009/04/final-words-on-tail-...

You can implement TCO in Python manually with continuation-passing style and trampolines.
Scheme guarantees TCO, but not Lisp in general I think. I seem to remember that Common Lisp does not guarantee TCO either.
Generally, by doing actual work instead of linear recursion exercises.
Common Lisp doesn't guarantee TCO, and yet people manage just fine
Not that it matters for a fun project but there are much faster ways to check if something is an int/float instead of exceptions.
How would you do it?
Well, if you've defined valid representation (and if not, you can by just copying the specs from the Python constructor functions you are calling, whose documentation has the definition they support), match to that before calling the conversion function rather than trapping conversion errors.

Even with the exact same representation, it will speed up the no cases at the expense of slightly slowing down the yes cases (assuming that that is also what the function itself does), but since this is called on tokens with no a-priori reason to expect a match as the normal case, you’ll have more no than yes, so even if it was symmetric speed swap that'd be a win (and I think the existing way you lose more to each exception that you gain avoiding duplicating the check on matches, so it should be a bigger gain that you'd expect if it was symmetric.)

This is a very nice example of a literate programming document. It would be amazing if we could adopt this nice ergonomic two-column display style in source files in text editors.

It would be nice if it were clearer how to download the plaintext.

I'm wondering about whether using exceptions in the utility functions is misusing Python's exception handling system... Look at is_integer() for example. Is catching ValueError -- if the token cannot be converted -- a Pythonic thing? Or is it abusing exceptions for ordinary (non-exceptional) tokenizing?
Looking at the usage of is_integer, personally I would write something like parse_integer(x: str) -> Optional[int], where the None variant represents a caught ValueError; this avoids parsing the integer twice but achieves basically the same effect (in a very similar style).

What would you propose as "more pythonic" here? Writing your own integer parsing code, by looking at the ASCII values of each character in the string?

It's suboptimal and you wouldn't want it for a production interpreter, but it's the lowest friction for leveraging what you get for free with Python to get something working correctly.