[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)