Lispのオペレータは関数とマクロに大別できる

REPループについての説明で言ったように、Lispにコンピュータ的な処理をさせるには、要素をカッコで囲んだリストを入力してやります。

(OPERATOR arg1 arg2 ...)

そのリストの最初の要素には普通、オペレータ(を表すシンボル)がきます。

処理系が、このオペレータに渡す引数の扱いのちがいで、オペレータは関数とマクロに大別できます。

関数
すべての引数を一度、評価(eval)してから、関数本体に渡す。
KISS>(+ 10 *pi*)
;; シンボル+が表す関数本体には、
;; 引数として 10を評価した結果 10 と
;; 定数*pi*を評価した結果 0.3141592653589793e1 が渡される

0.13141592653589793e2 ;; 10 + 3.14 = 13.14

KISS>
KISS>(* (+ 10 *pi*) 3)
;; 最初に、掛け算をする関数*への引数がすべて評価される
;;
;; 一つ目の引数 (+ 10 *pi*) を評価すると上記のように値
;; 0.13141592653589793e2 が得られる。
;;
;; 二つ目の引数 3 は評価しても 3 のまま
;;
;; したがって、関数*の本体は、引数として
;; 0.13141592653589793e2 と 3 を受け取り、
;; 掛け算をして結果を返す

0.39424777960769379e2
;; 3.14 + 10 で 13.14 、13.14 * 3 = 39.42
KISS>

定義用オペレータ(defining operator)であるdefunを使ってユーザは関数を定義できます。

マクロ
すべての引数を、そのまま、マクロ本体に渡す。

なぜ、マクロがあるかと言うと、if, whileなどのような構文をユーザが自由に定義できるようにするためです。
たとえば、if構文は以下のような見た目をしています。

(if test-form then-form [else-form])

あるLisp Objectを評価(eval)されるものとして見るとき、それをフォーム(form)と呼びます。

KISS>(if (> 10 0) 'plus 'minus-or-zero)

plus

;; test-formである(> 10 0)を評価すると真(t)なので
;; then-form である'plus が評価されplusが返された
;; else-form である'minus-or-zero は一度も評価されないまま

KISS>

私が実装中のISLisp処理系KISSでは、スペシャルオペレータ if は次のように実装されています。
スペシャルオペレータは、あらかじめ用意されたオペレータで、引数の扱いから見るとマクロのように見えます。

/* special operator: (if test-form then-form [else-form]) -> <object> */
kiss_obj* kiss_if(kiss_obj* test_form, kiss_obj* then_form, kiss_obj* rest) 
{
  //test_form を評価(kiss_eval)した結果が KISS_NIL か
    //どうかで、then_form か rest の
    //どちらか一方だけが、
    //評価(kiss_eval)される
    if (kiss_eval(test_form) != KISS_NIL) {
	return kiss_eval(then_form);
    } else {
	return kiss_eval_body(rest);
    }
}

定義用オペレータ(defining operator)であるdefmacroを使ってユーザはマクロを定義できますが、どの引数が評価されるかは、自由に決められます。




Lispへの入力はどんな種類があるのか

Lispへの入力、つまり、REPループへの入力にはどんな種類があるのか、またそれが評価(eval)されるとどうなるのか、ざっくり説明させていただきます。

・Self evaluating objects (数字、文字列など) => そのまま
KISS>1

1  ;; self evaluating object つまり評価(eval)されても
   ;; 自分自身のまま

KISS>
KISS>3.2

0.32e1  ;; 3.2 = 0.32e1 数値としては同じ
        ;; REPループにおけるprint関数がどう
        ;; 表示するかの問題

KISS>
KISS>"been there, done that"

"been there, done that"  ;; 文字列も評価(eval)されても
                         ;; 同じです

KISS>
・シンボル => 変数や定数の参照
KISS>*pi*            ;; "*pi*"という名前のシンボル

0.3141592653589793e1 ;; 定数*pi*には円周率πの値が
                     ;; 設定されている

KISS>

ここで、*pi* というのが”*pi*”という名前のシンボルを表していおり、シンボルを評価(eval)すると、そのシンボルと結び付けられているLisp Objectを返すことになっています。(Cなどのプログラミング言語の定数や変数に相当)。

Lispでは、変数、関数、クラスなど各種Lisp Objectの名前を表すのにシンボルを使います。シンボルは処理系の実装でオブジェクトとして実体をもつものとして定義されることが多いのですが、その説明は別の機会に譲ります。

・リスト => 各種処理
KISS>(+ 1 2 3)  ;; リストの第一要素(ここでは+)によって
                ;; 色々起こる

6 ;; 1 + 2 + 3 = 6 ここでは+は引数をすべて足し算している

KISS>
KISS>(defun hello-world () "hello world!")  
     ;; hello-world関数を定義

hello-world ;; 関数定義が完了すると関数名のシンボルが返される

KISS>(hello-world) ;; 関数を呼び出してみる

"hello world!"

KISS>
KISS>(quote love) ;; quote はスペシャルオペレータと
                  ;; 呼ばれるもので関数とは引数の扱
                  ;; いが異なる

love              ;; quoteは1つのLisp Objectを受け取り、
                  ;; それをそのまま返す
                  ;; ここではシンボルlove。

KISS>'love        ;; quoteはよく使うので省略記法が
                  ;; 用意されている

love           ;; 'love ≡ (quote love)

KISS>love         ;; じゃ、quoteしないとどうなるか…

KISS| Unbound variable love

                  ;; 変数loveに値を設定していないのでエラー
KISS>




LispのREPループとは何なのか

Lispを触り始めると、まずいわゆるREPループと対面することになると思います。

簡単に言うと、Lispのコマンドラインみたいなものです。

で、そのコマンドラインがLisp的にどういう風に実現されているかという観点から命名されたのがREPループです(Read -> Eval -> Print ループ)。

                         Lispユーザー   
    
       Textual Representation   Printed Representation
                |                           ^
                |                           |
    ~~ 端末を使ってすべて文字列でユーザーとやりとりします ~~~
                |                           |
      +---------|---------------------------|--------+
      |         |       Lisp World         |        |
      |         V                           |        |
      |   +----------+  +-------- -+  +-----------+  |
      |   |   read   |  |   eval   |  |   print   |  |
      |   +-----+----+  +-------+--+  +-----------+  |
      |         |         ^     |           ^        |
      |         V         |     |           |        |
      |    Lisp Object----+     +-> Lisp Object      |
      |                                              |
      +----------------------------------------------+
  • read関数が入力としてユーザーが打った文字列(textual representation)を読み、その入力文字列の内容で表現されているLisp Objectを作成して出力する。
  • そのLisp Objectをeval関数が”評価(evaluate)”してその結果のLisp Objectを出力する。
  • そのLisp Objectをprint関数が文字列(printed representation)に変換して出力する。

たとえば、(+ 1 2) とrepループに打ち込んだら、次のような処理の流れになります。

       
           "(+ 1 2)"                       "3"
                |                           ^
      +---------|---------------------------|--------+
      |         |       Lisp World         |        |
      |         V                           |        |
      |   +----------+  +-------- -+  +-----------+  |
      |   |   read   |  |   eval   |  |   print   |  |
      |   +-----+----+  +-------+--+  +-----------+  |
      |         |         ^     |           ^        |
      |         V         |     |           |        |
      |       (+ 1 2) ----+     +-> 3 ------+        |
      |                                              |
      +----------------------------------------------+

また、たとえば単独の数字を入力しても”1″ -> read -> 1 -> eval -> 1 -> print -> “1” のように、数字を評価(evaluate)しても元のLisp Objectとしての数字から変化しません。(eval 1) -> 1。単純で分かりやすいですね。

では、コンピュータ的な各種処理を行わせるにはどうするかというと、それはリストを入力してあげることで実現しています。

(OPERATOR arg1 arg2 ...)

リストを評価(evaluate)するときは、上記のように、第一要素を演算子(オペレーター)として扱い、第二要素以降はその演算子への引数として渡すというルールになっているのです。

つまり、プログラムをリストとして入力することになります。
LISP = LISt Processor という名前の由来です。