人生は勉強ブログ

https://github.com/dooooooooinggggg

Common Lispの基礎的な文法

あくまで自分用のメモ。

今後の実装の時に、簡単に振り返るための記事。

プログラマは皆どのようにしてLisperと化して行くのか? <- この記事を読んで興味を持ったのが始めようと思ったきっかけ。

進めている本は、Land Of Lisp

Land of Lisp

Land of Lisp

所感

今までのどんな言語とも書き方が違う。

イメージでは、各命令は、シェルスクリプトの、コマンドのような印象を受けた。

あとは、全てがリストである。

この二つを意識して、書いて行きたい。

defparameter

グローバル変数を定義する。

このような変数を、ドップレベル定義と呼ぶ。

これを使って定義された変数は、同じようにまたdefparameterが使われると上書きされる。

(defparameter *foo* 5)

defvar

こちらは、上と違って、定義した変数の値が変更されない。

(defvar *foo* 5)

let

local変数の定義、ブロック内でのみ有効。

(let ((a 5)
    (b 6))
    (+ a b))

flet

local関数の定義、ブロック内でのみ有効。

(flet (
    (f (n)
        (+ n 10))
    )
    (f 5))

書式は、

(flet ((関数名 (引数)
    関数本体...s))
    本体....)

;; ブロックの初めで、関数を定義する(fという名前) ;; これを、処理本体で、利用している。

;; 一つのfletコマンドで、複数のローカル関数を一緒に宣言するには、単にコマンドの最初の部分に複数の宣言を書く。

(flet (
    (f (n)
        (+ n 10))
    (g (n)
        (- n 3)))
    (g (f 5)))

labels

ローカル関数の中で、同じスコープで定義されるローカル関数名を使用したい場合は、labelsコマンド

形式は fletと同じ

再帰

(labels (
    (a (n)
        (+ n 5))
    (b (n)
        (+ (a n) 6)))
    (b 10))

出力->21

比較

;; eq
(eq 'fooo 'foOo)
;; T

要は、大文字と小文字を区別しない。

関数を食べる関数

;; 関数を食べる関数
(defun my-length(list)
    (if list
        (1 + (my-length (cdr list)))
        0))

(my-length '(list with four symbols))

再帰の簡単な実装。

car cdr

carは、リストの一番目の要素を取り出す、リスト関数。

cdrは、リストの二番目以降を取り出す、リスト関数。

これらは、組み合わせて使って行くことができる。

caadadarみたいな使いかたもできる。

これらを内部で組み合わせた関数もたくさんある。

コードモード データモード

`をつけることで、データモードになる。

,をつけることで、データモードの中でも、一時的にコードモードになる。

if

(if ( = ( + 1 2) 3)
    'yup
    'nope)

(if ( = ( + 1 2) 4)
    'yup
    'nope)

やるべきではないかもしれないが、これをC言語で書いてみると、

if ( (1 + 2) == 3 ){
    return 1;
}else{
    return 0;
}

if ( (1 + 2) == 4 ){
    return 1;
}else{
    return 0;
}

このようになる。

progn

(defvar *number-was-odd* nil)

(if (oddp 5)

    ;; 本来ifでは一つのことしかできないが、prognを使って、複数のことをしている。
    (progn(setf *number-was-odd* t)
        'odd-number)
    'even-number)

*number-was-odd*

コメントアウトにもあるように、通常、ifでは、一つのことしかできないが、prognを使うことで、複数のことができるようになる。

when unless

(defvar *number-is-odd* nil)

;; ifではなく、whenを使うことによって、暗黙のprogn
(when (oddp 5)
    (setf *number-is-odd* t)
    'odd-number)

*number-is-odd*

(unless (oddp 4)
    (setf *number-is-odd* nil)
    'even-number)

*number-is-odd*

if文では、通常、評価がtrueだった場合には、その式しか実行されない(当然)

lispでは、whenを使うことで、暗黙のprognとなる。(イメージ的には、if (true) {hoge....})

その逆のunlessは、if (false) {hoge....}のような感じ。

cond

condは、カッコをたくさん使う代わりに、三目のprognが使えて、複数の分岐もかける。 それに加えて、続けていくつもの条件を評価することもできる。

(defvar *arch-enemy* nil)

(defun pudding-eater (person)
    (cond ((eq person 'henry) (setf *arch-enemy* 'stupid-lisp-alien)
            '(curse you lisp alien - you ate my pudding))
        ((eq person 'johnny) (setf *arch-enemy* 'unless-old-johnny)
            '(i hope you choked on my pudding johnny))
        (t '(why you eat my pudding stranger?))))

(pudding-eater 'johnny)
;; (I HOPE YOU CHOKED ON MY PUDDING JOHNNY)

*arch-enemy*
;; UNLESS-OLD-JOHNNY

(pudding-eater 'geroge-clooney)
;; (WHY YOU EAT MY PUDDING STRANGER?)

(pudding-eater 'henry)
;; (CURSE YOU LISP ALIEN - YOU ATE MY PUDDING)

case

他の条件分岐として、

(defun pudding-eater (person)
    (case person
        ((henry) (setf *arch-enemy* 'stupid-lisp-alien)
            '(curse you lisp alien - you ate my pudding))
        ((johnny) (setf *arch-enemy* 'useless-old-johnny)
            '(i hope you choked on my pudding johnny))
        (otherwise '(why you eat my pudding stranger ?))))

このように、caseを使うことができる。

member

(if (member nil '(3 4 nil 6))
    'nil-is-in-the-list
    'nil-is-not-in-the-list)

(if (member nil '(3 4 nil))
    'nil-is-in-the-list
    'nil-is-not-in-the-list)

memberを使うことで、ある要素が、そのリストに含まれているかを確認することができる。

しかし、ここが少し特殊で、

(member 1 '(3 4 1 5))

とすると、かえってくる値は、trueではなく、

;; (1 5)

となる。

こうなる理由に関しては、本を読んでもらえればわかるが、lispの条件の評価の仕方によるものである。

lispが偽であると判定する値には、実は4種類しかない。

'()
()
'nil
nil

以上の4こを除き、全て真となる。つまり、それぞれの関数は、真である、という情報以外に、好きなものを返すことができる。

また、上の例でいうと、もしこのmemberが見つけたリスト自身を返した場合、

通常は上手く行くが、

(if (member nil '(3 4 nil 6))
    'nil-is-in-the-list
    'nil-is-not-in-the-list)

この例でいくと、nilが返ってきてしまい、あるにもかかわらず、結果が、偽と判定されてしまう。

しかし、この実装のおかげで、返ってくる値は、nilではなく、(nil 6)、もしnilが最後の要素だったとしても、

(nil)が返ってくるため、真と判定できる。

eq

;; シンボル同士は、eqで比較する。
(defparameter *fruit* 'apple)

(cond ((eq *fruit* 'apple) 'its-an-apple)
    (eq *fruit* 'orange) 'its-an-orange)

;; シンボル同士で、eqを使えるのに、eqを使わないのは、いけてない

;; シンボル同士の比較
(equal 'apple 'apple)
;; T

;; リスト同士の比較
(equal (list 1 2 3) (list 1 2 3))
;; T

;; 異なる方法で作られたリストでも、中身が同じなら、同一とみなされる。

;; 整数同士の比較
(equal 5 5)


;; eqlとか、equalpなどは、紛らわしい。

;; ここら辺は後で調べに帰って来ればいいか。

lispには、等しいかどうかを調べる関数が、4つほどある。上に書いた通りで、それぞれ微妙に条件が異なる。

まとめ

今まで触ったどんな言語とも書き方が異なるため、写経を積極的に行なっている。

書かないと、本当に意味がわからない。

その時に書いて理解してみたものをここに書き連ねたため、漏れなどもあるかもしれない。

今後実装を進めていく際に、必要になりそうだったら、この記事に追記して行きたい。

続き↓

www.ishikawa.tech