According to this article the following line of Lisp code prints "Hello world" to standard output.
(format t "hello, world")
Lisp, which is a homoiconic language, can treat code as data in this way:
Now imagine that we wrote the following macro:
(defmacro backwards (expr) (reverse expr))
backwards is the name of the macro, which takes an expression (represented as a list), and reverses it. Here’s "Hello, world" again, this time using the macro:
(backwards ("hello, world" t format))
When the Lisp compiler sees that line of code, it looks at the first atom in the list (
backwards
), and notices that it names a macro. It passes the unevaluated list("hello, world" t format)
to the macro, which rearranges the list to(format t "hello, world")
. The resulting list replaces the macro expression, and it is what will be evaluated at run-time. The Lisp environment will see that its first atom (format
) is a function, and evaluate it, passing it the rest of the arguments.
In Lisp achieving this task is easy (correct me if I'm wrong) because code is implemented as list (s-expressions?).
Now take a look at this OCaml (which is not a homoiconic language) snippet:
let print () =
let message = "Hello world" in
print_endline message
;;
Imagine you want to add homoiconicity to OCaml, which uses a much more complex syntax compared to Lisp. How would you do that? Does the language has to have a particularly easy syntax to achieve homoiconicity?
EDIT: from this topic I found another way to achieve homoiconicity which is different from Lisp's: the one implemented in the io language. It may partially answer this question.
Here, let’s start with a simple block:
Io> plus := block(a, b, a + b) ==> method(a, b, a + b ) Io> plus call(2, 3) ==> 5
Okay, so the block works. The plus block added two numbers.
Now let’s do some introspection on this little fellow.
Io> plus argumentNames ==> list("a", "b") Io> plus code ==> block(a, b, a +(b)) Io> plus message name ==> a Io> plus message next ==> +(b) Io> plus message next name ==> +
Hot holy cold mold. Not only can you get the names of the block params. And not only can you get a string of the block’s complete source code. You can sneak into the code and traverse the messages inside. And most amazing of all: it’s awfully easy and natural. True to Io’s quest. Ruby’s mirror can’t see any of that.
But, whoa whoa, hey now, don’t touch that dial.
Io> plus message next setName("-") ==> -(b) Io> plus ==> method(a, b, a - b ) Io> plus call(2, 3) ==> -1