blog.ojisan.io

scheme の記号調べた

最近、悪い大人に唆されて The Reasoned Schemer をやりはじめたのですが Scheme どころか LISP 系の言語をやったことがないので大混乱していました。と言うわけでググった記号をまとめます。miniKanren の実装で出てきたもののまとめなので、Scheme にある記号以外のものも含んでいます。

記号は https://en.wikipedia.org/wiki/Scheme_(programming_language) でその読み方を知り、それをググって調べました。記号そのままググってもあまりいい結果は表示されなかったためです。説明が不足していたところは適宜仕様書と見比べました。

あと miniKanren 以前に Scheme そのものが初めてなので間違った説明とか指摘いただけると幸いです。

また実行は Racket の上で行っています。MacOS での環境構築がとても楽だったので・・・

.

cons とも呼ばれ、ドット対や Cons セルと呼ばれているものを作ります。 ドット対は car 部と cdr 部で構成される LISP の基本的なデータ構造です。

(cons 1 2)

; or

'(1 . 2)

ドット対 は 2 つのデータしか持てませんが、cdr 部分に別のドット対を入れることで数珠つなぎにでき、リストを作れます。なおリストでは最後の要素となるドット対の cdr 部分は空です。

'(a . (b . (c . ()))

ちなみに最後のドット対を空にしないと、列の最後がドット対になるので忘れない様に注意しましょう。

> (cons 1 (cons 2 (cons 3 (cons 4 '()))))
'(1 2 3 4)

> (cons 1 (cons 2 (cons 3 4)))
'(1 2 3 . 4)

ちなみにどうしてリストの話をするかというと次の記号が vector だからです。

#

# はその直後の文字によっていろんな機能を持ちます。 The Reasoned Schemer では # が 2 通り出てきます。一つは #t#f という真偽値です。これは Scheme に組み込まれているものです。またこれに類似したものとして The Reasoned Schemer では #s#u が登場します。これは 質問の問い合わせ結果を Success, Fail で表現したものです。

もう一つの使われ方はベクトルです。

vector は

A vector is a fixed-length array with constant-time access and update of the vector slots, which are numbered from 0 to one less than the number of slots in the vector.

といったデータ構造です。

#(1 2)
'#(1 2)

FYI: https://docs.racket-lang.org/reference/vectors.html

FYI: https://groups.csail.mit.edu/mac/ftpdir/scheme-7.4/doc-html/scheme_9.html

リストは cdr 部を連結するため iterate していくのに向いていますが、一方でベクトルはランダムなアクセス(=添字でのアクセス)に強いです。ただ、ベクトルは破壊的な操作が可能なのでそこは注意が必要です。

> (let ((i (vector 1 2 3)))
    (vector-set! i 0 10000)
    (display i))

#(10000 2 3)

とはいえベクトルを # で作っておけば "Vectors generated by the default reader (see Reading Strings) are immutable." として作られるので破壊されないです。(これは Racket だけ?)vector 呼び出しで作った場合は mutable です。

FYI: https://docs.racket-lang.org/reference/vectors.html

> (let ((i '#(1 2 3)))
    (vector-set! i 0 10000)
    (display i))

vector-set!: contract violation
  expected: (and/c vector? (not/c immutable?))

積極的に # を使っていきましょう。

'

quote とも呼ばれています。

quote は

scheme では、値が括弧の内側から外側に向けて評価が行われ、 一番外側の括弧が評価した値がその式の値として返ってくるという計算法を取っているため、 トークン(プログラム言語における意味を持つ最小単位、ほとんど単語と同じ意味) は常に評価される危険にさらされます。 そこで、シンボルやリストなど、評価されると自分自身にならないデータそのものをプログラムに与えるときは quote という命令を使います。

とのことです。

FYI: https://www.shido.info/lisp/scheme3.html

(quote ⟨datum⟩) は ⟨datum⟩ に評価されると覚えておきましょう。 任意のリテラルを扱える様になります。

また、数値定数、文字列定数などの定数はそれ自身に評価されるため、' は不要です。

> '3
3

> 3
3

,

unquote とも呼ばれています。先ほどの ' は評価を防ぐものでしたが、反対に , は続く式を評価するものです。

> '(1 (+ 1 2) 4)

'(1 (+ 1 2) 4)

> `(1 (+ 1 2) 4)

'(1 (+ 1 2) 4)

> `(1 ,(+ 1 2) 4)

'(1 3 4)

またこれは次に紹介する quasiquote の中でしか使えないものです。

`

quasiquote や 準クオート とも呼ばれています。

準クォートは

固定部分と変数部分の両方を持つような構造を構成するのに便利

です。

FYI: https://practical-scheme.net/gauche/man/gauche-refj/Zhun-kuoto.html

R7RS の言葉を借りると

「quasiquote」式は全部ではないけれど部分的にあらかじめ判っているようなリストやベクタ構造を構築する場合に便利です。⟨qq template⟩ 内にコンマが無ければ `⟨qq template⟩を評価した結果は ’⟨qq template⟩ を評価した結果と同等です。しかし ⟨qq template⟩ 内にコンマがある場合、コンマに続く式は評価 (「unquote」) され、その結果がそのコンマと式の代わりにその構造内に挿入されます。

> '(1 ,(+ 1 2) 4)

'(1 ,(+ 1 2) 4)

> `(1 (+ 1 2) 4)

'(1 (+ 1 2) 4)

> `(1 ,(+ 1 2) 4)

'(1 3 4)
> (let ((x 0) (y 1))
  `(foo bar ,x ,y))

'(foo bar 0 1)

自分は JavaScript の template literarl のように捉えています。

,@

unquote-splicing とも呼ばれています。 日本語の意味としては「つなぎ合わせる」といったものです。

つまり、unquote-splicing は評価結果をつなぎ合わせてくれます。

> `(1 (+ 2 3) 4)
'(1 (+ 2 3) 4)

> `(1 ,(+ 1 2) 4)
'(1 3 4)

> `(1 ,@(list 1 2) 4)
'(1 1 2 4)

評価して () を剥ぎ取って、元あった場所に入れてくれると言う風に捉えると良いでしょう。R7RS でもそのような説明がされています。また ,@ は後続の式がリストである必要があります。

?

null? や integer? などについてくるものです。 boolean を返す関数についてることが多く、ユーザーが定義する関数にも boolean を返すなら ? を suffix につけます。

*

* 自体は Scheme では let* くらいでしか出てきませんが、The Reasoned Schemer では

(run* q)

という呼び出しが多数(というより必ず?)されます。

miniKanren の実装で言えばこれは

(define-syntax run*
  (syntax-rules ()
    ((run* q g ...) (run #f q g ...))))

というマクロ定義であり、(run* q g ...)(run #f q g ...) に置き換えます。

The Reasoned Schemer はボトムアップな解説であり、Macro の説明なしに run* を使うので、最初は scheme にそんな構文があるのかと疑問に思っていましたが、10 章まで読み進めると run* の実装が登場します。

!

mutable な操作など危険な操作を行う手続きに付くものです。 miniKanren には登場しませんが調べてる時に目に入ったので、メモ。

...

マクロ定義でたびたび登場する記号です。 任意個数の式を意味します。

(define-syntax when
  (syntax-rules ()
    ((_ pred b1 ...)
     (if pred (begin b1 ...)))))

(let ((i 0))
  (when (= i 0)
    (display "i == 0")
    (newline)))

FYI: https://www.shido.info/lisp/scheme_syntax.html

あとがき

冒頭に LISP は初めてと書いたがあれは嘘です。Land of LISP の絵だけは読んだことがあります。グラセフのパロディがお気に入りです。

https://nostarch.com/download/Lisp08.pdf

※ chapter8 は出版社が公開しているので、多分読んでも大丈夫なやつです。(https://nostarch.com/lisp.htm)