Hacker News new | ask | show | jobs
by weland 4577 days ago
Quines are not quite the same thing.

Reasonably simple illustration: in Common Lisp, it is possible to write a function that takes a mathematical expression as an argument and returns another expression, as its derivative.

So basically I can write (pseudo-lisp-ish):

(defun diff (f) ... )

then call it like this:

(diff '(+ (pow x 2) x)

and I'd get the expression

(+ (* 2 x) 1)

as a result. The point is, however, that this expression is Lisp-callable code. If I already have a function p, I can write:

(setq dp (diff p))

and dp is now the function computed by (diff p).

This is not necessarily the most practically-useful example, but I think it's closest to being a strong illustration of the principle. In a nutshell, you can do symbolic manipulation with code from within your code.

Edit: I just read your other post from this thread. It's important to note that this happens at runtime, not at compile time.

This isn't impossible to do in C if you really want, but it wouldn't be portable (the reason for this is left at the user's discretion). Think about how you would do the same in C: write a function that takes a pointer to a function f, and returns a pointer to another function g, so that g is the derivative of f. It's obviously ok to restrict f to common mathematical operators.

I can think about ways of doing it, but it ain't pretty.

2 comments

Actually if you had to do this kind of thing in C or any other non-homoiconic language, the right way to go would be to define data structures that represent your mathematical functions, and manipulate these structures to compute the derivatives (by pattern matching of the represented mathematical expression, exactly as you would do in Lisp using Lisp code which kind of is its own AST). And you would have to embed an interpreter for the mathematical expressions encoded in the data structures of your program, so you can not only manipulate the expressions, but also use them (which also comes for free in Lisp).

See Greenspun's tenth rule of programming.

> Actually if you had to do this kind of thing in C or any other non-homoiconic language, the right way to go would be to define data structures that represent your mathematical functions, and manipulate these structures to compute the derivatives (by pattern matching of the represented mathematical expression, exactly as you would do in Lisp using Lisp code which kind of is its own AST).

Yes, of course. The language, being non-homoiconic, would require you to translate between a data representation that you can process and the data representation that the runtime can process.

However, if you were to do the equivalent thing -- that is, obtain the derivative based on the data representation that your runtime can process -- you'd have to examine the contents of the program memory itself. Needless to say, that would make things even unportable, or outright impossible due to compiler optimizations. But it would be the same thing :).

Edit: perversity bonus, you're working on a computer that does bank switching and the code that is computing the derivative is in a different memory bank than the function whose derivative it must compute.

And then there is this whole other category of self-modifying programs - programs that know about their own structure and can change themselves at runtime. This is (almost) in the "unthinkable" category for non-homoiconic languages.
This was, in fact, more or less pioneered in languages that aren't homoiconic in the same manner as Lisp is (code that used self-modification for reasons like time or space optimization was, if not common, at least known of in the early days of electronic computers). Arguably, machine code is very much homoiconic; what is beautiful about Lisp is that the underlying structure of program/data is regular and nicely-abstracted.
I think you could solve most of the problems that you'd need this property for with closures. If the bulk of the execution is defined with them, and you have closures defining which other closures to run, you can have them swap each other out.
I'm not sure I understand what you have in mind. Assuming I have something like this:

int foo(void) { return bar(1) + 2; }

is there any way in which I could arbitrarily modify this function? E.g. to dynamically turn it into

int foo(void) { return bar(2) * 4 }

using closures?

I get how you could do that, assuming you had a function defined for each of the possible operations you needed. But what about arbitrary modifications?

You wouldn't want to modify the function in-place, that would violate immutability of data. Each of these closures would themselves be generated, by a factory method. When you're ready to modify, you'd invoke the factory to generate another closure, then you'd use whatever infrastructure you built to assign them in the first place to re-assign them to the new closures.
Ah, got it. Thanks!
If you wanted to get really sophisticated, you could generate the factory methods, too.