Hacker News new | ask | show | jobs
by dmux 1482 days ago
> In LISP 1.5, function definitions were stored on the property list of the function's name...

The fact that this was once the norm but has since been done away with saddens me. I've always been fascinated with "live environments" but felt they only went half way if they didn't include the source code itself. If I'm going to be updating something in a running system, I want to know what source code was used to get the system to the state its currently in, and preferably be able to query that source code as data. Of course, that code could be kept within source control, but then it's a shadow of the running system -- a map of the territory and not the territory.

As far as I know, the only languages/environments where this functionality is still available are Tcl, Smalltalk, and to some extent stored procedures within an RDBMS.

10 comments

Forth as well. ANS Forth defines the word SEE <https://forth-standard.org/standard/tools/SEE> that shows the current source of a given word.
Minor caveat on SEE. If the Forth implementation uses "threaded" code the resulting source code can be quite close to the original text.

If the Forth compiler generates native code the one-to-one relationship with the source is lost and SEE will typically show the compiled code as Assembly Language.

Javascript does this: functions have to toString() method which returns their source code. Python also does something like this. Though it's just a debugging tool; you can't do much practical with it in real applications (like edit it and instantiate a new function from it) because it doesn't include the closure the function was defined in.
Also same for Rebol & Red.

And its easy to view the source code from the console (REPL). Some examples from Rebol2...

    >> source func

    func: func [
        "Defines a user function with given spec and body."
        [catch]
        spec [block!] {Help string (opt) followed by arg words (and opt type and string)}
        body [block!] "The body block of the function"
    ][
        throw-on-error [make function! spec body]
    ]


    >> source source

    source: func [
        "Prints the source code for a word."
        'word [word!]
    ][
        prin join word ": "
        if not value? word [print "undefined" exit]
        either any [native? get word op? get word action? get word] [
            print ["native" mold third get word]
        ] [print mold get word]
    ]
Even when you can access and modify the source in-place, it may be tricky when closure is involved
This is not the norm in Forth due to the very spartan environments it originated in, but most mature implementations include a pretty comprehensive decompiler as SEE. (Ironically, this is where the Forth claim of having a modular compiler comes apart, because SEE is always monolithic and nonextensible, or at least I haven’t seen it done any other way.)
Doesn't SBCL have this functionality?
The DOCUMENTATION function shows documentation from a function's definition.
R has this (being secretly a lisp that only looks C-like on the surface).
many Lisp systems record the source location and/or the source itself (especially a source level interpreter needs that)
Do you mind enumerating what those systems are? I've only played around with Allegro CL and SBCL myself.
LispWorks for example has a documented and extensible mechanism to record the locations of definitions:

http://www.lispworks.com/documentation/lw80/lw/lw-dspecs-ug....

Common Lisp has standard function FUNCTION-LAMBDA-EXPRESSION:

  * (defun foo (a)
      (my-if (> a 10) 'big 'small))
  FOO

  * (function-lambda-expression #'foo)
  (LAMBDA (A) (BLOCK FOO (MY-IF (> A 10) 'BIG 'SMALL)))
  T
  FOO
Maybe collectively we can. In Emacs/SLIME/CCL,SBCL on ARM32,x86-64 ESC + '.' (command 'find-tag') on a symbol in the REPL opens a buffer with the definition of that symbol, provided you have the sources (it's neither decompiling nor reading the definition from the binary, but follows a hint to the source file). So this is more functionality of the environment / tools, rather then the compiler or language. Works beautifully though and in most cases you don't really want the sources embedded within the binary.
> So this is more functionality of the environment / tools, rather then the compiler or language.

Doesn't FUNCTION-LAMBDA-EXPRESSION apply here?

Didn't even know about this one. It is one of those 'optional' features -- the standard puts it like "Any implementation may legitimately return nil as the lambda-expression of any function." And indeed, CCL choses to do just that. SBCL seems to put in some effort to decompile the function.
Worst case scenario, you can probably patch your compiler's COMPILE function to store the data somewhere before performing the actual compilation.

As for CCL, it seems to work for me just fine as long as you set CCL:*SAVE-DEFINITIONS* to T:

    Clozure Common Lisp Version 1.12.1 (v1.12.1) LinuxX8664

    For more information about CCL, please see http://ccl.clozure.com.

    CCL is free software.  It is distributed under the terms of the Apache
    Licence, Version 2.0.
    ? (defun foobar (a b c) (* a (+ b c)))
    FOOBAR
    ? (function-lambda-expression #'foobar)
    NIL
    NIL
    FOOBAR
    ? (setf ccl:*save-definitions* t)
    T
    ? (defun foobar (a b c) (* a (+ b c)))
    FOOBAR
    ? (function-lambda-expression #'foobar)
    (LAMBDA (A B C) (DECLARE (CCL::GLOBAL-FUNCTION-NAME FOOBAR)) (BLOCK FOOBAR (* A (+ B C))))
    NIL
    FOOBAR
CCL has a feature called source-note to record locations and source.
Not a Common Lisp implementation but Clojure can find the source for a symbol (provided the source code file is in the classpath) with the clojure.repl/source function (doc: https://clojure.github.io/clojure/clojure.repl-api.html#cloj... .) All development environments support jump to definition.
And Emacs
Julia has this