Skip to content

Instantly share code, notes, and snippets.

@otaon
Last active April 30, 2023 07:23
Show Gist options
  • Save otaon/0a731fb98016c163732114c4d2c40430 to your computer and use it in GitHub Desktop.
Save otaon/0a731fb98016c163732114c4d2c40430 to your computer and use it in GitHub Desktop.
common lisp の環境構築から基本構文を説明

本記事の構成

  • 001_Common-Lisp-基本構文説明.md
  • 002_Common-Lisp-リスト操作説明.md
  • 003_Lisp処理系起動時のディレクトリパス-カレントディレクトリパスを取得-設定する.md

目的

  • Common Lisp の基本的なシンタックスをすぐに確認するため。
  • 自分用の備忘録。

前提条件

  • Common Lisp および Lisp が、括弧だらけのプログラミング言語だと知っていること。
  • Common Lisp の実行環境を持っていること。(CLisp、Steel Bank Common Lisp など)

Lisp = ListProcessor (List構造を理解しよう)

LispとAST

LispはAST(抽象構文木)を直接記述するかの如くプログラミングする言語である。 換言すれば、ソースコードが殆どASTと等価であると見做せる。 また、Lispは、そのインタプリタによって、逐次的にソースコードを読み込み、評価し、表示する。 そして、この手順を繰り返してソースコード全体を処理する。 この仕組みを、REPL(Read–Eval–Print Loop)という。

コンスセル

上記のとおり、Lispのソースコードは殆どASTであるから、データ構造も命令と同様の表現を取る。 即ち、全ての表現を同様に記述できる。 これを実現するために、Lispでは「コンスセル」という概念を導入している。 これは、単純化すれば、2つの値を代入できる__箱__である。 視覚化するならば、[a|b]のようになる。このaおよびbに、値が入る。 ここで、値とは、シンボルへの参照と、コンスセルへの参照を意味する。 即ち、Lispでは、ソースコードのアトムとしてシンボルがあり、それらが構造を成す際にコンスセルを用いているといえる。

リスト表現の例

下記に、Lispに於けるリスト表現を樹形図として表す。

'(A)
*--nil
|
A

'((A B) (C D))
*----------*-----nil
|          |
*--*--nil  *--*--nil
|  |       |  |
A  B       C  D

リストの記述方法

リストは(list hoge foo bar)の様に記述する。 またはquote(')を使って、'(hoge foo bar)の様に記述する。

'(a b c)
; => (A B C)

```cl
(defun describe-path (edge)
 `(there is a ,(caddr edge) going ,(cadr edge) from here.))

`(describe-path '(garden west door))
; => (THERE IS A DOOR GOING WEST FROM HERE.)

コンスセル・リストの操作方法

下記に、コンスセル、および、リストの操作方法を示す。

(cons 'x 'y)
; => (x . y)
(cons 'x nil)
; => (X)
(cons 'x ())
; => (X)

; 先頭のスロットのデータを取り出す
(car '(a b c d))
; => A

; 2番めのスロットのデータを取り出す
(cdr '(a b c d))
; => (b c d)

; car cdr を同時に行う関数
(cadr '(a b c))
= (car (cdr '(a b c)))
; => 'B

キーワードシンボル

コロンを先頭に付けたシンボルの使い方の1つ。 シンボルが、そのシンボルそのままの名前を評価値に持つ。 用途は以下がある。

  • 普通の変数により、値を持たせる
(let ((cigar 5))
   cigar)
; => 5

:cigar
; => CIGAR
  • 定数として使う

定数として使うと、Lispが処理をより効率的に最適化してくれる。

(let ((:cigar 5))
   :cigar)
; => LET :CIGAR is constant ...

変数

Lispに於ける変数は、他の言語と同様に、グローバル変数とローカル変数が存在する。

グローバル変数

上書き 定義
する (defparameter *global-value* num)
しない (defvar *global-value* num)

ローカル変数

(let ((variable-name1 value1)
      (variable-name2 value2))
  ...)

例:

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

関数

Lispに於ける関数は、グローバル関数とローカル関数が存在する。

グローバル関数

(defun function-name (arguments)
  ...)

(defun function-name (arguments)
       (ash (+ *small* *big*) - 1))
; (ash x n) := xを2進nビット左にずらす

ローカル関数

基本的な定義方法

(flet ((function-name1 (arguments)
        ...)
       (function-name2 (arguments)
        ...))
      ...)

例:

(flet function-name (arguments)
       (ash (+ *small* *big*) - 1))
; (ash x n) := xを2進nビット左にずらす

ローカル関数定義中、同じスコープで定義されるローカル関数を使う場合

(labels ((function-name1 (arguments)
       ...)
      (function-name2 (arguments)
       (function-name1 *arguments*))
     ...)

例:

(labels ((a (n)
          (+ n 5))
         (b (n)
          (+ (a n) 6)))
 (b 10))
; => 21

関数をパラメータとして渡す

functionパラメータ

下記の表記を使うことで、関数をパラメータとして渡すことができる。 これを使って、mapcar関数などに使用できる。

; (方法1)
#'関数名

; (方法2)
(function 関数名)

無名関数(Lambda)

一連の処理を纏める。 関数名を定義するまでもない場合に有効。

構文:

(lambda (args) (procedure))
; => <FUNCTION :LAMBDA...>

例:

; 下記のように通常どおり処理を纏めると関数名が必要となる。
(defun half (n)
   (/ n 2))
; => <FUNCTION :HALF...>

; 下記のように書けば関数名は不要となる。
(lambda (n)
   (/ n 2))

(mapcar (lambda (n) (/ n 2)) '(2 4 6))
; => (1 2 3)

返り値が複数ある関数

Lispでは、返り値を複数持つ関数が存在する。例えばread-lineがそれにあたる。 通常は1つ目の返り値しか使われない。

(read-line)
; abc
; => "abc";
     NIL

複数の返り値を持つ関数の作成方法

下記のようにvaluesを用いる。

(defun foo ()
   (values 3 7))

(foo)
; => 3;
;    7

複数の返り値を受け取る方法

multiple-value-bindを用いる。

multiple-value-bindの構文:

(multiple-value-bind '(binding-vals) (func-call))
'(binding-vals)
(func-call)の返り値を束縛するための変数のリスト。
(func-call)
複数の返り値を持つ関数の呼び出し。

例:

(defun foo ()
   (values 3 7))

(multiple-value-bind (a b) (foo)
   (list a b))
; => ( 3 7)

; fooの返り値を、aとbに束縛している。

制御構文

条件分岐

if

(if (審議を返すリスト)
  (真の時に実行するリスト)
  (偽の時に実行するリスト))

例:

(if (= (+ 1 2) 3)
  'yup
  'nop)
; => YUP

when

(when (条件式)
  (処理1)
  (処理2)
  ...
  (処理n))

例:

(when (oddp 5)
  (setf *number-is-odd* t)
  'odd-number)
; => ODD-NUMBER
*number-is-odd*
; => T

unless

(unless (条件式)
	(処理1)
	(処理2)
	...
	(処理n))

例:

(unless (oddp 4)
  (setf *number-is-odd* nil)
  'even-number)
; => EVEN-NUMBER
*number-is-odd*
; => nil

progn

上記に示したifなどの制御構文では、実行処理するリストが1つのみ持てる構造となっている。 しかし、一般的には複数の処理を行いたいことが多い。 prognは、中のリストを順繰りに評価して、最後の評価値をprognフォームの値として持つ。

例:

(if '(oddp 5)
  (progn (setf *number-was-odd* t)
   'odd-number)
   'even-number)
; => ODD-NUMBER
*number-was-odd*
; => T

case

condは、C言語のswitch-case文のようなものである。 つまり、複数のシンボルに対するテストを行う。 caseのシンボルと、それぞれのシンボルをeqlを使って評価する。 もし偽と評価されれば、otherwiseに分岐される。

(case シンボル
  ((分岐のシンボル) (処理1)
                  (処理2)
                  ...
                  (処理n))
  ((分岐のシンボル) (処理1)
                  (処理2)
                  ...
                  (処理n))
  (otherwise      (処理1)
                  (処理2)
                  ...
                  (処理n)))

cond

condは、caseと同じく、C言語のswitch-case文のようなものである。 ただし、C言語のswitch-case文とは異なり、シンボルではなく条件式を列挙する。

(cond ((条件式1) (処理1)
                (処理2)
                ...
                (処理n))
      ((条件式2) (処理1)
                (処理2)
                ...
                (処理n))
      (t        (処理1)
                (処理2)
                ...
                (処理n)))
; 条件式nが全て偽だった時、tが実行される。

ループ処理

loop

loopは、第1引数の値によって、異なるループ方法を指定できる。

(loop repeat 10
   collect 1)
; => (1 1 1 1 1 1 1 1 1 1)

(loop for n from 1 to 10
   collect n)
; => (1 2 3 4 5 6 7 8 9 10)

(loop for n from 1 to 10
   collect (+ 100 n))
; => (101 102 103 104 105 106 107 108 109 110)

論理演算

C言語の||&&のように、制御構文の条件式に使用できる。

(or (oddp 4) (setf *it-is-even* t))
; => T
*it-is-even*
; => T

短絡評価: 下記のように、lispは短絡評価する。

(or (oddp 5) (setf *it-is-even* t))
; => T
*it-is-even*
; => NIL

シンボル・リストを比較、検査する

ここでは、シンボル、または、リストの値が他のリストの値と等しいか、また、リストの中に目当ての値が入っているかなどを調べるための方法を示す。

eq,equal,eql,equalp,=,string-equal,char-equal関数

シンボルやリストが等しいか否かを調べる関数。

関数名 比較方法
eq シンボル同士はeqで比較する
equal シンボル同士では無い場合はequalで比較する
eql eqと似ているが、更に数値と文字を比較できる
equalp equalと似ているが、少し緩い評価をする
= 数値用のequal
string-equal 文字列用のequal
char-equal 文字用のequal

例:

(defparameter *fruit* 'apple)
=> *FRUIT*
(cond	((eq *fruit* 'apple) 'its-an-apple)
		((eq *fruit* 'orange) 'its-an-orange))
(equal 'apple 'apple)
=> T
(equal 'apple 'apple)
=> T
(equal (list 1 2 3) (list 1 2 3))
=> T
(equal '( 1 2 3) (cons 1 (cons 1 (cons 2 (cons 3 nil)))))
=> T	; 中身が同じなら同一とみなす
(equal 5 5)
=> T	; 整数同士
(equal 2.5 2.5)
=> T	; 浮動小数点同士
(equal "foo" "foo")
=> T	; 文字列同士
(equal #\a #\a)
=> T	; 文字同士
(eql 'foo 'foo)
=> T	; シンボル同士
(eql 3.4 3.4)
=> T	; 数値同士
(eql #\a #\a)
=> T	; 文字列同士
; equalpの使い方
(equalp "Bob Smith" "bob smith")
=> T	; 大文字、小文字を無視した文字列同士
(equalp 0 0.0)
=> T	; 整数と浮動小数点数

coerce関数

オブジェクトを指定した形式へ強制変換する。 (coerce : 〜を無理にdoさせる。)

(coerce '(a b c) 'vector) ; => #(A B C)
(coerce 'a 'character) ; => #\A
(coerce 4.56 'complex) ; => #C(4.56 0.0)
(coerce 4.5s0 'complex) ; => #C(4.5s0 0.0s0)
(coerce 7/2 'complex) ; => 7/2
(coerce 0 'short-float) ; => 3.5L0
(coerce 3.5L0 'float) ; => 3.5L0
(coerce (cons 1 2) t) ; => (1 . 2)
; NOTE: タイプtは、全てのオブジェクトのタイプの集合。
;       従って、この場合は、引数をそのまま返す。

complement関数

補集合。(高階関数)

例:

(complement #'alphanumericp)
; => アルファベットでも数字でもない

目的

この記事では、リスト操作において基本的な関数の使い方について説明する。

関数の説明

member関数

リスト中に、或る要素が含まれているか否かを調べる。

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

; memberの実際の返り値
(member 1 '(3 4 1 5))
=> (1 5)

上記に示したように、memberの挙動は下記のとおりである。

  1. リストをcarして、
  2. その要素が目的oの値だったら、
  3. そのリストを返す。
  4. 違ったら、cdrしたリストを対象にする。
  5. 上記1-4を繰り返す。
  6. 最後までみつからなかったらNILを返す。

例:

(member 3 '(1 5 3 4 (1 2) 4))
; => (3 4 (1 2) 4)
(member '(1 2) '(3 (1 2) 4))
; => NIL  ; シンボルでないと駄目らしい

find-if関数

第2引数をcarした値を対象に、第1引数に指定した関数を使って評価し、 真を返した値を返す。 もしも全ての値が偽だった場合はnilを返す。

(find-if #'func '(hoge foo bar))
; => (foo bar)

#'oddpにより、リストから奇数が見つかるまで順に操作して、 真を返す値を見つけたら、その値を返す。 さもなければ、空リストを返す。

例:

(if (find-if #'null '(2 4 5 6))
	'there-is-an-odd-number
	'there-is-no-odd-number)
; => 'THERE-IS-AN-ODD-NUMBER

注意:nilを探す場合には使えない。

(find-if #'null '(2 4 nil 6))
; => NIL

assoc関数

連想リスト(association list)から、指定したキーのリストを返す。

(assoc 'key2 '((key1 (list1)) (key2 (list2)) (key3 (list3))) )
; => (KEY2 (LIST2))

例:

(assoc 'a '((a (1 2)) (b (3 4)) (c (5 6)) (d (7 8))) )
; => (A (1 2))

mapcar関数

リストを第n引数(2 <= n <= N)に持ち、これの要素を1つずつ取り出して、第1引数の関数に対して適用する。

(mapcar #'func '(list-for-arg-1) ... '(list-for-arg-n))
; => `((func '(list-for-arg-1の第1要素) ... '(list-for-arg-nの第1要素))
;      (func '(list-for-arg-1の第2要素) ... '(list-for-arg-nの第2要素))
;      ...
;      (func '(list-for-arg-1の第m要素) ...'(list-for-arg-nの第m要素)))

定義:

(defuc mapcar (func mlist)
  (cond ((null mlist)
          nil)
        (t
          (cons (funcall func (car mlist)) (mapcar func (cdr mlist))) ))

; 上記のmapcarを実行した結果
(mapcar #'+ '(1 2 3))
; =>
; mapcarを定義にしたがって展開する
(cons (funcall #'- (car '(1 2 3))) (mapcar #'- (cdr '(1 2 3))))
; =>
(cons (funcall #'- 1) (mapcar #'- '(2 3)))
; =>
(cons -1 (mapcar #'- '(2 3)) )
; =>
(cons -1 (cons (funcall #'- 2) (mapcar #'- '(3))) )
; =>
(cons -1 (cons -2 (mapcar #'- '(3)) ) )
; =>
(cons -1 (cons -2 (cons (funcall #'- 3) (mapcar #'- nil)) ) )
; =>
(cons -1 (cons -2 (cons -3 (mapcar #'- nil)) ) )
; =>
(cons -1 (cons -2 (cons -3 nil) ) )
; =>
(-1 . (-2 . (-3 . () ) ) )
; =>
(-1 -2 -3)

append関数

引数に会う複数のリストを、1つのリストに統合する。 (append = 〜を付け加える。)

(append '(list1) '(list2) '(list3))
; => (LIST1 LIST2 LIST3) 

例:

(append '(many had) '(a) '(little lamb))
; =>
(MARY HAD A LITTLE LAMB)

apply関数

引数にある、複数の要素を持つリスト(要素1つのリストでも良い)の各要素を、 引数にある関数の引数として渡す。

(apply #'func '(list))

例1:

(apply #'1+ '(2))
; => 3

(apply #'+ '(2 3))
; => 5

(apply #'append '((mary had) (a) (little lamb)) )
; => (MARY HAD A LITTLE LAMB)

append関数とapply関数の使用例

とあるゲーム(Land of Lispのアレ)を例に示す。

ゲームの変数を定義する。

  • とあるゲームにおいて、各々の場所について(つながっている他の場所 移動方向 移動方法)を定義
(defparameter *edges* '((living-room (garden west door)
                                    (attic upstairs ladder)
                       (garden      (living-room east door)
                       (attic       (living-room downstairs ladders))))
  • とあるゲームにおいて、登場するオブジェクトを定義
(defparameter *objects* '(whiskey bucket frog chain))
  • とあるゲームにおいて、登場するオブジェクトが置いてある場所を定義
(defparameter *objects-locations* '((whiskey living-room)
                                    (bucket living-room)
                                    (chain garden)
                                    (frog garden)) )

関数を定義する。

  • 与えられたパスを表示する
(defun describe-path (edge)
  '(there is a ,(caddr edge) going ,(cadr edge) from here.))

; 使用例:
(describe-path '(garden west door))
; => (THERE IS A DOOR GOING WEST FROM HERE.)
  • 指定した場所のパスを一覧表示する
(defun describe-paths (location edges)
  (apply #' append (mapcar #'describe-path (cdr (assoc location edges)) )) )

; 使用例:
(describe-paths 'living-room *edges*)
; => (THERE IS A DOOR GOING WEST FROM HERE. \
;     THERE IS A LADDER GOING UPSTAIRS FROM HERE.)
  • 指定した場所にあるオブジェクトを一覧表示する
(defun objects-at (loc objs obj-locs)
  (labels ((at-loc-p (obj)
             (eq (cadr (assoc obj obj-locs)) loc)))
             ; remove-if-not : predicate が偽となる要素を取り除く
             (remove-if-not #'at-loc-p objs)))

; 使用例:
(objects-at 'living-room *objects* *objects-locations*)
; => (objects-at *objects* *objects-locations*)

; => (remove-if-not #'at-loc-p *objects*)

; => 下記の評価を*objects*リストの全要素について行い、
;    真になったものをリストから消す。
;    (eq (cadr (assoc obj obj-locs)) 'living-room)
;                     │   └ '((whiskey living-room) 
;                     │       (bucket living-room) 
;                     │       (chain garden) 
;                     │       (frog garden))
;                     └ 'whiskey
;    => T
;    (eq (cadr (assoc obj obj-locs)) 'living-room)
;                     │   └ '((whiskey living-room) 
;                     │       (bucket living-room) 
;                     │       (chain garden) 
;                     │       (frog garden))
;                     └ 'bucket
;    => T
;    (eq (cadr (assoc obj obj-locs)) 'living-room)
;                     │   └ '((whiskey living-room) 
;                     │       (bucket living-room) 
;                     │       (chain garden) 
;                     │       (frog garden))
;                     └ 'frog
;    => F
;    (eq (cadr (assoc obj obj-locs)) 'living-room)
;                     │   └ '((whiskey living-room) 
;                     │       (bucket living-room) 
;                     │       (chain garden) 
;                     │       (frog garden))
;                     └ 'chain
;    => F
 
; => (WHISKEY BUCKET)
  • 全てのオブジェクトを一覧表示する
(defun describe-objects (loc objs obj-loc)
  (labels ((describe-obj (obj)
              `(you see a ,obj on the floor.) ))
   (apply #'append (mapcar #'describe-obj (objects-at loc objs obj-loc)))))

; 使用例:
(describe-objects 'living-room *objects* *objects-locations*)
; => (apply #'append (mapcar #'describe-obj (objects-at living-room *objects* *objects-locations*)))
; => (apply #'append (mapcar #'describe-obj '(WHISKEY BUCKET)))
; => (apply #'append '((YOU SEE A WHISKEY ON THE FLOOR.) '(YOU SEE A BUCKET ON THE FLOOR.)))
; => (append '(YOU SEE A WHISKEY ON THE FLOOR.) '(YOU SEE A BUCKET ON THE FLOOR.))
; => (YOU SEE A WHISKEY ON THE FLOOR. YOU SEE A BUCKET ON THE FLOOR.)

find関数

リストから引数の要素を探す。

(find 'keyword 'object :key #'func)
; 'objectの各要素に対してfuncを適用した値がkeywordと等しいとき、その要素を返す。

例:

(find 'y '((5 x) (3 y) (7 z)) :key #'cadr)
; => (3 Y)

push関数

引数をリストの先頭に追加する。

(push val '(list))
; => (val list)

下記の2つの処理は同等。

(push 7 *foo*)

(setf *foo* (cons 7 *foo*))

例:

(defparameter *foo* '(1 2 3))
(push 7 *foo*)
; => (7 1 2 3)
*foo*
; => (7 1 2 3)

assoc関数とpush関数を使った、alist(association list)の値変更

assoc関数は、先頭から探索して、最初に見つかった要素を返したら残りは無視する。 従って、assocpushを使用すると、alistの値を、さも変更したかのように見せられる。

例:

(defparameter *foo* '((whiskey living-room) 
                      (bucket living-room) 
                      (chain garden) 
                      (frog garden)))

(assoc 'whiskey *foo*)
; => (WHISKEY LIVING-ROOM)

(push '(whiskey void) *foo*)

(assoc 'whiskey *foo*)
; => (WHISKEY VOID)

*foo*
; => ((WHISKEY VOID) 
;     (WHISKEY LIVING-ROOM) 
;     (BUCKET LIVING-ROOM) 
;     (CHAIN GARDEN) 
;     (FROG GARDEN))

本記事の目的

この記事では、文字列表示、および、文字列操作において基本的な関数の使い方について説明する。

文字列出力用の関数

print関数

print関数は下記の順で文字を表示する。

  1. 現在の表示位置が行頭でない場合、改行する
  2. 引数を表示する
  3. 空白を表示する

例:

(progn (print "this")
       (print "is")
       (print "a")
       (print "test"))
; => "this"_
;    "is"_
;    "a"_
;    "test"_
; (_ = 空白)

prin1関数

prin1関数は下記の順で文字を表示する。

  1. 引数を表示する

例:

(progn (prin1 "this")
       (prin1 "is")
       (prin1 "a")
       (prin1 "test"))
; => "this""is""a""test"

princ関数

人間が見やすい形式で文字を表示する。

例:

(princ '3) ; => 3
(print '3) ; => 3

(princ '3.4) ; => 3.4
(print '3.4) ; => 3.4

(princ 'foo) ; => FOO
(print 'foo) ; => FOO

(princ '"foo") ; => foo
(print '"foo") ; => "foo"

(princ '#\a) ; => a
(print '#\a) ; => #\a

#\newline

#\newlineを用いることで、文字列中に改行を入れられる。

(progn (princ "This sentence will be interrupted")
       (princ #\newline)
       (princ "by an annoying newline character."))
; => This sentence will be interrupted
;    by an annoying newline character.

prin1-to-string関数

シンボルを含むリストを文字列に変換する。

(print1-to-string '(a b c d))
; => "(A B C D)"

(cons 'foo (list (prin1-to-string '(a b c d))))
; => (FOO "(A B C D)")

princ-to-string関数

上記のprin1-to-string関数の出力を、人間が読みやすい形式にしたもの。

(princ-to-string '(a b c d))
; => "(A B C D)"

(princ-to-string '(a b "c" d))
; => "(A B c D)"

write-to-string関数

prin1-to-string関数やprintc-to-string関数の一般形。

(write-to-string object :escape t)
; =
(print1-to-string object) ; escape文字が出力される

;
(write-to-string "abc" :escape t)
; => "\"abc\""

(write-to-string object :escape nil :readably nil) ; escape文字が出力されない
; =
(princ-to-string object)

;
(write-to-string "abc" :escape nil :readably nil)
; => "abc"

文字入力用の関数

read関数

標準入力から得たものを返す。文字列からS式になる。

構文:

(read &optional input-stream eof-error-p eof-value recursive-p) ; => Object
&optional
ここから右の引数は任意。
input-stream
ストリーム指定。
デフォルト : 標準入力
eof-error-p
eof検出時の処理指定。
t : デフォルト。エラーを吐く。
nil : エラーを吐かない。
eof-value
eof検出時の返り値指定。
デフォルト : nil
recursive-p
これがnil以外の場合、readを再帰呼び出しする。
デフォルト : nil

例:

(read)
; a
; => A

(read)
; 'a
; => 'A

(read)
; (a)
; => (A)

(read)
; "a"
; => "A"

; readの挙動について細かく見るための例を以下に示す。
(setq a "line 1
   line2")
; 1の後ろに改行文字があることに注意

; => "line 1
;    line2"

(read (setq input-stream (make-string-input-stream a)))
; => LINE

(read input-stream)
; => 1

(read input-stream nil nil)
; => LINE2

(read input-stream nil nil)
; => NIL

read-line関数

入力から1行ずつ得たものを文字列として返す。

NOTE: newlineからeofまで見る。

NOTE: read-line関数は、実行するたびに新規に文字列を生成する。 したがって、大量の行を読み込むとガベージコレクションの実行時間が長くなりすぎる。 read-sequenceを使用する方が実用的である。 しかし、read-sequenceは使用が面倒である。

構文:

(read-line &optional input-steam eof-error-p eof-value recursive-p) ; => line, missing-newline-p
&optional
ここから右の引数は任意。
input-stream
ストリーム指定。
デフォルト : 標準入力
eof-error-p
eof検出時の処理指定。
t : デフォルト。エラーを吐く。
nil : エラーを吐かない。
eof-value
eof検出時の返り値指定。
デフォルト : nil
recursive-p
これがnil以外の場合、readを再帰呼び出しする。
デフォルト : nil
返り値2: missing-newline-p
次の行があるか否かを表す。
read-line実行時にnewlineで終わっていたらnilを返す。
eofで終わっているか、第1引数がeof検出時の返り値ならtを返す。

例:

(read-line)
; abc
; => "abc";
;    NIL

(read-line)
; "abc"
; => "\"abc\"";
;    NIL

(setq a "line 1
   line2")
; 1の後ろに改行文字があることに注意

; => "line 1
;    line2"

(read-line (setq input-stream (make-string-input-stream a)))
; => "line 1";
;    NIL
(read-line input-stream nil nil)
; => "line2";
;    T
(read-line input-stream nil nil)
; => NIL;
;    T

read-from-string関数

文字列から、それを入力とみなして返す。

構文:

(read-from-string string 
                  &optional 
                  eof-error-p 
                  eof-value 
                  &key 
                  start 
                  end 
                  preserve-whitespace)
; => Objects, position
eof-error-p
eof検出時の処理指定。
t : デフォルト。エラーを吐く。
nil : エラーを吐かない。
eof-value
eof検出時の返り値指定。
デフォルト : nil
start end
文字列の左端と右端を指定する。
デフォルト : start = 0, end = nil
preserve-whitespace
whitespaceを保持するか否かを指定する。
t : 保持する。
nil : デフォルト。保持しない。
返り値 position
読まれなかった文字列の最初の箇所を指す。
もし全体が読み込まれた場合、positionは文字数か、文字数 + 1を示す。

例:

(setq foo "(a b c d)")
; => FOO

(read-from-string foo)
; => (A B C D);
     9

文字列編集用の関数

string-trim関数

第1引数の文字を、第2引数の文字列の両端から除く。 第1引数の文字は1つずつ独立して評価される。 つまり、第1引数に"abc"が指定されている場合、 両端から「a」「b」「c」の文字のいずれにもマッチしない文字が現れるまで文字を取り除き続ける。

(string-trim "abc" "abcaakaaaabckabcbaaa")
; "abcaakaaaabckabcbaaa"
;  ~~~~~kaaaabck~~~~~~~ (~の部分が除かれる)
; => kaaaabck

(string-trim '(#\Space #\e #\t) " trim me ")
; => "rim m"

NOTE: スペース、タブ、改行を削除対象に指定したい場合は下記のようにする。

; 下記のリストを指定する。
(string-trim '(#\Space #\newline #\tab #\IDEOGRAPHIC_SPACE) str)

string-left-trim関数

第1引数の文字を、第2引数の文字列の左端から除く。

string-right-trim関数

第1引数の文字を、第2引数の文字列の右端から除く。

concatenate関数

文字列やリストを結合する。

(concatenate 'string "Hello " "world.")
; => "Hello World."

(concatenate 'list '(a b) '(c d) '(e f))
; => (A B C D E F)

NOTE: ただし、リストの場合はappendを用いれば良い。

(append '(a b) '(c) '(d))
; => (A B C D)

substitute-if関数

与えられたテスト関数の結果によって値を置き換える。 (substitute : 〜を代用する。代理をする。)

構文:

(substitute-if new-item predicate sequence &key from-end start end count key)
; => result-sequence
new-item
置換後の要素。
predicate
述語。#'関数を描く要素に照らし合わせて真偽を判断する。
sequence
proper sequence(適切なシーケンス)
1次元配列や、popしていくと()になるリスト。
つまり、巡回リスト以外のリスト、および、最後のセルの参照先が非nil以外のリスト。
count
置換する要素数の上限。
デフォルト : nil
from-end
countがセットされていれば働く。右端から上限判定を行う。
デフォルト : false
start end
置換範囲は、startより大きく、end以下までの中に制限される。
デフォルト : start = 0, end = nil
例: :start 2 :end 5 なら (x x o o o x x)

例:

(substitute-if #\e #'digit-char-p "I'm a l33t hack3r!")
; => I'm a leet hacker!

subseq関数

引数の文字列から、両端の各々任意の文字数を取り除いた文字列を返す。 NOTE: endの一つ前までしか表示されないことに注意。

構文:

(subseq sequence start-position end-position)
; end-position は optional で、デフォルトではnil

例:

; "h e l l o   w o r l d"
;  0 1 2 3 4 5 6 7 8 9 10

(subseq "hello world" 3)
; => "lo world"

(subseq 3 5)
; => "lo"

length関数

文字列の長さを返す。

(length "abcdefg")
; => 7

fresh-line関数

行頭に出力ストリームがない場合、newlineを出力する。

; 下記のようなプロンプトの状態になっているときを考える。
; 前の出力 CLISP>

(fresh-line)
; => プロンプトが下記のように改行される。
; 前の出力
; CLISP>

terpri関数

出力ストリームにnewlineを出力する。

ファイル操作用の関数

with-open-file関数

関数内でfileをopenし、streamで制御する。 処理終了時には、fileをcloseする。

構文:

(with-open-file (my-stream
                 "file-name.txt"
                 :direction :output
                 :if-exsts :supersede)
   (procedure))
my-stream
ストリーム名の指定
"file-name.txt"
open対象のファイル名
:direction
方向(入出力)の指定
output : 出力
input : 入力
:if-exsts
ファイルが既存のとき、
:supersede : 置き換える
procedure
ファイルオープン後の処理

例:

(with-open-file (my-stream
                 "file-name.txt"
                 :direction :output
                 :if-exsts :supersede)
   (princ "Hello File!" my-stream))
; => princでmy-streamに投げた文字列を、そのmy-streamに関連付けたファイルに書き出す。

本記事の目的

Lisp処理系起動時のディレクトリパス/カレントディレクトリパスを取得/設定する

参考サイト

処理系を起動したディレクトリのパスネームを返す

処理系を起動したディレクトリのパスネームを返す

truenameを使う。

対応環境: SBCL, CLISP, CMUCL

(truename "./")  ;=> #P"処理系を起動したディレクトリ"

実行例

(truename "./") ;=> #P"/users/username/"

各処理系でのカレントディレクトリの取得と設定

対応環境: SBCL

; ディレクトリパスを取得する
(sb-posix:getcwd) ;=>#P"カレントディレクトリ"

; ディレクトリパスを設定する
(sb-posix:chdir #P"設定したいディレクトリ")

対応環境: CLISP

; ディレクトリパスを取得する
(ext:default-directory) ;=>#P"カレントディレクトリ"

; ディレクトリパスを設定する
(ext:cd #P"設定したいディレクトリ")

対応環境: CMUCL

; ディレクトリパスを取得する
(extensions:default-directory) ;=>#P"カレントディレクトリ"

; ディレクトリパスを設定する
(setf (extensions:default-directory) #P"設定したいディレクトリ")

カレントディレクトリの設定後に (truename "./") が返す値が変化しているかは処理系により異なる。 また、処理系によっては *default-pathname-defaults* に起動時のディレクトリが入っている。 CLISPだと *default-pathname-defaults* ;=> #P"" のとおり空白だった。

SBCLだと、 *default-pathname-defaults* を書き換えないとload関数で参照する先が変わらない。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment