﻿


   Descartes Lisp/λ  デカルト言語によるLisp言語の作成  H.Niwa (c) 2010

					(ライセンスはGPL2 or laterです)



デカルト言語の例題としてLispを作成しました。
名づけてデカルトLisp/λ(Descartes Lisp/λ)です。

恒例ですので、まず、"hello, world"を次に示しましょう。

(print "hello, world")

このデカルトLisp/λを使うとlispの関数とともに、λ式(みたいなもの)が
書けます。

(car '(a b c))

λx (car x) '(a b c)

λxyz (* x (+ y z)) 9 8 7 

(define (f x) (* x 2))

(define f (λ(x) (* x 2)))

(define f λx (* x 2))

λ関数の括弧がかなり省略できるのがわかるでしょうか。

■ 1 特徴と制限

ここで新たに作成したLispの方言は、以下のような特徴や制限を持ちます。

1. 1行ずつプログラムを読み込む。
2. リストのドット対は、"."ではなく、":"を使う。
3. 使える組み込み関数は、car, cdr, cons, equal, atom, cond, λ, 
  print, define, 整数演算子、比較演算子
4. ローカル変数は設定と参照はできるが変更はできない。
   (副作用がなく参照透明性を持つ。)
5. defineで設定される変数と関数は同じグローバル名空間に設定される。
6. lamda関数を、"λ"と多バイト文字で表現する。
7. λ関数で、括弧を減らす糖衣構文(シンタックスシュガー)を導入する。
8. λ関数の中で実行する関数は一つしか書けない。
   λx (関数1)としか書けず、λx (関数1)(関数2)とは書けない。
   普通のlispと異なり、(λ (x) (関数1)(関数2))とは書けない。

できるだけシンプルになるようにデザインしました。

■ 2. 文字コードの注意点

SJISの文字コードを使います。

■ 3. 実行例
このDescartes Lisp/λでは以下のようなプログラムを実行できます。

(car '(a b c))
(define f 1)
(define f (λ(x) (* x 2)))
(define (f x) (* x 2))
((λ(x y) (* x (+ y 3))) 2 3)
λxyz (* x (+ y z)) 9 8 7 
λx λy λz (+ x (* y z))  7 40 3
(define f λx (* x 2))
(f 3)

実行結果を以下に示します。

$ descartes -u lisp-utf8
Descartes Lisp/λ (c) 2010 H.Niwa
Ready
(car '(a b c))
a
Ready
(define f 1)
1
Ready
(define f (λ(x) (* x 2)))
(λ (x) (* x 2))
Ready
(define (f x) (* x 2))
(λ (x) (* x 2))
Ready
((λ(x y) (* x (+ y 3))) 2 3)
12
Ready
λxyz (* x (+ y z)) 9 8 7
135
Ready
λx λy λz (+ x (* y z))  7 40 3
283
Ready
(define f λx (* x 2))
(λ (x) (* x 2))
Ready
(f 3)
6
Ready

■4. ラムダ関数

デカルトLisp/λのラムダ関数は、他のlisp言語の"lambda"を"λ"に置き換えた
ものです。

他のlisp言語で"(lambda (x) (+ x 1))"と書くものは、デカルトLisp/λでは
"(λ(x) (+ x 1))"と書きます。

それだけでは、あまり面白くもありませんので、さらに、以下の条件の場合には、
1番外側と２番目の括弧と引数の括弧を書く必要がないようにしました。

λ計算理論で使われるλ式にかなり近い形になります。

- λ関数の引数パラメタが 1 から 3 個である。かつ。
- λ関数の引数パラメタの変数名が 英字1文字 である。かつ、
- λ関数の引数パラメタが括弧でくくられていない場合。

たとえば、以下は同じものです。
((λ(x)(+ x 1)) 1)
λx (+ x 1) 1

λとxはくっついていても大丈夫です。

実行してみましょう。

((λ(x)(+ x 1)) 1)
2
Ready
λx (+ x 1) 1
2
Ready

defineによる関数定義も同様です。

(define f (λ(x)(+ x 1)))

(define f λx (+ x 1))

ラムダ関数を入れ子にすることもできます。

λx λy (/ x y) 3 6
2

これは、以下と同じです。

((λ(x) ((λ(y) (/ x y)) 3)) 6)
2
 
引数パラメタ変数のxが6, yが 3にバインドされることに注意してください。
λ関数を入れ子にした場合には、与える引数が定義とは逆順にパラメタの引数に
バインドされます。

複数のパラメタが一つのラムダに定義されている場合には、順にバインドされます。

λxy (/ x y) 6 3
2

((λ(x y) (/ x y)) 6 3)
2

■5. load関数

Lispの関数プログラムを、ファイルに書いておいて、Lispの実行時に
読み込むことがload関数を使うとできます。
(このとき、Lisp関数プログラムは複数行に分かち書き
 されていてもloadされます)

(load "ファイル名")でプログラムを読み込みます。

■6. let関数

let関数は以下のような構文です。

(let ((変数1 値1)(変数2 値2) … (変数n 値n)) 本体1 … 本体m)

変数に値をバインドして、本体の関数を実行します。
ここで定義された変数は、let関数内だけのローカル変数となります。

変数にバインドする値としては整数やアトムに加えて、関数やλ式など
も使えます。
ローカル変数のスコープは、定義されたすぐ後ろのローカル変数の値
から有効になります。たとえば、変数1は、変数2の値や関数として使用
できます。
λ式を変数に設定すると、ローカル関数となります。また、再帰関数も
書くことができます。

本体の関数は、複数記述可能であり、最後に実行された関数の値が
let関数の値として返されます。

例) 次のプログラムを、ファイルに保存してください。
(let ((x 3) 
      (y (* 2 x)) 
      (z λx (cond ((<= x 1) 1) (T (* x (z (- x 1))))))) 
		(print (list x y (z 6))))


上の例では、xに3、yに6(2 * 3)、そして、zに階乗を計算する関数
が定義されます。
本体関数は、printとlistでx, y, (z 6)の結果をリストにして表示
します。

load関数で読み込むと実行されます。(例えば"let.lsp"と保存した場合)

(load "let.lsp")
>  (let ((x 3) (y (* 2 x)) (z (λ (x) (cond ((<= x 1) 1) (T (* x (z (- x 1))))))
)) (print (list x y (z 6))))
(3 6 720)
(3 6 720)

T

■7. 特別付録

Yコンビネータのプログラムを付録として付けました。

y-combinator-lisp  : デカルトLispプログラム
Y-combinator.txt   : 説明書

Yコンビネータは、無名のλ式だけで再帰関数が実行できるものです。
また、階乗、リストの長さ、およびフィボナッチ数の例も付けました。


以上

