[HN Gopher] Homoiconic Python
___________________________________________________________________
Homoiconic Python
Author : aburjg
Score : 123 points
Date : 2024-05-12 16:24 UTC (6 hours ago)
(HTM) web link (aljamal.substack.com)
(TXT) w3m dump (aljamal.substack.com)
| andrelaszlo wrote:
| I did something along those lines with JS lists:
| https://github.com/andrelaszlo/js-lisp
| frozenport wrote:
| Appears it takes an LLM running on the fastest computers in the
| world to read lisp
| netbioserror wrote:
| Only tangentially related, but for anyone interested in the idea
| of a simple, quick Python-like scripting Lisp, there are two
| Clojure-style languages to look at:
|
| 1) Hy (https://hylang.org/, compiles to Python bytecode, usually
| slower than Python but compatible with all Python libraries)
|
| 2) Janet (https://janet-lang.org/, very light Lua-style
| embeddable VM ~1 Mb, roughly twice as fast as Python for similar
| ops, very easy C interop)
| refset wrote:
| 3) Basilisp (https://github.com/basilisp-lang/basilisp, "A
| Clojure-compatible(-ish) Lisp dialect targeting Python 3.8+")
| vindarel wrote:
| > quick python-like scripting
|
| since we're at it, why not a batteries-included Common Lisp:
| https://github.com/ciel-lang/ciel it comes as a binary that
| starts fast and that includes libraries for mundane tasks.
|
| (for more CL<->Python if anyone's interested:
| https://github.com/CodyReichert/awesome-cl?tab=readme-ov-
| fil...)
| koito17 wrote:
| Ciel is exactly what I wanted out of a "scripting language"
| that runs atop Common Lisp. Really glad that you mentioned
| it. The built-in SLY/SLIME support makes me instantly want to
| ditch Babashka for this
| behnamoh wrote:
| I find it funny and also ironic that a lot of modern languages
| re-discover Lisp's amazing features decades later. The other day
| I was infuriated to see my Python program halt after 9 hours of
| API calling to my own home server. The API calls were similar
| (calling an LLM with a pre-defined prompt template, constrained
| using grammars). I wanted to save the state of the program and
| exit it before running the remaining iterations separately. So I
| needed a way to modify the current running Python code and
| inspect the variables. I couldn't figure out a way to "dig into"
| the running Python process and save the state and/or fix it. So I
| lost 9 hours worth of work.
|
| A couple days later, I saw this:
|
| https://malisper.me/debugging-lisp-part-1-recompilation/
|
| The fact that CL had this feature built into the language decades
| ago just blows my mind. Not to mention numerous other features
| (like the most powerful macro system there is), etc.
| pyuser583 wrote:
| Was there no exception being thrown?
| kqr wrote:
| I don't know how that would have helped. Most languages
| (including Python, AFAIK) unwind the stack when an exception
| is thrown, all the way up to the nearest catch. Whatever you
| wanted to do at the place of throwage is no longer possible
| because that stack doesn't exist other than as an incomplete
| printout.
|
| In contrast, Common Lisps condition system keeps all stack
| between throw and catch so you can resume from anywhere in
| between, possibly with altered code or local variables.
|
| Basically, Common Lisps exception stack traces are
| interactive by default.
| ptsneves wrote:
| Ironically that stack unwinding is exactly why I like a
| straight old sigsegv.
|
| Just dump the core and analyse the frozen state. One thing
| that annoyed me in go is that even when it core dumps on
| panic and after I worked on the backtrace analyser for gdb,
| most of the core dumps were useless as everything of worth
| was already unwound and lost.
| mananaysiempre wrote:
| Hidden in plain sight is the fact is that structured
| exception handling in 32-bit Windows (the linked-list-on-
| the-stack version[1,2], not saying anything about the
| gigantic-tables "zero-cost" version) is a _resumable_
| exception system a la Common Lisp conditions--inevitably,
| seeing as it subsumes SIGSEGV coredumps!
|
| [1] http://bytepointer.com/resources/pietrek_crash_course
| _depths...
|
| [2] http://bytepointer.com/resources/pietrek_vectored_exc
| eption_...
| aidos wrote:
| I don't quite understand what sort of state the system is
| in at this stage. So the process has errored but not
| exited?
|
| I know that during the exception you can pry into the
| variables from the stack. When do they get cleaned up?
|
| https://stackoverflow.com/a/5328139/171450
| behnamoh wrote:
| For that to happen, you must have implemented what that
| SO answer says in your code. In contrast, CL allows you
| to modify the current running code and apply fixes.
| aidos wrote:
| I guess I'm confused about what running code even _means_
| after the exception. The process has crashed. Is it
| waiting to be reaped by a parent process?
| lispm wrote:
| If there is an exception a matching error handler will be
| selected and called in the context of the error. A
| default handler could be a debugger, a break loop (which
| is a Lisp REPL), something which asks the user waht to do
| or it could be a handler provided by the software, which
| could do anything, including looking for restarts and
| using one.
|
| For example the variable a is unbound and we try to add
| 3. CL-USER 37 > (+ 3 a)
|
| CL shows an UNBOUND-VARIABLE error and provides Restarts.
| Restart 3 is a USE-VALUE restart.
| Error: The variable A is unbound. 1 (continue)
| Try evaluating A again. 2 Return the value of
| :A instead. 3 Specify a value to use this time
| instead of evaluating A. 4 Specify a value to
| set A to. 5 (abort) Return to top loop level 0.
| Type :b for backtrace or :c <option number> to proceed.
| Type :bug-form "<subject>" for a bug report template or
| :? for other options.
|
| We are now in a break loop, a REPL one level deeper, in
| the context of the error. Let's see what the condition
| (-> exception) is: CL-USER 38 : 1 > :cc
| #<UNBOUND-VARIABLE 8010002EC3>
|
| we call one restart (listed above, number 3)
| interactively, it uses the new value and continues to
| compute the expression. It asks for a value. I enter 5 ->
| 5 + 3 = 8 CL-USER 39 : 1 > :c 3
| Enter a form to be evaluated: 5 8
|
| We can do it also programmatically. HANDLER-BIND
| establishes a handler for UNBOUND-VARIABLE. It invokes
| the restart USE-VALUE with 4 -> 3 + 4 = 7
| CL-USER 40 > (handler-bind ((unbound-variable #'(lambda
| (c)
| (invoke-restart 'use-value 4))))
| (+ 3 a)) 7
|
| All this is the default behavior. There is no special
| "debug mode", attaching a debugger or "instrumentation of
| code" needed.
| kqr wrote:
| In the CL condition system they have separated error
| detection (exception thrown) from handler selection
| (catching exception) _and_ handler implementation (what
| are called "restarts".)
|
| So basically instead of registering one handler (catch
| block) that is responsible both for determining the
| proper course of action and implementing it, the
| condition system allows you to register multiple
| "restarting points" which the handler can select from
| when deciding how to handle the error.
|
| One of the restarts often used in debugging is restarting
| any of the functions in the call chain, i.e. just trying
| the same thing again. But just as common is varying some
| parameter or local variable and _then_ restarting the
| function.
| pyuser583 wrote:
| The process hasn't crashed after an exception. It still
| might even exit with a success error code, if you really
| want it to.
|
| An exception is raised when you attempt to parse HTML as
| JSON. The process doesn't end. You catch the exception,
| see what happened, and respond with a 400.
| kortex wrote:
| > Most languages (including Python, AFAIK) unwind the stack
| when an exception is thrown, all the way up to the nearest
| catch.
|
| You might have to use a few tricks/hacks, but I'm pretty
| sure you can set up python in a way to breakpoint at an
| exception, including all local state at the throw site.
| Otherwise pdb would not work.
| pyuser583 wrote:
| Definitely.
|
| It's very possible to pause (pdb-like) or persist the
| local state/stack.
|
| But there needs to be an actual exception.
|
| If someone pulls the power cord, you're out of luck.
| eikenberry wrote:
| > I find it funny and also ironic that a lot of modern
| languages re-discover Lisp's amazing features decades later.
|
| This is never surprising as Lispy languages are some of the
| most flexible and expressive languages. But that's the problem,
| it is too expressive. The Lisps always reminded me of mixed
| media (visual) art, where the freedom of expression from the
| mixed media sounds good on the face of it but in the end it
| generally produces sub-par works compared to more traditional
| single media art. It turns out that the restrictions of the
| medium are just as important as the expressiveness.
| sweeter wrote:
| thats a great analogy.
| cess11 wrote:
| I've seen shitty code running in 'prod' at every employment
| I've had as a developer. The language isn't that important,
| the people and the organisation are.
| rowanG077 wrote:
| Language is very important. Bad code in python is at least
| still python. Bad code in lisp can be an inpenetrable
| eldritch abomination where the only option is to rewrite
| from scratch. I believe this is actually the primary reason
| google build go. You can't write good code. But you also
| can't write really bad code.
| cess11 wrote:
| Why do you believe it impossible for Python to be
| impenetrable?
|
| A Common Lisp will likely be much more introspectable and
| debuggable than your average CPython.
|
| I think Google designed Golang to be a small language for
| implementing network services that will tie newcomers to
| programming hard to that specific language and then also
| Google. Hence the syntactic quirks and the err spectre.
| That it enables development of CLI applications was
| likely an unexpected bonus.
| dudinax wrote:
| It's not impossible for python to be impenetrable, but it
| usually isn't.
| rowanG077 wrote:
| It's not impossible. But rather much less likely. And
| even less likely to do by accident. Lisp by itself
| basically encourages programming deviating from standard
| practices. That the entire reason it's so powerful. It
| takes a real genius in addition to strong restraint to
| keep writing good code in that environment.
| albertzeyer wrote:
| Py-spy or Pystack to inspect the state to certain degree.
|
| Or https://github.com/malor/cpython-lldb.
|
| Or more here: https://github.com/albertz/pydbattach/
| tromp wrote:
| Another functional language that can be concisely implemented [1]
| in Python is Binary Lambda Calculus, with a large part of the
| code dealing with BLC's pure I/O model. Instead of using an
| association list for variable lookup, it uses de-Bruijn indices
| to index the environment array. The same page shows
| implementations in 9 other languages, with BLC's self interpreter
| being the most concise at 232 bits (29 bytes), which includes a
| parser and tokenizer.
|
| [1] https://rosettacode.org/wiki/Universal_Lambda_Machine#Python
| Garlef wrote:
| Wait... Isn't this just an implementation of lisp in python?
|
| It's not a homoiconic version of python as the title sugests,
| right?
|
| Or am I missing something?
| evanjrowley wrote:
| You would be correct according to Greenspun's 10th Rule:
| https://en.m.wikipedia.org/wiki/Greenspun's_tenth_rule
| dj_mc_merlin wrote:
| Yep. I also got really excited thinking this is homoiconic
| python I might be able to make my coworkers use (without having
| them buy into lisp). But no, it's just lisp.
| programjames wrote:
| MIT's fundamentals of programming course requires all its
| students to write a Lisp interpreter in Python. It's a throwback
| to when the class actually was in Lisp.
|
| https://py.mit.edu/spring24
| yismail wrote:
| Similarly a course I took in university made us implement a
| (rudimentary) Lisp interpreter in Lisp.
___________________________________________________________________
(page generated 2024-05-12 23:00 UTC)