読者です 読者をやめる 読者になる 読者になる

by shigemk2

当面は技術的なことしか書かない

Land of Lisp 3章-4章 メモ

勉強会

勉強会3章-4章(社内)のメモ

Ch3 Lispの構文の世界を探検する

3.1 シンタックスとセマンティクス

なんでLispは括弧だらけなのか

シンタックス テキストが正しい文を構成するために必要な、基本的な規則 セマンティクス 文の意味。多くの場合、同じセマンティクスを持つプログラムを異なるプログラミング言語で書くことができる

Lispは単純なシンタックスを持つ

3.2 Lispシンタックスの構成要素

cf. C++ 様々なシンタックスがあるので、例えばコンパイラを作ろうとしたら大変な仕事になる。

その点、Lispはシンタックスが非常にシンプルなのでコンパイラもインタプリタも簡単に作れる(≒機械に優しい)

Lispは括弧を使ってコードをリスト化するだけ。

[1]> (defun square (n)
 (* n n))
SQUARE
[2]> (square 4)
16

シンボル

Lisp の最も基本的なデータ型で、あらゆるところで使われる、独立した単語。なお、下の例もあるように、大文字小文字は区別しない(このあたりはPHPもそんな感じだった気がする)

[3]> (eq 'fooo 'FoOo)
T

数値

Lisp は浮動小数点数と整数の両方をサポート

オペランドの片方の型が浮動小数点だったら演算結果も浮動小数点。

[4]> (+ 1 1.0)
2.0

大きな数の演算も軽くできちゃう

[5]> (expt 53 53)
24356848165022712132477606520104725518533453128685640844505130879576720609150223301256150373

除算は有理数を返す

[6]> (/ 4 6)
2/3

2/3=0.666666みたいにしたい場合は、やっぱりオペランドの片方を小数点にするといい。

[7]> (/ 4.0 6)
0.6666667

文字列

文字列を出力したいときはprincを使う。なお、2回出ているのは、princで表示されたものと、princ自身の戻り値。

[8]> (princ "Tutti Frutti")
Tutti Frutti
"Tutti Frutti"
[9]> (princ "He yelled \"Stop that thief!\" from the busy street.")
He yelled "Stop that thief!" from the busy street.
"He yelled \"Stop that thief!\" from the busy street."

3.3 Lisp はコードとデータをどう区別するか

Common Lispはコードモードデータモードを行ったり来たりしている

  • コードモード コンパイラはそれを実行されるコマンドと解釈
  • データモード 全てデータとして扱われる(クオートを使うと実現する)

コードモード例

[10]> (expt 2 (+ 3 4))
128

データモード例

[11]> '(expt 2 3)
(EXPT 2 3)

3.4 Lisp とリスト

コンスセル

  • Lisp のリストはコンスセル
  • コンスセルは二つの小さなくっついた箱

リストを扱う関数

cons
[12]> (cons 'chicken 'cat)
(CHICKEN . CAT)
car/cdr

ScalaやHaskellでいうところのhead/tail

[13]>  (car '(pork beef chicken))
PORK
[14]>  (cdr '(pork beef chicken))
(BEEF CHICKEN)
cadr

cdr→car

[15]>  (cadr '(pork beef chicken))
BEEF
list

List(1,2,3) List[1,2,3]

[16]>  (list 'pork 'beef 'chicken)
(PORK BEEF CHICKEN)
ネスト

ネストしてても変わらない

[19]> (car '((peas carrots tomatoes) (pork beef chicken)))
(PEAS CARROTS TOMATOES)
[20]>  (cdr '(peas carrots tomatoes))
(CARROTS TOMATOES)
[21]>  (cdr (car '((peas carrots tomatoes) (pork beef chicken))))
(CARROTS TOMATOES)
[22]>  (cdar '((peas

3.5 本章で学んだこと

  • Lisp の括弧は、シンタックスを最小限に保つためにある
  • リストはコンスセルから作られる
  • consコマンドでコンスセルを作ってゆくことで、リストが構成される
  • carと cdrを使ってリストの中身を調べることができる

おまけ

[63]> (cons (cons 'pork 'ch) (cons 'chick 'po)))
((PORK . CH) CHICK . PO)
[73]> (cons (cons 'pork 'ch) (cons 'chick 'po))
((PORK . CH) CHICK . PO)
scala> List(1,2) :: List(3,4)
res3: List[Any] = List(List(1, 2), 3, 4)

Ch4 条件と判断

4.1 nil と () の対称性

空とは偽なり 空のリストはfalse

[23]>  (if '()
 'i-am-true
 'i-am-false)
I-AM-FALSE
[24]>  (if '(1)
 'i-am-true
 'i-am-false)
I-AM-TRUE

4.2 条件分岐 : If とその仲間たち

ifは一度に一つずつ

[33]>  (if (= (+ 1 2) 3)
 'yup
 'nope)
YUP
[34]>  (if (= (+ 1 2) 4)
 'yup
 'nope)
NOPE

when

[35]> (defvar *number-is-odd* nil)
*NUMBER-IS-ODD*
[36]>  (when (oddp 5)
 (setf *number-is-odd* t)
 'odd-number)
ODD-NUMBER
[37]>  *number-is-odd*
T

cond

(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* 'useless-old-johnny)
         '(i hope you choked on my pudding johnny))
        (t '(why you eat my pudding stranger ?))))
(pudding-eater 'johnny)
(pudding-eater 'george-clooney)

case

先の例はcondを使わずとも書き直せる

[76]> (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 ?))))

4.3 ちょっとした条件式のテクニック

and と or を使う

これは他の言語のor条件とかand条件とかと変わらない

[78]>  (and (oddp 5) (oddp 7) (oddp 9))
T
[79]>  (and (oddp 5) (oddp 7) (oddp 8))
NIL
[80]>  (or (oddp 4) (oddp 7) (oddp 8))
T
[81]>  (or (oddp 4) (oddp 6) (oddp 8))
NIL

真理以上のものを返す関数

TもNILも返さない関数。

[82]> (if (member 1 '(3 4 1 5))
 'one-is-in-the-list
 'one-is-not-in-the-list)
ONE-IS-IN-THE-LIST
[83]> (member 1 '(3 4 1 5))
(1 5)

4.4 比較関数 : eq、equal、そしてもっと

オペランドがシンボルだとわかっているならeqのほうが断然速いし、もしシンボル同士の比較でeq以外を使っていたらバカにされるレベル。

[84]> (defparameter *fruit* 'apple)
*FRUIT*
[85]> (cond ((eq *fruit* 'apple) 'its-an-apple)
 ((eq *fruit* 'orange) 'its-an-orange))
ITS-AN-APPLE

シンボル同士の比較でなければequalのほうがよい

[86]>  (equal (list 1 2 3) (list 1 2 3))
T
[87]>  (equal '(1 2 3) (cons 1 (cons 2 (cons 3 ()))))
T

eql 数字も文字も比較できる

[88]>  (eql 'foo 'foo)
T
[90]>  (eql 3.4 3.4)
T

equalp 複雑な比較の場合について equalよりもう少し賢い判断をする。ここでいう賢いとは、文字列について大文字小文字の使い方が異なるものでも比較可能とか、そういうの。

[91]> (equalp "Bob Smith" "bob smith")
T
[92]> (eql "Bob Smith" "bob smith")
NIL
[93]> (eq "Bob Smith" "bob smith")
NIL
[94]> (equal "Bob Smith" "bob smith")
NIL

4.5 本章で学んだこと

  • Common Lisp では、式 nil、'nil、()、'()の値は全て同じである
  • Lisp では空リストかどうかを調べるのが簡単。そのためリストを頭から食べてゆく関数を簡潔に書ける
  • if等の Lisp の条件式は、条件に合致した部分の式しか評価しない
  • 条件式でいろんなことを一度にやりたければ、condが使える
  • Lisp でもの同士を比較するやり方はいくつもあるけれど、原則としてシンボルを比べる時は eqを、それ以外のものを比べる時は equalを使う、と覚えておけば良い。

Land of Lisp

Land of Lisp