3

As I read blog Revenge of the nerds, It says (in what made Lisp different section):

The whole language there all the time. There is no real distinction between read-time, compile-time, and runtime. You can compile or run code while reading, read or run code while compiling, and read or compile code at runtime.

Running code at read-time lets users reprogram Lisp's syntax; running code at compile-time is the basis of macros; compiling at runtime is the basis of Lisp's use as an extension language in programs like Emacs; and reading at runtime enables programs to communicate using s-expressions, an idea recently reinvented as XML.

In order to understand this sentence, I draw a statechart diagram:

read/compile/runtime in Lisp

I have two questions:

  1. how to understand to read at runtime enable programming to communicate using s-expression, an idea reinvented as XML
  2. what can we do when compiling at read time or reading at compile time?
Community
  • 1
  • 1
Jiacai Liu
  • 2,623
  • 2
  • 22
  • 42

2 Answers2

2

XML let you exchange data at runtime between programs (or between different invocations of the same program). The same goes for JSON, which is a subset of Javascript and a little closer to Lisp in spirit. However, Common Lisp gives more control over how the different steps are executed; as explained in the quotes, you can reuse the same tools as your Lisp environment instead of building a framework like other languages need to.

Basically, you print data to a file:

(with-open-file (out file :direction :output)
  (write data :stream out :readably t))

... and you restore it later :

(with-open-file (in file) (read in))

You call that "serialization" or "marshalling" in other languages (and in fact, in some Lisp libraries). The READ step can be customized: you can read data written in a custom syntax (JSON.parse accepts a reviver function, so it is a little bit similar; the Lisp reader works for normal code too). For example, the local-time library has a special syntax for dates that can be used to rebuild a date object from a stream.

In practice, this is a bit more complex because not all data has a simple readable form (how do you save a network connection?), but you can write forms that can restore the information when you load it (e.g. restore a connection). So Lisp allows you to customize READ and PRINT, with readtables and PRINT-OBJECT, but there is also LOAD-TIME-VALUE and MAKE-LOAD-FORM, which allows you to allocate objects and initialize them when loading code. All of this is already available in the language, but there are also libraries that make things even easier, like cl-conspack: you just store classes into files and load them back without having to define anything special (assuming you save all slots). This works well thanks to the meta-object protocol.

coredump
  • 37,664
  • 5
  • 43
  • 77
  • Thanks. Could you also explain my second question? I have read source code of Clojure, it's kinds of combining read and compile into one LispReader – Jiacai Liu Jul 03 '17 at 07:13
  • The [reader](https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/LispReader.java) only performs read, but you can see that there a syntax for evaluating things at read-time (EvalReader), enabled only when `*read-eval*` is true. The reader also looks into the compiler's environment (namespaces, etc.) to resolve symbols. There is not much compilation happening there. AFAIK, in Clojure, the code is compiled into Java code, which is then compiled on-the-fly or ahead-of-time into object files. – coredump Jul 03 '17 at 08:12
  • I'm afraid it's not this way. Clojure doesn't emit Java code at all, it use [ASM](http://asm.ow2.org/) to generate bytecode. see [this](https://clojure.org/reference/compilation). Put Clojure aside, How does Common Lisp tell read from compile? – Jiacai Liu Jul 03 '17 at 08:38
  • @JiacaiLiu You're right, it directly emits bytecode, thanks. So, in CL, there are functions named "READ" and "COMPILE" and you call them when you want. In some implementations, calling "LOAD" always compiles the code first (in others, there might be an interpreter). I am not sure I really understand your question. – coredump Jul 03 '17 at 11:19
  • 1
    @JiacaiLiu "Reading" means a transformation from a stream of characters to Lisp objects (in the case of Lisp source code, these Lisp objects are usually lists of mostly symbols representing function calls and macro applications). "Compiling" is the transformation of these lists of Lisp objects into object code. These are two clearly distinguishable operations, regardless whether you're using Clojure or CL. – Rörd Jul 03 '17 at 12:05
  • @coredump My original question relates to the statechart diagram. Obviously, there should be some relation between read and compile, but the author doesn't say the relation like others. – Jiacai Liu Jul 05 '17 at 08:21
  • @JiacaiLiu "Compile" compiles form that were "Read", or built by other means. Read generally doesn't need to call compile, but imagine you write a stateful parser for READ which requires a temporary closure: you can compile that closure first if you want. – coredump Jul 05 '17 at 11:01
  • @coredump OK, It's indeed unnecessary to put them together. It seems only relating to Runtime we can do something useful. – Jiacai Liu Jul 05 '17 at 15:02
2

Common Lisp

READ is a function which reads s-expressions and returns Lisp data.

CL-USER> (read)
(defun example (n) (if (zerop n) 1 (* (example (1- n)) n)))  ; <- input
(DEFUN EXAMPLE (N) (IF (ZEROP N) 1 (* (EXAMPLE (1- N)) N)))  ; <- output

The last result again:

CL-USER> *
(DEFUN EXAMPLE (N) (IF (ZEROP N) 1 (* (EXAMPLE (1- N)) N)))                                                                                                       

Setting the variable code to the last result.

CL-USER> (setf code *)
(DEFUN EXAMPLE (N) (IF (ZEROP N) 1 (* (EXAMPLE (1- N)) N)))                                                                                                       

What is the third element?

CL-USER> (third code)
(N)                   

We can evaluate this list, since it looks like valid Lisp code:

CL-USER> (eval code)
EXAMPLE                                                    

The function EXAMPLE has been defined. Let's get the function object:

CL-USER> (function example)
#<interpreted function EXAMPLE 21ADA5B2>                           

It's an interpreted function. We use a Lisp interpreter.

Let's use the function by mapping it over a list:

CL-USER> (mapcar (function example) '(1 2 3 4 5))
(1 2 6 24 120)                                      

Let's compile the function:

CL-USER> (compile 'example)
EXAMPLE                                                                                                                                                           
NIL                                                                                                                                                               
NIL                    

The function has been compiled successfully. The compiler has no warnings and the function should now run much faster.

Let's use it again:

CL-USER> (mapcar (function example) '(1 2 3 4 5))
(1 2 6 24 120) 

This is the same result, but probably much faster computed.

Since it is now compiled, let's disassemble the function:

CL-USER> (disassemble #'example)
0          : #xE3E06A03 : mvn            tmp1, #12288                                                                                                             
4          : #xE18D6626 : orr            tmp1, sp, tmp1, lsr #12                                                                                                  
8          : #xE5166030 : ldr            tmp1, [tmp1, #-48]                                                                                                       

... and a lot more lines of ARM assembler machine code

Rainer Joswig
  • 136,269
  • 10
  • 221
  • 346
  • My origin second question is what can we do when compiling at read time or reading at compile time. Did i make myself clear? – Jiacai Liu Jul 05 '17 at 10:08
  • @JiacaiLiu : Maybe you want to edit your original question to make it clearer. Compiling at read-time or reading at compile-time are rarely used features. It's possible to do, but why? Typically the Lisp keeps running beween different compilation actions: read and compile are interleaved. – Rainer Joswig Jul 05 '17 at 17:36