This is the byte-compiled case, which sadly is not readable for some reason. That said, IELM (Elisp REPL) makes that #<bytecode ...> form clickable (it's a so-called "presentation"), which leads straight to the following disassembly:
byte code:
args: nil
0 varref x
1 return
After native-compiling via (load (native-compile "file.el")), I get the same output as with byte-compiled case (+/- the address in #<bytecode ...> form), but the disassembly is slightly different:
byte code:
args: nil
0 constant :bar
1 return
EDIT: turns out it's IELM that's playing games with the printer. If I write:
(print (hn/foo :bar :ignored))
instead, I get the readable output from the article, in both byte and native-compiled cases. My understanding of the latter is, the closure returned from a native-compiled function is itself only byte-compiled (at least when used in REPL), and the difference in bytecode assembly come from the latter running through a more thorough optimization process.
So how does this interact with updating the captured environment? Does the printing essentially just show a snapshot of the environment at that point?
I assume the (closure) is read back as a new closure such that the environment will be restored as an environment shared between invocations of the read result?
IIRC the python-on-guile project can probably do this using some C voodoo. I believe it can serialize continuations and make deep copies of generators with mutable state.
Edit: however, I doubt the serialized format is human readable by default...
> Edit: Actually, I see my confusion, you accidentally printed 'f', not 'g', which I think was your intent?
I am not quite sure what you mean? There was no 'g' at all in my code snippet except for that in 'object->string'. Unless you are mistaking the '9' for one?
Is what happens when I write so late at night. I mistook the 9 for a g, thinking it was showing the body of the lambda. Really, I was just reading it all sorts of wrong.
I still feel this is somehow different, but I can't express the difference well.
Note that I should say I am not at all surprised that scheme could do this, for what its worth. I'm just saying that this feels different.
You can serialize closures (and continuations) in Common Lisp using Common Cold. The serialized representation of a closure contains a tag instead of the actual code, but the forms are also available in case you need to serialize them as well.
CL-USER> (write-to-string
(let ((y 4))
(common-cold:slambda (x)
(* x y))))
"#.(S:F '1 '(4))"
CL-USER> (funcall (read-from-string *) 3) ;; * here means reference to the last returned value
12
> The serialized representation of a closure contains a tag instead of the actual code
Was going to ask about that.
For readers unfamiliar with Common Lisp, the output is essentially equivalent to
"(eval '(s:f '1 '(4)))"
because #. is a reader macro that means "instead of returning the s-expression, return the result of evaluating it". I.e. it's a way to run arbitrary Lisp code at parsing stage. This is not something you want to see in a serialization format.
Ignoring the security implications[0], the result seems to rely on the runtime state of the Lisp image (I assume that '1 bit is a key to the actual code of the function) - so you won't be able to persist it and load after program restart, or send it to another instance of the program.
--
[0] - We're talking serializing code here anyway, so adding arbitrary code execution to paring changes nothing.
Like I said, the mapping from tags to forms is available and you can serialize that as well, if you wish. Using a tag is useful, as it makes for a compact serialization when creating many closures of the same function. With regards to security, you would want to attach an authentication tag that you can verify prior to deserialization. Common Cold comes with an example of a Hunchentoot handler that compresses and encrypts the serialized continuation representations.
It’s such a shame that the Common Lisp community has to rely on archive.org so much. I remember working with others to recover (IIRC) https://wiki.alu.org/ from archive.org after... something.
Yes, it is unfortunate. Information loss on the internet is not limited to CL, however, and many of my bookmarks became stale over the years. When I encounter a "treasure trove" of information, I make sure to archive it on my machine (and ultimately on backup hardware). Sometimes I put it up on sites like GitHub.
Wow, this is a really cool feature... specially the compiled lambda form with readable bytecode.
I will have a look at the "next" article from that post that explains emacs bytecode in more detail, I can imagine that opens the door to really interesting features in emacs.
Realized I have Emacs installed and tried it; the above prints "1" while calling the lambda without first serializing causes the (print x) to print "3"
Interpreted result:
This is equivalent to the output in the article. Now, after running compile-defun on the function and calling it, I get: This is the byte-compiled case, which sadly is not readable for some reason. That said, IELM (Elisp REPL) makes that #<bytecode ...> form clickable (it's a so-called "presentation"), which leads straight to the following disassembly: After native-compiling via (load (native-compile "file.el")), I get the same output as with byte-compiled case (+/- the address in #<bytecode ...> form), but the disassembly is slightly different: EDIT: turns out it's IELM that's playing games with the printer. If I write: instead, I get the readable output from the article, in both byte and native-compiled cases. My understanding of the latter is, the closure returned from a native-compiled function is itself only byte-compiled (at least when used in REPL), and the difference in bytecode assembly come from the latter running through a more thorough optimization process.