Scheme

ガイド

インストール・エディタの設定

このページを参考にしたら Emacs での gauche の REPL が苦もなく起動した.

(modify-coding-system-alist 'process' "gosh" '(utf-8 . utf-8))
(setq scheme-program-name "gosh -i")
(autoload 'scheme-mode "cmuscheme" "Major mode for Scheme." t)
(autoload 'run-scheme "cmuscheme" "Run an inferior Scheme process." t)

(defun scheme-other-window ()
  "Run scheme on other window"
  (interactive)
  (switch-to-buffer-other-window
   (get-buffer-create "*scheme*"))
  (run-scheme scheme-program-name))

(define-key global-map
  "\C-cs" 'scheme-other-window)

オンライン学習

キーバインド

C-c C-l     Scheme file のロード
C-c C-r     リージョンの実行
C-x C-e     直前の S 式を評価
M-C Space     カーソルの次の S 式をマーク
M-C-a     カーソルを含むトップレベルの S 式の先頭へ移動
M-C-e     カーソルを含むトップレベルの S 式の末尾へ移動
M-C-f     次の S 式へ移動
M-C-b     前の S 式へ移動
M-C-t     カーソルの前後の S 式を交換

SICP

素数夜曲

素数夜曲とプログラミング B-B.1.2 Scheme, Python, Haskell, JavaScript 第1回

投稿用メモ

はじめに: なぜ素数夜曲か

数学・物理学習とプログラミングと絡められないかと試行錯誤している.

数学からの取っつきやすさ, 物理からの取っつきやすさ, プログラミングからの取っつきやすさをそれぞれ考えないといけない. ある程度の体系性もほしい.

数学や物理としても面白い内容にしたいし, プログラミングから見ても意味がある内容にしたい.

どうしたものかとずっと思っていたのだが, 素数という数学のキラーコンテンツとプログラミングを絡めた素数夜曲があった.

微妙なところも多いとは思いつつ, メインエディタとして Emacs を使っているし, LISP 系の言語はきちんとやりたいとも思っている.

そんなところでいい感じの落とし所という気がしたので, 少しずつ読み進めつつコードの記録をしていこうと決めた.

数学・物理の数値計算との相性の良さ, 私の勉強してみたさとも合わせて Python や Haskell のコードも書いていけたらいいな, と思っている. あと何となく JavaScript もやってみよう. JavaScript は動きが速すぎて何かやってもすぐに動かなくなりそうで嫌なのだが, ここでやるくらいのことならそう簡単に陳腐化しないだろうと勝手に信じてやっていこう.

基本的には付録のプログラミングパート, 特に付録 B から記録をつけていく予定だ.

Emacs の org-babel で書いているので, 素数夜曲本編とは見た目ちょっと違うコードを書いていくかもしれない. Scheme, org-babel ともにあまりよくわかっていないがとりあえず進める.

org-babel の Scheme が標準で Guile を使っていて, その変更の仕方がわからなかったのでとりあえず Guile を使う. これまで入れていた Gauche を使いたかったが org-babel が対応していないようだ. そうでないなら MIT/GNU Scheme が良かった気もするがこれも設定がわからない.

でははじめよう.

出力用関数

+BEGIN_SRC scheme :session :results output

(write "write test") (newline) (display "display test")

+END_SRC

+RESULTS:

"write test"
display test

B.1 評価値を得る

とりあえず Hello, World! で.

+BEGIN_SRC scheme :session :results output

(write "Hello, Scheme!")

+END_SRC

+RESULTS:

"Hello, Scheme!"

B.1.2 四則計算

基本的な書き方

加減乗除は次の通り. 前置記法なのがポイント. 割り算で分数にしてくれるのは割とポイント高い気がする.

+BEGIN_SRC scheme :session :results output

(write (+ 5 3)) (newline) (write (- 5 3)) (newline) (write (* 5 3)) (newline) (write (/ 5 3))

+END_SRC

+RESULTS:

8
2
15
5/3
Python

+BEGIN_SRC ipython :session :results output

print(5 + 3) print(-5 + 3) print(5 * 3) print(5 / 3)

+END_SRC

+RESULTS:

8
-2
15
1.6666666666666667
Haskell

まだ (?) org-babel との相性がよくないっぽい. ここ を参考にちょっと改造.

Haskell でのあまりの計算には mod を使う.

+BEGIN_SRC haskell :results output

main = do print $ 5 + 3 print $ -5 + 3 print $ 5 + 3 print $ 5 / 3 print $ mod 5 3 print $ 5 mod 3

+END_SRC

+RESULTS:

:
8
-2
8
1.6666666666666667
2
2
JavaScript

+BEGIN_SRC js :cmd node :results output

console.log(5 + 3); console.log((-5 + 3)); console.log(5 * 3); console.log(5 / 3);

+END_SRC

+RESULTS:

8
-2
15
1.6666666666666667
長めの計算をどう書くか

例えば次の式.

\begin{align} 2 \div 3 + 5 \times 7 - 11 \end{align}

Scheme (LISP 系言語) だと括弧で細かく区切っていくから, 演算の優先度みたいな面倒なことをあまり考えなくても済む.

+BEGIN_SRC scheme :session :results output

(define a (- (+ (/ 2 3) (* 5 7)) 11)) (write a)

+END_SRC

+RESULTS:

74/3
Python

Python だと普通に書く.

+BEGIN_SRC ipython :session :results output

a = 2 / 3 + 5 * 7 - 11 print(a)

+END_SRC

+RESULTS:

24.666666666666664
Haskell

Python っぽくも Scheme っぽくも書ける. 演算子を括弧でくくると Scheme のように演算子を前に置けるから.

+BEGIN_SRC haskell :results output

main = do print $ 2 / 3 + 5 * 7 - 11 print $ (-) ((+) ((/) 2 3) ((*) 5 7)) 11

+END_SRC

+RESULTS:

:
24.666666666666664
24.666666666666664
JavaScript

+BEGIN_SRC js :cmd node :results output

console.log(2 / 3 + 5 * 7 - 11);

+END_SRC

+RESULTS:

24.666666666666664
平方根を取る関数 (演算子?)

素数夜曲 P.389 によると Scheme で sqrt は単項演算子らしい.

+BEGIN_SRC scheme :session :results output

(write (sqrt 2))

+END_SRC

+RESULTS:

1.4142135623730951

入れ子にすれば複数回適用できる. Scheme は関数合成あるのだろうか. ここを見ると Gauche ならあるようだが.

+BEGIN_SRC scheme :session :results output

(write (sqrt (sqrt 4)))

+END_SRC

+RESULTS:

1.4142135623730951
Python

Python 版は次の通り. Python は math をインポートしないといけないのがめんどい. Python 3.5.1 を使っているが関数合成はないのだろうか. ちょっと調べたところでは標準ではなさそうだった.

+BEGIN_SRC ipython :session :results output

import math print(math.sqrt(2)) print(math.sqrt(math.sqrt(4)))

+END_SRC

+RESULTS:

1.4142135623730951
1.4142135623730951
Haskell

Haskell は関数合成 (.) がある.

+BEGIN_SRC haskell :results output

main = do print $ sqrt 2 print $ sqrt $ sqrt 4 print $ (sqrt . sqrt) 4

+END_SRC

+RESULTS:

:
1.4142135623730951
1.4142135623730951
1.4142135623730951
JavaScript

+BEGIN_SRC js :cmd node :results output

console.log(Math.sqrt(2)); console.log(Math.sqrt(Math.sqrt(4)));

+END_SRC

+RESULTS:

1.4142135623730951
1.4142135623730951
自然対数の底, ネイピア数

Scheme では exp を使えば出せる. $e$ 自体は定義されていない?

+BEGIN_SRC scheme :session :results output

(write (exp 1))

+END_SRC

+RESULTS:

2.718281828459045
Python

Python の場合はやはり math モジュールにある.

+BEGIN_SRC ipython :session :results output

import math print(math.e)

+END_SRC

+RESULTS:

2.718281828459045

NumPy にもある.

+BEGIN_SRC ipython :session :results output

import numpy as np print(np.e)

+END_SRC

+RESULTS:

2.718281828459045
Haskell 版

$e$ 自体は定義されていないので Scheme と同じく指数関数から近似値を出す.

+BEGIN_SRC haskell :results output

main = do print $ exp 1

+END_SRC

+RESULTS:

:
2.718281828459045
JavaScript 版

Math.exp() が自然対数の底による指数関数で, Math.pow() は一般の底と指数の関数.

+BEGIN_SRC js :cmd node :results output

console.log(Math.exp(1)); console.log(Math.pow(2, 3));

+END_SRC

+RESULTS:

2.718281828459045
8
2 項演算子をいくつか

まずは等号 = を.

+BEGIN_SRC scheme :session :results output

(write (= 1 1)) (newline) (write (= 1 2))

+END_SRC

+RESULTS:

t

f

ここで #t は true, #f は false を意味している. == ではないのかとちょっと驚いた.

ちなみに引数がたくさんある場合にも適用できる.

+BEGIN_SRC scheme :session :results output

(write (= 1 2 3 4 5 6)) (newline) (write (= 1 1 1)) (newline) (write (= 1 1 1 2))

+END_SRC

+RESULTS:

f

t

f

不等号 > >= などもある. 不等号はそれぞれ隣の項に対して適用させていった結果を出力するようだ.

+BEGIN_SRC scheme :session :results output

(write (< 1 2 2 3)) (newline) (write (< 1 2 3 4))

+END_SRC

+RESULTS:

f

t

Python

等しくないことの判定は != だ.

+BEGIN_SRC ipython :session :results output

print(1 == 2) print(1 == 1) print(1 != 1) print(1 < 2)

+END_SRC

+RESULTS:

False
True
False
True

こちらは = だと代入になってしまうので == と重ねる.

Haskell

Scheme の (= 1 2 3 4) みたいなのはどう書けばいいのだろう. foldr で書けると思ったので foldr (==) 1 [1, 2, 3, 4] と書いてみたら怒られた. 相談したら「それ型エラー起こしてるから. ちゃんとして」と言われた. Haskell, ちょっとしたところですぐこけるのでとてもつらい.

結論からいうと適当に 2 つリストを作り, その要素を比較していく形で対処するのが普通らしい. 他のところでもそういう処理をするのがいいというコメントを頂いたことがあるので, 道具箱におさめておこう.

+BEGIN_SRC haskell :results output

main = do print $ 1 == 1 print $ 1 /= 1 print $ 1 < 2 let xs = [1, 2, 3, 4] print $ and $ zipWith (==) xs (tail xs) let ys = [1, 2, 3, 4] print $ and $ zipWith (<) ys (tail ys) -- print $ foldr (==) 1 [1, 2, 3, 4] -- エラーになった

+END_SRC

+RESULTS:

:
True
False
True
False
True

let を使うのが癪なので次回やる予定の lambda を使って書いてみる.

+BEGIN_SRC haskell :results output

main = do print $ (\xs -> and $ zipWith (==) xs (tail xs)) [1, 2, 3, 4] print $ (\xs -> and $ zipWith (<) xs (tail xs)) [1, 2, 3, 4]

+END_SRC

+RESULTS:

:
False
True

見づらい気もするので無理せず関数定義した方がいい気もする.

+BEGIN_SRC haskell :results output

mycompare :: [Int] -> Bool mycompare xs = and $ zipWith (<) xs (tail xs)

main = do print $ mycompare [1, 2, 3, 4]

+END_SRC

+RESULTS:

:
True
Haskell 追記

またコメントを頂いてしまった. これ.

+BEGIN_SRC haskell :results silent

and $ ap (zipWith (<)) tail [1..4] True

and $ ap (zipWith (<)) tail [1,2,2,3] False

+END_SRC

実行してみよう. しばらくはまったが何となく ap を使うためにインポートが必要っぽい.

+BEGIN_SRC haskell :results output

import Control.Monad

main = do print $ and $ ap (zipWith (<)) tail [1..4] print $ and $ ap (zipWith (<)) tail [1,2,2,3]

+END_SRC

+RESULTS:

:
True
False

最高か.

そして「素人はこんなこともわからない」というのを思い出せさてくれるし, とてもいい体験だった.

ちょっと調べはしたが ap が何なのかあまりよくわかっていない. とりあえずこれ.

+BEGIN_SRC haskell :results output

myap f g = \x -> f x (g x) main = do print $ and $ myap (zipWith (<)) tail [1..4] print $ and $ myap (zipWith (<)) tail [1,2,2,3]

+END_SRC

+RESULTS:

:
True
False

stackoverflow もきちんと全部読んだわけではないし, この雑な理解でいいとも思わないがとりあえずこれで掴まえられるところもある. 「適当にしか理解していない (そして半端な理解だと割とどうでもいいところで無駄にはまる)」ことさえ理解しておけば, あとでいくらでも調節は効くと数学でいつも経験することをいつも通り信じて追記を終える.

JavaScript

比較には ===== がある. 基本的に === でやるのが安全.

+BEGIN_SRC js :cmd node :results output

console.log(1 === 2); console.log(1 === 1); console.log(1 !== 1); console.log(1 < 2);

+END_SRC

+RESULTS:

false
true
false
true

JavaScript で複数の比較はどう書くといいだろうか. reduce を使おうと思ったが, それは Haskell の最初の foldr でエラーになるのと同じ理由で駄目だ. ここを見ると zip がないようだ. ふつうにループを回すのでは面白くない.

大文字小文字の区別

~~素数夜曲 P.390 の記述によると,~~ ~~Scheme は原則として大文字小文字を区別しないらしいが実装により異なるとのこと.~~ いわゆる処理系で変わるとかいうやつか?

安全性を考えるなら小文字に統一するのがいいらしい. 区別しないことを前提にキャメルケースを使うのも一手とか何とか.

今回はこのくらいにしよう.

Scheme 追記

下にあるようにコメントを頂いた.

Scheme の仕様は改定を重ねられているので、どの版であるかによって挙動が異なる部分があります。 Scheme の仕様はその正式名称を略して RnRS と呼ばれていて、 n の部分に何回目の改定であるかの番号が入ります。 最新は R7RS です。 「大文字小文字を区別しない」というのは R5RS までの仕様です。 (ただ、 R5RS 処理系を名乗る処理系でも区別する処理系や、切り替えられる処理系はあります。) R6RS 以降では区別するのがデフォルトです。 有理数や無限長整数などは R6RS 以外ではオプショナルな仕様です。 (R6RS で必須になった後、 R7RS では再びオプショナルになりました。) 処理系によっては桁あふれに配慮が必要な場合もあります。 有理数や無限長整数は Guile や Gauche では問題なく使えますので気にする必要はありませんが、その他に色々なところで仕様では未規定の部分があるので処理系を乗り換える機会にはつまらないことで躓くかもしれません。

思っていたよりも面倒そうな話だった. ただ「ハマる可能性があるかもしれない」 「ハマるところはこの辺」というのが頭の片隅にあるかないかだけでも 全く違うことはこれまでの経験でよくわかっている. こんなありがたいことはない. こういうコメントを頂けるのが記事にしておく醍醐味だ.

地道に続けよう.

素数夜曲とプログラミング B.2-B.4 Scheme, Python, Haskell, JavaScript 第2回

投稿用メモ

素数夜曲とこれまでの内容

素数夜曲
これまでの内容

B.2 名前と手続き

リストの一番左は特別: そこには手続きを置く. 命名と抽象化について割といいことが書いてある気がする.

太字で強調してあるところがあって, 何となく大事そうだからこちらにも書かせてもらおう.

プログラミングは命令的知識を扱い, 数学は宣言的知識を扱う.

命令的というのは「如何にして為すか」という意味で, 宣言的というのは「何であるか」という意味.

B.2.1 関数の定義

素数夜曲では「函数」と書いているがめんどいので関数と書くことにする. いわゆるラムダの話から入る. 次の関数は $f(x) = x^2$ だ. ラムダで書くと $\lambda x.x^2$.

ラムダ以外の書き方もあるがとりあえず本の順に沿って書いていこう.

+BEGIN_SRC scheme :session :results output

(lambda (x) ( x x)) (write ((lambda (x) ( x x)) 5))

+END_SRC

+RESULTS:

25

一次関数 $x \mapsto ax + b$ は Scheme だと次のように書く.

+BEGIN_SRC scheme :session :results output

(lambda (a) (lambda (b) (lambda (x) (+ ( a x) b)))) (write ((((lambda (a) (lambda (b) (lambda (x) (+ ( a x) b)))) 2) 3) 5))

+END_SRC

+RESULTS:

13

こんな鬱陶しいのは嫌だ. LISP が嫌いになる理由として括弧が挙げられる理由を強く感じる. 慣れるとむしろ便利なくらいと聞くが道は遠い.

何はともあれもう少し簡単な書き方がラムダレベルでもあるし Scheme にもある. $\lambda abx.ax+b$ で, Scheme は次の通り.

+BEGIN_SRC scheme :session :results output

((lambda (a b x) (+ (* a x) b)) 2 3 5)

(write ((lambda (a b x) (+ (* a x) b)) 2 3 5))

+END_SRC

+RESULTS:

13

素数夜曲でいつ出てくるのかわからないが, 大分長いこと lambda をひっぱるようだ. 鬱陶しいので適当に検索してきて lambda 抜きの関数定義を書いておく.

+BEGIN_SRC scheme :session :results output

(define (hello name) (string-append "Hello " name "!")) (write (hello "World"))

+END_SRC

+RESULTS:

"Hello World!"
Python の lambda

$f(x) = x^2$ を書いてみよう.

+BEGIN_SRC ipython :session :results output

myfunc = lambda x: x ** 2 print(myfunc(2))

+END_SRC

+RESULTS:

4

普通の関数で書いてみよう.

+BEGIN_SRC ipython :session :results output

def f(x): return x**2 print(f(2))

+END_SRC

+RESULTS:

4
Haskell の lambda

Haskell での lambda は次の通り.

+BEGIN_SRC haskell :results output

mysquareInt = \x -> x ^ 2 mysquareDouble = \x -> x ** 2

main = do print $ mysquareInt 2 print $ mysquareDouble 2.0

+END_SRC

+RESULTS:

:
4
4.0
JavaScript の lambda

一般的な lambda と無名関数の違いがよくわかっていない.

+BEGIN_SRC js :cmd node :results output

var f = function(a) { return Math.pow(a, 2) }; console.log(f(3)); var a = (function(a){ return Math.pow(a, 2) })(3); console.log(a); console.log((function(a){ return Math.pow(a, 2) })(3));

+END_SRC

+RESULTS:

9
9
9

B.2.2 アルファ変換

いわゆる束縛変数の文字を変えるというやつ.

B.3 ラムダ算法

Lambda calculus だし, たぶんいわゆるラムダ計算なのだろう. プログラムじたいはないのでばっさり省略.

B.3.3 イータ変換

省略.

B.3.4 ラムダ項の定義

略.

B.3.5 コンビネータ

略.

B.3.6 簡約の戦略

略.

B.4 特殊形式 (special form)

対象に名前をつけてプログラム上のどこからでも使えるようにすることを, トップレベル定義 (top-level definition) という.

1 次関数を定義する.

+BEGIN_SRC scheme :session :results output

(define linear (lambda (a b x) (+ (* a x) b))) (write linear) (newline) (write (linear 2 3 5))

+END_SRC

+RESULTS:

13

最後の (linear 2 3 5) は $2 \times 5 + 3$ を計算している.

Lambda を使わない省略記法もあって MIT 記法 という.

+BEGIN_SRC scheme :session :results output

(define (linear a b x) (+ (* a x) b)) (write linear) (newline) (write (linear 2 3 5))

+END_SRC

+RESULTS:

13

この手の省略記法は糖衣構文 (syntax sugar) と呼ばれる.

トップレベルは対話的入力時のプロンプトに象徴される領域で, 大域環境 (global environment) と呼び, この環境下で定義された変数を大域変数 (global variable) と呼ぶ. これと対になるのが局所環境 (local environment), 局所変数 (local variable) だ.

アルファ変換した linear# も書いておこう.

+BEGIN_SRC scheme :session :results output

(define (linear# u v w) (+ (* u w) v)) (write linear#) (newline) (write (linear# 2 3 5))

+END_SRC

+RESULTS:

13

アルファ変換は局所変数を別の文字 (列) に置き換えてもいいとか, とりあえずそういう雑な理解をしている. 計算機科学的にもっと面倒なところまでカバーしている可能性があり, そこまで調べていないからいまの理解の雑さは割と真剣に警戒している.

B.4.1 define

用語の定義があるから適当に書いておこう. 対象を挿入する場所 (スロット) は本文と同じくを角括弧 (<>) で書きたいが, いろいろな事情から隅括弧 ([]) で書くことにする^prime_nocturne_scheme_python_haskell_0001.

今の私の腕ではスロットだけを自動で変換するスクリプトを作れないので諦めた. Markdown の引用の「>」や TeX 中の不等式を無視して, スロットだけを狙い打って実体参照に書き換えるコードを書く腕がない.

Lambda 抜きの糖衣構文を使った define の一般形は次の通り.

+BEGIN_SRC scheme :session :results silent

定義方法: (define ([name] [fps]) [body]) 利用方法: ([name] [aps])

+END_SRC

本の記述が死ぬほどわかりづらいが, とりあえず引用しよう.

手続の作用の対象となるのが引数 (parameter, argument) だ. [fps] は関数内部で使われる仮引数 (formal parameters) の略記で, [aps] は実際の処理対象である実引数 (actual parameters) の略記. [body] は任意個数の (expression) で構成される手続の本体だ. Scheme での式はアトムやリテラルを含めて評価値が戻ってくるもの全てを指す.

とりあえずここまでくり返し書いてきた関数定義を.

+BEGIN_SRC scheme :session :results output

(define (linear a b x) (+ (* a x) b))

+END_SRC

linear が [name], [fps] は a, b, x で, [body] が (+ (* a x) b) だ. [name] は要は関数の名前, [fps] は定義した関数の引数, [body] は関数で実際にやる処理のこと.

よくプログラミングのマニュアルでは上のタイプのよくわからない説明書きがある. こういうやつ.

+BEGIN_SRC php :results output

array explode ( string $delimiter , string $string [, int $limit = PHP_INT_MAX ] )

+END_SRC

こういうのを見たらとりあえず実際にコード片を書いて実行して確認してみた方がいい. めちゃくちゃ引数がたくさんあってそんなことやっていられないこともよくあるけれども.

Scheme の一般評価規則にしたがわない対象, つまり特定の対象の評価値を求めないものを特殊形式 (special form), あるいは構文 (syntax) と呼ぶ.

スペシャルフォームと片仮名書きされているのをよく見かける. ここからは代表的な特殊形式を紹介していくようだ.

一応改めて Python や Haskell での関数定義を書いておこう.

Python での変数・関数定義

まずは変数定義.

+BEGIN_SRC ipython :session :results output

a = 1 print(a)

+END_SRC

+RESULTS:

1

数を 2 乗する関数を定義する.

+BEGIN_SRC ipython :session :results output

def f(x): return x ** 2

print(f(2)) print(f(3))

+END_SRC

+RESULTS:

4
9
Haskell での変数・関数定義

私が理解している限り, いわゆる変数はない. 定数関数があってそれが定数の役割をしてくれるという理解.

+BEGIN_SRC haskell :results output

two :: Int two = 2

main = do print two

+END_SRC

+RESULTS:

:
2

こちらは整数を 2 乗する関数を定義する.

+BEGIN_SRC haskell :results output

mySquare :: Int -> Int mySquare x = x ^ 2

main = do print $ mySquare 2 print $ mySquare 3

+END_SRC

+RESULTS:

:
4
9
JavaScript の変数・関数定義

use strict がないバージョン. グローバル変数になるのでよくないと言われるやつ.

+BEGIN_SRC js :cmd node :results output

a = 2; console.log(a);

+END_SRC

+RESULTS:

2

use strict はどこまで使えるようになっているのだろうか. use strict をつけると上のキーワードなしの宣言でエラーになる. 次のキーワードはスコープとかの問題で詳しくはこことかで適当に調べる.

+BEGIN_SRC js :cmd node :results output

"use strict"; var a0 = 1; console.log(a0); var a = 2; console.log(a); let b = 3; console.log(b); const c = 4; console.log(c);

+END_SRC

+RESULTS:

1
2
3
4
略号まとめ
引数の有効範囲

変数定義的な意味での define の使い方. 正確には次のように言うべきのようだ.

記憶領域のある場所に a という名前を与え, その場所と値 3.14 を結びつける.

値を参照するラベルが a とかいうやつだろう.

+BEGIN_SRC scheme :session :results output

(define a 3.14) (write a)

+END_SRC

+RESULTS:

3.14

define の次の用法の例でもある模様.

+BEGIN_SRC scheme :session :results silent

定義方法: (define [name] [exp])

+END_SRC

次のように読めばいいようだ.

[name] とは [exp] の名前である.

束縛と環境

a を定義したあとなら a を入力してもエラーが出ない. REPL 的なアレで挙動確認しているわけではないので, 次のコード片では write を使っている.

+BEGIN_SRC scheme :session :results output

(define a 3.14) (write a)

+END_SRC

+RESULTS:

3.14

このように変数に束縛された値を確認する方法を変数参照 (variable reference) という. 次の実行結果を見ると a と定数 3.14 が関連づけられていることがわかる.

+BEGIN_SRC scheme :session :results output

(define a 3.14) (write a) (newline) (write (* 2 a))

+END_SRC

+RESULTS:

3.14
6.28

この a に値を格納するのを束縛 (bind) と呼ぶ. この束縛情報の全体を環境 (environment) と呼ぶ.

B.4.2 lambda

+BEGIN_SRC scheme :session :results output

定義方法: (lambda ([fps]) [body]) 利用方法: ((lambda ([fps]) [body]) [aps])

+END_SRC

改めてコード例を書いておこう. 数を 2 乗する関数を定義する.

+BEGIN_SRC scheme :session :results output

(lambda (x) ( x x)) (write ((lambda (x) ( x x)) 2))

+END_SRC

+RESULTS:

4

関数定義の実験として前置記法 (prefix notation) を中置記法後置記法で書いている. 前置記法について prefix を作っている.

+BEGIN_SRC scheme :session :results output

(define prefix (lambda (proc a b) (proc a b))) (write (prefix + 2 3)) (newline) (write (prefix * 2 3))

+END_SRC

+RESULTS:

5
6

これを中置記法 (infix notation), 後置記法 (postfix notation) で書く.

+BEGIN_SRC scheme :session :results output

(define infix (lambda (a proc b) (proc a b))) (write (infix 2 + 3)) (newline)

+END_SRC

+RESULTS:

5

+BEGIN_SRC scheme :session :results output

(define postfix (lambda (a b proc) (proc a b))) (write (postfix 2 3 +))

+END_SRC

+RESULTS:

5

Python, Haskell, JavaScript のコード例はいらないだろう.

ブロック構造

手続の評価値は内部の最後の式で決まる.

+BEGIN_SRC scheme :session :results output

(define (linear x) (define a 2) (define b 3) (+ (* a x) b)) (write (linear 5)) (newline)

+END_SRC

+RESULTS:

13

次の式では最後の (* a b) の結果が返る.

+BEGIN_SRC scheme :session :results output

(define (linear1 x) (define a 2) (define b 3) (+ ( a x) b) ( a b)) (write (linear1 3))

+END_SRC

+RESULTS:

6

関数内部で定義した変数は外で参照できない. a は関数の中で定義されているだけでトップレベルで定義されていないから, 関数の外で呼ぼうとするとエラーになる.

次のコードを org-babel で実行するとエラーになる. よくわからないが guile -s で実行した結果を貼っておこう.

+BEGIN_SRC scheme :session :results output

(define a 3.14) (write a) (newline)

(define (linear2 x) (define a 2) (define b 3) (+ ( a x) b) ( a b)) (write (linear2 3)) (newline)

(write a)

+END_SRC

+RESULTS:

An error occurred.
guile -s での実行結果

3.14

6

3.14

# その他の言語

同じようなスコープの問題がある. JavaScript だとグローバル変数に関する有名な問題がある. 昔は必ず var を使えというのがあった. 最近は var よりも use strictlet, const を使うのがいいのだろうか. 識者のご意見求む.

begin

lambda の持つ列挙機能を含む特殊形式が begin. 式を対象にする限りは同じらしい. begin だとトップレベル定義になる.

+BEGIN_SRC scheme :session :results output

(begin (define a 2) (define b 3)) (write a) (newline) (write b)

+END_SRC

上のコード, org-babel で実行できない. guile -s での実行結果を書いておこう.

guile -s での実行結果

2

3

# その他の言語

Python, Haskell, JavaScript で似たようなのがあるだろうか?

今回はこの辺で終わろう.

素数夜曲とプログラミング B.4.3-B.4.7 Scheme, Python, Haskell, JavaScript 第3回

投稿用メモ

素数夜曲とこれまでの内容

素数夜曲
これまでの内容

緩募 Emacs で Scheme やるときに lispxmp 的なことをやりたい

いまさらだがいちいち write をかませるのがあまりに不細工だし, 何より本当にめんどくさい. lispxmp 的なことがやりたい. どなたか情報をご存知だったら教えてほしい.

B.4.3 Let

let は次の lambda の糖衣構文として定義されている.

+BEGIN_SRC scheme :session :results silent

((lambda ([var_1] [var_2] ... [var_n]) [body]) [exp_1] [exp_2] ... [exp_n])

+END_SRC

let は次の通り.

+BEGIN_SRC scheme :session :results silent

(let (([var_1] [exp_1]) ([var_2] [exp_2]) ... ([var_n] [exp_n])) [body])

+END_SRC

let には let*letrec, 名前つき let という 3 種類の派生がある.

$a=2$, $b=3$ に固定した一次関数を lambdalet で書く. Lambda 記法で $x$ に関する関数抽象を括り出す[^prime_nocturne_scheme_python_haskell_0002].

[^prime_nocturne_scheme_python_haskell_0002]: 関数抽象, 読み飛ばしたからか何のことかわからない. 索引もちょろっと見たらウルトラ使いづらい.

+BEGIN_SRC scheme :session :results output

((lambda (x) ((lambda (a b) (+ (* a x) b)) 2 3)) 5)

(write ((lambda (x) ((lambda (a b) (+ (* a x) b)) 2 3)) 5))

+END_SRC

+RESULTS:

13

$x$ に対する値 5 を取り除いて改めて関数 f_2x+3 を定義する.

+BEGIN_SRC scheme :session :results output

(define f_2x+3 (lambda (x) ((lambda (a b) (+ (* a x) b)) 2 3))) (write (f_2x+3 5))

+END_SRC

+RESULTS:

13

let を使った方がみやすい.

+BEGIN_SRC scheme :session :results output

(define f_2x+3 (lambda (x) (let ((a 2) (b 3)) (+ (* a x) b)))) (write (f_2x+3 5))

+END_SRC

+RESULTS:

13
Haskell 版: let と where?

Python や JavaScript だとあまり言うことない気がする. JavaScript なら use strict;let はあってもここの文脈での Scheme の let とは違うという理解.

Haskell だと多分また違う話として letwhere がある. まだあまりピンと来ていないがスコープとかその辺に違いがある.

どういう場合にどちらがいいのかは, Haskell をもっとたくさん書くとわかるようになるのだろう. where の方が一般的に使われるとか何とか聞いてはいる. とりあえず書いておく.

+BEGIN_SRC haskell :results output

f0 :: Int -> Int f0 x = let a = 2 b = 3 in a * x + b

main = do print $ f0 5

+END_SRC

+RESULTS:

:
13

+BEGIN_SRC haskell :results output

f1 :: Int -> Int f1 x = a * x + b where a = 2 b = 3

main = do print $ f1 5

+END_SRC

+RESULTS:

:
13
クロージャ

関数の本体の評価値を調べる.

+BEGIN_SRC scheme :session :results output

(write (lambda (x) ((lambda (a b) (+ (* a x) b)) 2 3)))

+END_SRC

+RESULTS:

:87:41 (x)>

参照される変数の定義, 定数の値を含んだ環境と関数抽象の組クロージャあるいは関数閉包と呼ぶ. $a=2$, $b=3$ はクロージャの中でだけ有効で, 外部に影響を与えないし影響も受けない. 外部とのやりとりは変数 $x$ が受け持つ. スキームはレキシカルスコープを採用している.

クロージャ, いまだに何なのかよくわかっていない. 何というか半端に具体的にプログラミング言語と絡めて説明されるからよくわからないのではないか, という感じもする. ラムダ計算から見た数学ベースの定義はどうなっているのだろう. いろいろなプログラミング言語ごとの事情よりもそちらを見た方がわかるのではないか感.

Python 版

ここを見ると次のようにある.

  • 関数内のローカル変数以外の名前解決が、
  • 呼び出し時のスコープではなく、
  • 宣言時のスコープによって行われるもの。
  • またそのような機能を持った関数のこと。

例が面白かった.

+BEGIN_SRC ipython :session :results output

x = 1 def get_x(): return x

class MyClass(object): x = 10 print(get_x()) # 10 ではなく 1 が返される

+END_SRC

+RESULTS:

1

次のコードには驚いた. circle_area()gen_circle_area_func() の外から呼べている. 他の言語でこんなことできる?

+BEGIN_SRC ipython :session :results output

def gen_circle_area_func(pi): """円の面積を求める関数を返す""" def circle_area(radius): # ここの pi は circle_area_func に渡された pi が使われる return pi * radius ** 2

return circle_area

circle_area1 = gen_circle_area_func(3.14) print(circle_area1(1)) # => 3.14 print(circle_area1(2)) # => 12.56

circle_area2 = gen_circle_area_func(3.141592) print(circle_area2(1)) # => 3.141592 print(circle_area2(2)) # => 12.566370614

+END_SRC

+RESULTS:

3.14
12.56
3.141592
12.566368
Haskell 版

クロージャとは、その関数が定義された環境(変数及びスコープ)への参照を持った関数のことです。 クロージャは英語で「閉鎖」という意味です。 (実際には、クロージャの概念自体が言語によって定義が異なります。)

Haskellにおけるクロージャ Haskellは純粋関数型言語であるため、JavaScriptなどのクロージャと違い、変数(再束縛)がありません。 そのため、関数から無名関数を返す際に無名関数の外側の値を束縛することがHaskellにおけるクロージャといえます。 さらに広義にとらえれば、関数内で一時的な関数を生成してそれを戻り値とするような「関数を返す関数」をクロージャといえます。 (実際には「無名関数の部分適用」と類似した機能といえます。)

上記ページに出てくるコードの挙動はわかるが, どこをもってどうクロージャと呼んでいるのだろうか.

+BEGIN_SRC haskell :results output

add :: Integer -> Integer -> Integer add x y = x + y

main = print(add 5 2)

+END_SRC

上のコードのクロージャ化 2 を引いておこう.

+BEGIN_SRC haskell :results output

add :: Integer -> Integer -> Integer add x y = f x y where f a b = a + b

main = print $ add 5 2

+END_SRC

+RESULTS:

:
7
JavaScript 版

JavaScriptでは関数はすべてクロージャです。 クロージャの簡単な定義として 「自分を囲むスコープにある変数を参照できる関数」 が挙げられます。

これはまあわかる, というコード例.

+BEGIN_SRC js :cmd node :results output

function func() { var value = 1; console.log(value); } func(); // 1 // console.log(value); // undefined, エラーで怒られるのでコメントアウト

+END_SRC

+RESULTS:

1

クロージャの例. これもわかる.

+BEGIN_SRC js :cmd node :results output

function func() { var value = 1;

function innerFunc() { console.log(value); } innerFunc(); } func(); // 1

+END_SRC

+RESULTS:

1
追記: クロージャとスコープ

またコメントを頂いた.

Python での circle_area にずいぶん驚いているようですが、それが「クロージャが環境を保存する」「クージャ (関数) が第一級 (値としてやりとりできる存在) である」ということです。 この記事で取り上げられている他の言語でも出来ますよ。

スコープを全然理解していないことが明らかになっていてとてもつらい. 何はともあれコードまで教えて頂いたのでこちらにも転記しておこう. 実にありがたい.

Scheme に関してはこんなコメントも.

;; 余談ですが、 Scheme ではローカル環境での define (internal definition ;; と呼ばれています) は letrec 又は letrec で定義したのと同じです。 ;; r5rs までは letrec、 R6RS 以降は letrec での定義と等しいと規定されています。

# Scheme 版

+BEGIN_SRC scheme :session :results output

(define (gen_circle_area_func pi) (define (circle_area radius) (* pi radius radius)) circle_area)

(let ((circle_area1 (gen_circle_area_func 3.14))) (display (circle_area1 1)) (newline) (display (circle_area1 2)) (newline))

+END_SRC

+RESULTS:

3.14
12.56
# Haskell 版

+BEGIN_SRC haskell :results output

gen_circle_area_func pi = let circle_area radius = pi * radius * radius in circle_area

main = do print $ circle_area1 1 print $ circle_area1 2 where circle_area1 = gen_circle_area_func 3.14

+END_SRC

+RESULTS:

:
3.14
12.56
# JavaScript 版

+BEGIN_SRC js :cmd node :results output

function gen_circle_area_func(pi) { function circle_area(radius) { return pi * radius * radius; } return circle_area; }

var circle_area1 = gen_circle_area_func(3.14); console.log(circle_area1(1)); console.log(circle_area1(2));

+END_SRC

+RESULTS:

3.14
12.56

B.4.4 引数の書法

lambda の引数部はリストになっている.

3 次元のユークリッド空間の原点からの距離 (の 2 乗) を計算する関数は次のように定義できる.

+BEGIN_SRC scheme :session :results none

(lambda (x y z) (+ ( x x) ( y y) (* z z)))

+END_SRC

$x=2, y=3, z=5$ のときの値を見る.

+BEGIN_SRC scheme :session :results output

(define d ((lambda (x y z) (+ ( x x) ( y y) (* z z))) 2 3 5)) (write d)

+END_SRC

+RESULTS:

38

仮引数の全てに値を与えないとエラーになる.

Python の場合は次の通り.

+BEGIN_SRC ipython :session :results output

def dist(x, y, z): return x2 + y2 + z**2

print(dist(2, 3, 5))

+END_SRC

+RESULTS:

38

Haskell の場合は次の通り.

+BEGIN_SRC haskell :results output

dist :: Int -> Int -> Int -> Int dist x y z = x^2 + y^2 + z^2

main = do print $ dist 2 3 5

+END_SRC

+RESULTS:

:
38

JavaScript の場合は次の通り.

+BEGIN_SRC js :cmd node :results output

function dist(x, y, z) { return Math.pow(x, 2) + Math.pow(y, 2)+ Math.pow(z, 2); } console.log(dist(2, 3, 5));

+END_SRC

+RESULTS:

38
ドット対

引数のデフォルト値とかその辺に関する話のようだが, いまひとつピンと来ない. とりあえず本の記述を優先させて書こう.

必須引数とそれ以外の引数をわけて設定できる.

+BEGIN_SRC scheme :session :results output

(define a ((lambda (x y . z) (+ ( x x) ( y y) (* 5 5))) 2 3)) (write a)

+END_SRC

+RESULTS:

38

z に値を指定していないがエラーにならない. デフォルト値と言われると次のようなのを想像した.

+BEGIN_SRC ipython :session :results output

def dist(x, y, z=5): return x2 + y2 + z**2

print(dist(2, 3)) print(dist(2, 3, 4))

+END_SRC

+RESULTS:

38
29

見たところインタプリタが Gauche ならこんな形があるらしい.

+BEGIN_SRC scheme :session :results none

; Gauche の場合 :optional (x x-default) (y y-default) z

+END_SRC

Scheme として一般的にはどうなっているのだろう?

何はともあれオプションの引数はリストとしてわたる. そのリストからの値取り出しには car, cdr を使う. car はリストの先頭の値を取り出し, cdr はそれ以外をリストで取り出す.

+BEGIN_SRC scheme :session :results output

(define l '(1 2 3)) (write l) (newline) (write (car l)) (newline) (write (cdr l))

+END_SRC

+RESULTS:

(1 2 3)
1
(2 3)
Python 版

さっき書いたがもう一度.

+BEGIN_SRC ipython :session :results output

def dist(x, y, z=5): return x2 + y2 + z**5

print(dist(2, 3))

+END_SRC

+RESULTS:

3138

Python での car, cdr の対応物.

+BEGIN_SRC ipython :session :results output

a = [1, 2, 3] print(a[0]) print(a[1:len(a)])

b = [1] print(b[0]) print(b[1: len(b)])

+END_SRC

+RESULTS:

1
[2, 3]
1
[]

b[1: len(b)] はエラーになるかと思ったがセーフだった.

Haskell 版

オプションの引数をどう定義したらいいのかよくわからなかった.

car, cdrhead, tail と自然に意味が取れる関数名になっている.

+BEGIN_SRC haskell :results output

main = do let a = [1,2,3] print $ head a print $ tail a

+END_SRC

+RESULTS:

:
1
[2,3]

要素が 2 つだけのタプルに対して 1 番目, 2 番目を取り出す関数として fst, snd がある. 初見でわかりづらい可能性があるので補足しておくと, first, second の略だ.

fstsnd はタプルで要素が 2 つある場合にしか使えない. リストでも駄目だし, タプルで 3 つ以上あっても駄目. 実際にコードを書いて実験するとわかる.

+BEGIN_SRC haskell :results output

main = do let a = (1,2) print $ fst a print $ snd a

+END_SRC

+RESULTS:

:
1
2
JavaScript 版

ここを見るとデフォルト値設定をできないことはないが, ウルトラ面倒そう.

carcdr は次のような感じか.

+BEGIN_SRC js :cmd node :results output

var a = [1,2,3] console.log(a[0]); console.log(a.slice(1, a.length));

+END_SRC

+RESULTS:

1
[ 2, 3 ]
リストの生成

括弧がない次のような場合を考えたい.

+BEGIN_SRC scheme :session :results output

(lambda x x)

+END_SRC

この lambda 項はこれだけで任意個数の実引数を受けとる. 評価値はリストで引数に束縛される.

+BEGIN_SRC scheme :session :results output

(define a ((lambda x x) 1 2 3)) (write a)

+END_SRC

+RESULTS:

(1 2 3)

これで与えられた要素からリストを作れる. 組み込みの list はこの糖衣構文である.

+BEGIN_SRC scheme :session :results output

(define list (lambda x x)) (write (list 1 2 3))

+END_SRC

+RESULTS:

(1 2 3)

MIT 記法では次の通り.

+BEGIN_SRC scheme :session :results output

(define (list . x) x) (write (list 1 2 3))

+END_SRC

+RESULTS:

(1 2 3)
Python 版

+BEGIN_SRC ipython :session :results output

a = [1, 2, 3] print(a)

+END_SRC

+RESULTS:

[1, 2, 3]
Haskell 版

+BEGIN_SRC haskell :results output

a = [1, 2, 3] main = do print $ a

+END_SRC

+RESULTS:

:
[1,2,3]
JavaScript 版

リストはない. 代わりは配列か.

+BEGIN_SRC js :cmd node :results output

var a = [1, 2, 3] console.log(a);

+END_SRC

+RESULTS:

[ 1, 2, 3 ]

B.4.5 引数の無い関数

+BEGIN_SRC scheme :session :results output

(write ((lambda () (* 2 3))))

+END_SRC

+RESULTS:

6

比較のために次の定義をしてみよう.

+BEGIN_SRC scheme :session :results output

(define late (lambda () ( 2 3))) (define now ( 2 3)) (write (late)) (newline) (write now)

+END_SRC

+RESULTS:

6
6

この機能を使うと値呼びシステムの中に名前呼び評価を実現させられるとのこと. まだよくわかっていない.

MIT 記法を使うと上の 2 つは次のようにも書ける.

+BEGIN_SRC scheme :session :results output

(define (late) ( 2 3)) (define now ( 2 3)) (write (late)) (newline) (write now)

+END_SRC

+RESULTS:

6
6

同種の機能は最初から Scheme に組み込まれている.

+BEGIN_SRC scheme :session :results output

(write (delay ( 2 3))) (newline) (write (force (delay ( 2 3))))

+END_SRC

+RESULTS:

:118:41 ()>>

6

delay は評価値を出力しないで保留する. force ではじめてその評価値を出力する.

この遅延を含んだ計算がプログラムの発想を大いに変えるらしい. まだよくわかっていない.

Python, Haskell, JavaScript で何か対応するものはあるだろうか?

B.4.6 抽象化の問題

要は中身は無視して同じ入力に対して同じ出力を返すかどうかを見るとかそういう感じの話. 具体例としては次の 3 つ.

+BEGIN_SRC scheme :session :results output

(define five (lambda (x) ( 5 x))) (define five' (lambda (y) (+ y y y y y))) (define five'' (lambda (z) (/ ( 10 z) 2)))

(write (five 2)) (newline) (write (five' 2)) (newline) (write (five'' 2))

+END_SRC

+RESULTS:

10
10
10

中身は違うが入力した数を 5 倍する作用は同じ.

B.4.7 quote

リテラルを増やす方法. "Gauss" と入力するとこれはリテラルと見なされて評価値は "Gauss" になる. 引用符の無い文字列そのものを評価値とする方法があって, それが quote だ.

+BEGIN_SRC scheme :session :results output

(write (quote Gauss)) (newline) (write "Gauss") (newline) (write (quote "Gauss")) (newline) (write 'Gauss)

+END_SRC

+RESULTS:

Gauss
"Gauss"
"Gauss"
Gauss

最初の 2 つの評価の違いは何なのだろう? 参考のために 3 つめを追加した. write をかませていることはあるにせよよくわかっていない.

ちなみに quote はよく出てくるので ' で代用できるようになっている. もちろん糖衣構文というやつだ.

Scheme はまず引数の評価値を求める. だから評価されて困るなら quote しないといけない. 今まで elisp もちょこちょこ書いていたがいまだにこの辺がよくわかっていない. 本にも書いてある次のコードを見てようやく何かわかるような気がしてきた気もする.

+BEGIN_SRC scheme :session :results output

(write (+ 2 3)) (newline) (write '(+ 2 3))

+END_SRC

+RESULTS:

5
(+ 2 3)

評価しないでそのまま出力するというのはこういう感じ. これがプログラムをデータをしてやりとりできる LISP 系の言語の特性とかいうのはよく聞くもの, まだきちんとわかった気はしない.

この逆が eval. この辺を見てみたがよくわからない. eval の第 2 引数の指定の仕方で, 本に書かれた次の指定は Gauche 前提の実装で, 他のコンパイラでうまくいく保証がないようだ. Guile で実行しているこのミニ講座という名の自前まとめではどうなるのかよくわからない.

+BEGIN_SRC scheme :session :results none

(eval '(+ 2 3) (interaction-envirionment))

+END_SRC

これ, Guile の REPL (と言ってもいい? コマンドラインで guile と叩いて実行するやつ) で 実行したらエラーになる. ちなみに次の 2 つもエラーになる.

+BEGIN_SRC scheme :session :results output

(eval 'x (environment '(a library))) (eval 'x (scheme-environment 5))

+END_SRC

エラーメッセージは次の通り.

+BEGIN_SRC scheme :session :results output

scheme@(guile-user)> (eval 'x (environment '(a library))) ;;; :1:9: warning: possibly unbound variable `environment' :1:9: In procedure #:1:0 ()>: :1:9: In procedure module-lookup: Unbound variable: environment

+END_SRC

+BEGIN_SRC scheme :session :results output

scheme@(guile-user) [1]> (eval 'x (scheme-environment 5)) ;;; :2:9: warning: possibly unbound variable `scheme-environment' :2:9: In procedure #:2:0 ()>: :2:9: In procedure module-lookup: Unbound variable: scheme-environment

+END_SRC

よくわからない.

ちなみにこの quote, 他の言語で対応する概念あるのだろうか. マクロと何か関係あるとか何とかいうのを聞いたこともあるが, 全くわからない. ちょっと調べてみたが一応いろいろあるらしい. いったん「よくわからない」箱に入れておいて適当なときに勉強しよう.

リストとの関係

四則演算の手続も記号としてリストにできるとか何とかいう次のサンプルコードがある.

+BEGIN_SRC scheme :session :results output

(write ((car (list + - * /)) 5 3)) (newline) (write ((cadr (list + - * /)) 5 3)) (newline) (write ((caddr (list + - * /)) 5 3)) (newline) (write ((cadddr (list + - * /)) 5 3))

+END_SRC

+RESULTS:

8
2
15
5/3

caddrcarcdr を適当に組み合わせた関数だ. 4 段の深さまで計 28 個ある.

コード内に空リストを書きたいなら次のように書く.

+BEGIN_SRC scheme :session :results output

(write '())

+END_SRC

+RESULTS:

()

数値はリテラルだから空リストとの cons でそれを唯一つの要素とするリストが作れる.

+BEGIN_SRC scheme :session :results output

(write (cons 7 '()))

+END_SRC

+RESULTS:

(7)

文字はリテラルではないから quote が必要.

+BEGIN_SRC scheme :session :results output

(write (cons 'b '()))

+END_SRC

+RESULTS:

(b)

cons を何度も重ねるのもめんどいので list を使うのがいい.

+BEGIN_SRC scheme :session :results output

(write (list 'a 'b 'c))

+END_SRC

+RESULTS:

(a b c)

list は lambda 項 (lambda x x) だった: (define list (lambda x x)).

リストを繋ぎたいなら append.

+BEGIN_SRC scheme :session :results output

(write (append '(a b) '(c d)))

+END_SRC

+RESULTS:

(a b c d)

念のため consappend の違いを見ておこう.

+BEGIN_SRC scheme :session :results output

(write (cons '(a b) '(c d))) (newline) (write (append '(a b) '(c d))) (newline) (write (append '((a b)) '(c d)))

+END_SRC

+RESULTS:

((a b) c d)
(a b c d)
((a b) c d)

carcdr の再確認.

+BEGIN_SRC scheme :session :results output

(write (car (list 'a 'b 'c))) (newline) (write (cdr (list 'a 'b 'c)))

+END_SRC

+RESULTS:

a
(b c)

2 重の quote に対する分析.

+BEGIN_SRC scheme :session :results output

(write (car (quote (quote Euler)))) (newline) (write (car ''Euler))

+END_SRC

+RESULTS:

quote
quote

素数夜曲とプログラミング B.4.8-B.4.9 Scheme, Python, Haskell, JavaScript 第4回

投稿用メモ

素数夜曲とこれまでの内容

素数夜曲
これまでの内容

B.4.8 内部と外部

未定義の文字列 scope を使って次のように入力するとクロージャになるらしい.

+BEGIN_SRC scheme :session :results session

(lambda () scope)

+END_SRC

そして括弧をつけて評価してみると未定義を警告するエラーが出る.

+BEGIN_SRC scheme :session :results output

((lambda () scope))

+END_SRC

+RESULTS:

An error occurred.

いったん次のようにトップレベル (クロージャの外部) で定義する.

+BEGIN_SRC scheme :session :results output

(define scope 'external) (write ((lambda () scope)))

+END_SRC

+RESULTS:

external

これはクロージャの変数が scope の外部定義された情報を参照していることを表す. 次にクロージャ内部に scopelet で定義してみる.

+BEGIN_SRC scheme :session :results output

(write ((lambda () (let ((scope 'internal)) scope))))

+END_SRC

+RESULTS:

internal

もちろん内部で定義した scope を使っている. これを隠蔽 (shadow) と呼ぶそうだ.

変数の視野

クロージャ内部の let を入れ子にしてさらに隠蔽の仕組みを調べる.

+BEGIN_SRC scheme :session :results output

(write ((lambda () (let ((scope 'outer)) (let ((scope 'inner)) scope)))))

+END_SRC

+RESULTS:

inner

最後の scope に近い内側の let の定義が優先されている.

let で lambda 項との束縛関係も定義できる.

+BEGIN_SRC scheme :session :results output

(let ((times2 (lambda (x) ( x 2))) (times3 (lambda (x) ( x 3)))) (write (times3 (times2 2))))

+END_SRC

+RESULTS:

12

これは define による定義と似てはいるが times2, times3 は大域的な関数として定義されない. 次の形式では束縛する引数がない.

+BEGIN_SRC scheme :session :results output

(let () (define times2 (lambda (x) ( x 2))) (define times3 (lambda (x) ( x 3))) (write (times3 (times2 2))) )

+END_SRC

+RESULTS:

12

let の仕様を忘れたので次の確認コードを. let の外では times2, times3 は未定義だ.

+BEGIN_SRC scheme :session :results output

(let () (define times2 (lambda (x) ( x 2))) (define times3 (lambda (x) ( x 3))) ) (write (times3 (times2 2)))

+END_SRC

+RESULTS:

An error occurred.

これに対するのは begin: begin の外でも times2times3 は定義されている.

+BEGIN_SRC scheme :session :results output

(begin (define times2 (lambda (x) ( x 2))) (define times3 (lambda (x) ( x 3))) (write (times3 (times2 2)))) (newline) (write (times3 (times2 2)))

+END_SRC

+RESULTS:

12
12
let*

let の複数の引数設定での順序の問題を考える. 例えば次のコードはエラーになる. z を設定するときに x, y が設定済みとは限らないから.

+BEGIN_SRC scheme :session :results output

(let ((x 2) (y 3) (z (* x y))) z)

+END_SRC

+RESULTS:

An error occurred.

let を入れ子にする方法もあるが左から右へ順序を限定して設定する let* もある. まずは入れ子の let を見てみよう.

+BEGIN_SRC scheme :session :results output

(let ((x 2) (y 3)) (let ((z (* x y))) (write z)))

+END_SRC

+RESULTS:

6

次に let*.

+BEGIN_SRC scheme :session :results output

(let ((x 2) (y 3) (z ( x y))) (write z))

+END_SRC

+RESULTS:

6
letrec

let の使い分けシリーズ. 次のような形式でも使える.

+BEGIN_SRC scheme :session :results output

(write ((letrec ((z (lambda () (* x y))) (x 2) (y 3)) z)))

+END_SRC

+RESULTS:

6

単独では未定義な (* x y)z の値にするから, 単に (z (* x y)) とするとエラーになる (そういう実装もあるしエラーにならない実装もある). letrec は定数部分からも引数が見えるので再帰関数を定義するのに使える. define による定義に一番近い形式になるとのこと.

再帰関数の定義に関して名前からすればそうなのだろうが, 少なくとも B パートから読みはじめた限りでは本で実例が出てきていない. こんな文章だけでは理解できないから P.443 から例を取ってくる.

まずは define による形式から.

+BEGIN_SRC scheme :session :results output

(define fact (lambda (n) (if (zero? n) 1 (* n (fact (- n 1)))))) (write (fact 4))

+END_SRC

+RESULTS:

24

次に letrec.

+BEGIN_SRC scheme :session :results output

(letrec ((fact (lambda (n) (if (zero? n) 1 (* n (fact (- n 1))) )))) (write (fact 5)))

+END_SRC

+RESULTS:

120

書いてみたのはいいが letrec でないといけないところはどこだろう. letrecletlet* にするとエラーになることは確認した.

B.4.9 set!

既に定義されている変数の値を変更するためには set! を使う. ! は破壊的な変更を表すのに使う. この流儀, 少なくとも Ruby は受け継いでいる. 他にも何かあった気がするがパッと思い浮かばない.

+BEGIN_SRC scheme :session :results output

(set! [name] [new-value])

+END_SRC

[name] には値を変更する変数の名前を, [new-value] には新たな値を入れる.

試してみよう. まずは同じ変数相手に define を重ねるとエラーになる.

+BEGIN_SRC scheme :session :results output

(define a 3) (define a 4)

+END_SRC

+RESULTS:

An error occurred.

では set! を.

+BEGIN_SRC scheme :session :results output

(define a 3) (write a) (newline) (set! a 4) (write a)

+END_SRC

+RESULTS:

3
4

let の内部でも同じ.

+BEGIN_SRC scheme :session :results output

(let ((z 0)) (write z) (newline) (set! z 2) (write z))

+END_SRC

+RESULTS:

0
2
大域変数

z をトップレベルで定義すると大域変数になる. Schem では大域変数をアスタリスクで囲む慣習があるようだ. しかし短い変数名の変数についてはいちいち *z* のようにはしないらしい.

何はともあれ次の評価を見てみよう.

+BEGIN_SRC scheme :session :results output

(define z 5) (write ((lambda () (let ((z 0)) (set! z (* 2 3)) (write z) (newline)) z)))

+END_SRC

+RESULTS:

6
5

let の内部で set! をしていて, そこでの評価は確かに 6 になっている. つまり破壊的に変更されている. しかし let の外にその変更の情報は出ていかないから lambda の評価値は 5 になる.

このように内部定義に束縛されていない変数は外部に定義を探しに行く. それはクロージャの窓口として機能する.

しかしこの「それはクロージャの窓口として機能する」というのが何を意味しているのかまだよくわかっていない. とりあえず本にしたがってコードを書いていこう.

+BEGIN_SRC scheme :session :results output

(define z 5) ((lambda () (set! z (* 3 5)))) (write z)

+END_SRC

+RESULTS:

15

確かに z が破壊的に変更に変更されていることを示している.

+BEGIN_SRC scheme :session :results output

(define z 5) (write ((lambda () (set! z (* 3 5)) z))) (newline) (write z)

+END_SRC

+RESULTS:

15
15

こちらは lambdaz の値そのものを返している.

P.438 の冒頭は lambda の中で lambda の最後に書いた変数が lambda の返り値になっていると言っているだけで, それ以上の何かを言っているわけではないと思うがどうなのだろう.

Ruby や Scala はそういう仕様だったと思う. 次のコードは Ruby のコード.

+BEGIN_SRC ruby :results output

def mytest a = 3 end

def mytest2 a = 4 a end

p mytest p mytest2

+END_SRC

+RESULTS:

3
4

特に関数 mytest2 を見てほしい. 最後に a と書いただけで return a と同じ意味になっている.

対比のために Python のコードを.

+BEGIN_SRC ipython :session :results output

def mytest(): a = 3 a

def mytest2(): a = 4 return a

print(mytest()) print(mytest2())

+END_SRC

+RESULTS:

None
4

Python はきちんと return しないと値が返らない.

コメントで何度もご指摘頂いたようにクロージャやスコープが全くわかっていないのだろう. この辺の話すっきりしないものがずっと残っている. しばらくやっているとそのうちわかるときが来るだろうから, それまで耐えよう.

大域環境によるカウンタ

大域変数の値を set! で変えると変更された値をまた大域環境から取り出せる.

+BEGIN_SRC scheme :session :results output

(define n 0) (write n) (newline) (set! n (+ n 1)) (write n) (newline) (set! n (+ n 1)) (write n) (newline) (set! n (+ n 1)) (write n) (newline)

+END_SRC

+RESULTS:

0
1
2
3

次の count 関数を定義すると大域環境が壊れない限りカウントが続く.

+BEGIN_SRC scheme :session :results output

(define count (lambda () (set! n (+ n 1)) (write n) (newline))) (define n 0) (count) (count) (count)

+END_SRC

+RESULTS:

1
2
3

Python, JavaScript, Haskell だとどう書くのだろう. Python で次のようなコードを書いてみたがうまくいかない. Python ならジェネレータで対応するような話なのだと勝手に思っているがよくわからない.

+BEGIN_SRC ipython :session :results output

def count(): return n + 1

n = 0 print(count()) print(count()) print(count())

+END_SRC

+RESULTS:

1
1
1

次のようなコードが対応するコードだろうか.

+BEGIN_SRC ipython :session :results output

def counter(): num = 0 while True: yield num num += 1

count = counter() for i in count: print(i) if i >= 3: break

print("ここで 4 が出る") print(count.next())

for i in count: print(i) if i >= 7: count.close()

+END_SRC

+RESULTS:

0
1
2
3
ここで 4 が出る
4
5
6
7
内部環境によるカウンタ

ここまで n は無防備な状態で大域的にさらしていたので, どこからどんな変更を受けるかわからず「安全」ではない. そこで安全なカウンターとして扱うために変数を隠蔽したい.

ところで JavaScript で DOM の扱いが大変とかその辺の話とも絡むと思っている.

それはそれとして count を改善しよう. 変数 n を内部で定義すればいい.

+BEGIN_SRC scheme :session :results output

(define count ((lambda (n) (lambda () (set! n (+ n 1)))) 0)) (write count) (newline) (write (count))

+END_SRC

+RESULTS:

:77:5 ()>

素数夜曲には「(count) を連打することで, くり返し評価値が返されて, 数値が 1 から順に上がっていく」とあるが, どうやって n を取り出せばいいのだろう. いわゆるリターン的なやつはどこに仕込めばいいのか. これ, lambda が何なのか全くわかっていないからこの程度もわからないのだろう. とてもつらい.

とりあえず本に沿って let で書き直す.

+BEGIN_SRC scheme :session :results output

(define count (let ((n 0)) (lambda () (set! n (+ n 1)))))

+END_SRC

+RESULTS:

An error occurred.

何かエラーになった. どこかタイポしているようだがどこだかわからない.

それはそれとして, 最初のコードに関して 力づくで次のような (私の当初の意図とは違う) 出力を得た.

+BEGIN_SRC scheme :session :results output

(define count ((lambda (n) (lambda () (set! n (+ n 1)) (write n) (newline))) 0)) (count) (count) (count)

+END_SRC

+RESULTS:

1
2
3

あくまで count では n の値を返してほしい.

本によれば lambda に先駆ける let, 通称 let over lambda はクロージャを作り, これがいい感じに振る舞ってくれるとかいう話. クロージャどころか lambda の理解さえあやふやになってきた.

追記

またもコメントを頂いた.

記事中のこれ

(define count ((lambda (n) (lambda () (set! n (+ n 1)))) 0))

(define count (let ((n 0)) (lambda () (set! n (+ n 1)))))

は Scheme の手続きとしては誤っていないはずなのでただちにエラーになるものではありませんが、値を返さないのは set! フォームの返却値は未規定になっているからです。

(define count (let ((n 0)) (lambda () (set! n (+ n 1)) n)))

という風に最後に n を入れれば n の内容を返す手続きになります。

Scheme で言うところの「返却値が未規定」というのは、何を返すかは処理系の裁量で決めていいし、ユーザは何が返ってくるかあてにしてはいけないということです。 代入した値が返ってくるかもしれませんし、変数の名前が返ってくるものも存在しますし、状況に無関係なデタラメな値かもしれません。 Guile の場合は未規定を意味する型のオブジェクトが設けられていてそれが返るようですね。 # と表示されるのはそういう意味です。

return がないが, やはり Scala や Ruby のように最後に値を入れれば return 的なことはしてくれるのか.

+BEGIN_SRC scheme :session :results output

(define count (let ((n 0)) (lambda () (set! n (+ n 1)) n))) (write (count)) (newline) (write (count))

+END_SRC

+RESULTS:

1
2

ここで最初 (count)count と素の形で書いていて #<procedure 10a6234e0 at <current input>:149:4 ()> と出力されてはまった. (count) としないといけないところを count と書いてしまうのは, 根本的なところで Scheme を理解していないという気しかしない. Emacs LISP を書かねばならないときも同じことをよく思う. クォートをつけないといけないときもいまだによくわからない.

let 使わない方も書いておこう.

+BEGIN_SRC scheme :session :results output

(define count ((lambda (n) (lambda () (set! n (+ n 1)) n)) 0)) (write (count)) (newline) (write (count))

+END_SRC

+RESULTS:

1
2

ようやくできた.

generator

この手続きをさらに一般化するために次の generator を作る.

+BEGIN_SRC scheme :session :results output

(define generator (lambda () (let ((n 0)) (lambda () (set! n (+ n 1)) (write n) (newline))))) (define count-1 (generator)) (define count-2 (generator)) (write count-1) (newline) (write count-2)

+END_SRC

+RESULTS:

:111:6 ()>

:111:6 ()>

値が返ってこない. 鬱陶しい.

+BEGIN_SRC scheme :session :results output

(define generator (lambda () ((lambda (n) (lambda () (set! n (+ n 1)) (write n) (newline))) 0))) (define count-1 (generator)) (define count-2 (generator)) (write (count-1)) (newline) (write (count-1)) (newline) (write (count-2)) (newline) (write (count-1)) (newline)

+END_SRC

+RESULTS:

1

2

1

3

とりあえず独立したカウンターとして動作することを確認した. しかし はどこから出てくるのだろう. (write (count-1)) あたりを削除するとエラーになる. この程度もわかっていないとかさすがに震える.

カウンターの初期値を設定できる generator を書いて終わり.

+BEGIN_SRC scheme :session :results output

(define generator (lambda (initial) (let ((n initial)) (lambda () (set! n (+ n 1)) (write n) (newline))))) (define count-1 (generator -1)) (define count-2 (generator 0)) (write (count-1)) (newline) (write (count-1)) (newline) (write (count-1)) (newline) (write (count-2)) (newline)

+END_SRC

+RESULTS:

0

1

2

1

これも多分上のコードと同じ変なところがあるはずだ. Scheme はとても難しい印象を受けた.

追記

count に関してコメントを頂いたことを受け, 書き直してみよう.

+BEGIN_SRC scheme :session :results output

(define generator (lambda (initial) (let ((n initial)) (lambda () (set! n (+ n 1)) n)))) (define count-1 (generator -1)) (define count-2 (generator 0)) (write (count-1)) (newline) (write (count-1)) (newline) (write (count-1)) (newline) (write (count-2)) (newline)

+END_SRC

+RESULTS:

0
1
2
1

中の lambda の中で n を返すことにしたらエラーにならなくなった. それ自体はいいのだがまだピンと来ない. 正確に言うならもう一度同じような処理が必要になったとき, 混乱せずに一発できちんとしたコードを書ける自信がない.

じっくりやるしかない.

素数夜曲とプログラミング B.4.10-B. Scheme, Python, Haskell, JavaScript 第5回

投稿用メモ

素数夜曲とこれまでの内容

素数夜曲
これまでの内容

B.4.10 if

quote は引数の評価値を返している訳ではない. define, lambda も引数との関連を定めているだけで, 手続として機能している訳ではない. こうした Scheme の一般的な評価規則に従わないものを特殊形式と呼ぶ.

このあたり, いまだにあまりよくわかっていない. set! やここでのメイン if も特殊形式とのこと.

とりあえず if の一般形を.

+BEGIN_SRC scheme :session :results output

(if [predi] [consq] [altna])

+END_SRC

predi は述語 (predicate), consq は帰結部 (consequence), altna は代替部 (alternative).

とりあえずサンプルコード.

+BEGIN_SRC scheme :session :results output

(if (> 2 1) (write "OK") (write "NG")) (newline) (if (> 1 2) (write "OK") (write "NG"))

+END_SRC

+RESULTS:

"OK"
"NG"

elisp だとインデント合わなかった気がする.

+BEGIN_SRC emacs-lisp :results xmp silent

(if (> 2 1) ; => t "OK" ; => "OK" "NG") ; => "OK" (if (> 1 2) ; => nil "OK" ; => "NG") ; => "NG"

+END_SRC

Emacs 先生の自動インデントを使うとインデント合わなかった. elisp はほとんど書かないけれども, if よりは cond をよく使っている. いまは Scheme なのであって elisp の話をしていてもしょうがないのだが. cond は Scheme にもあるようだ: あとで出てくるとのこと.

絶対値を求める関数を定義する. 本には「手続」と書いてあったが Scheme としてはそう呼んだ方がいいのだろうか?

+BEGIN_SRC scheme :session :results output

(define abs (lambda (x) (if (< x 0) (- x) x))) (write (abs -1)) (newline) (write (abs 1))

+END_SRC

+RESULTS:

1
1

ちなみに組み込みの述語で positive?, zero?, negative? がある.

+BEGIN_SRC scheme :session :results output

(write "positive?") (newline) (write (positive? 1)) (newline) (write (positive? 0)) (newline) (write (positive? -1)) (newline)

(write "zero?") (newline) (write (zero? 1)) (newline) (write (zero? 0)) (newline) (write (zero? -1)) (newline)

(write "negative?") (newline) (write (negative? 1)) (newline) (write (negative? 0)) (newline) (write (negative? -1))

+END_SRC

+RESULTS:

+begin_example

"positive?"

t

f

f

"zero?"

f

t

f

"negative?"

f

f

t

+end_example

これを使って abs を書き直そう.

+BEGIN_SRC scheme :session :results output

(define abs (lambda (x) (if (negative? x) (- x) x))) (write (abs 1)) (newline) (write (abs 0)) (newline) (write (abs -1)) (newline)

+END_SRC

+RESULTS:

1
0
1

この辺, タイトルにあるように Python や Haskell や JavaScript でも書いた方がいいのだろうが, ここまで自明だとわざわざ書く気がしない.

ゼロ判定の例として階乗のコードが出ている.

+BEGIN_SRC scheme :session :results output

(define fact (lambda (n) (if (zero? n) 1 (* n (fact (- n 1)))))) (write (fact 5))

+END_SRC

+RESULTS:

120

letrec によるコードもある.

+BEGIN_SRC scheme :session :results output

(letrec ((fact (lambda (n) (if (zero? 0) 1 (* n (fact (- n 1))) )))) (write (fact 5)))

+END_SRC

+RESULTS:

1

どうして 1 になってしまうのだろう. それはそれとして letrec というか let の書式がいまだに頭に全く入らない.

Python 版の階乗

+BEGIN_SRC ipython :session :results output

def fact(n): if n == 0: return 1 else: return n * fact(n-1)

print(fact(5))

+END_SRC

+RESULTS:

120

いまだ名前しかわかっていない末尾再帰とかそのあたりは全く意識していない.

Haskell 版の階乗

+BEGIN_SRC haskell :results output

fact n = if n == 0 then 1 else n * fact (n - 1)

main = do print $ fact 5

+END_SRC

+RESULTS:

:
120
JavaScript 版の再帰

+BEGIN_SRC js :cmd node :results output

function fact(n) { if (n === 0) { return 1; } else { return n * fact(n-1) } }

console.log(fact(5));

+END_SRC

+RESULTS:

120

B.4.11 特殊形式の意味

if が特殊形式であることは次のコードからも推測されるらしい.

+BEGIN_SRC scheme :session :results output

(define Card-1 (lambda (x) (if (= x 1234) 'ok! oops!))) (write (Card-1 1234))

+END_SRC

+RESULTS:

ok!

ここで oops!quote していないは意図的だ 1234 以外を評価してみよう.

+BEGIN_SRC scheme :session :results output

(define Card-1 (lambda (x) (if (= x 1234) 'ok! oops!))) (write (Card-1 1))

+END_SRC

+RESULTS:

An error occurred.

oops!quote していないので怒られる. (= x 1234)#f のときにはじめて評価値を求めるプロセスがはじまる. そのとき oops!quote されていないから解釈系は未定義変数と認識し, 異常終了になるという寸法. これが if が一般評価規則に従わない形式であるということだ, ということらしい.

Scheme の一般評価規則とは何だったろうか, と思って本の索引を調べてみたら死ぬほど使いづらい. 変に細かく項目わけされているせいで, どこに一般評価規則が載っているのかよくわからない. PDF で検索できるわけでもないのに本当に使いづらくていらつく.

cond

というわけで次は cond. 一般形は次の通り.

+BEGIN_SRC scheme :session :results silent

(cond ([predi1] [exp1]) ([predi2] [exp2]) ... ([predin] [expn]))

+END_SRC

絶対値 abscond を使って書いてみる.

+BEGIN_SRC scheme :session :results output

(define abs (lambda (x) (cond ((positive? x) x) ((zero? x) 0) ((negative? x) (- x)) ))) (write (abs 1)) (newline) (write (abs 0)) (newline) (write (abs -1))

+END_SRC

+RESULTS:

1
0
1
Python 版 if

わざわざ書くほどのことでもないが, elif がいまだに覚えられないので.

+BEGIN_SRC ipython :session :results output

def abs(x) if x > 0 return x elsif return 0 else return -x end end

print(abs(1)) print(abs(0)) print(abs(-1))

+END_SRC

+RESULTS:

1
0
1

Ruby だと elsif でそれとも混ざって鬱陶しい.

+BEGIN_SRC ruby :results xmp silent

def abs(x) if x > 0 return x elsif x == 0 return 0 else return -x end end

abs(1) # => 1 abs(0) # => 0 abs(-1) # => 1

+END_SRC

JavaScript 版 if

これは else if.

+BEGIN_SRC js :cmd node :results output

function abs(x) { if (x > 0) { return x; } else if (x === 0) { return 0; } else { return -x; } } console.log(abs(1)); console.log(abs(0)); console.log(abs(-1));

+END_SRC

+RESULTS:

1
0
1
Haskell 版 if

if はあるが複数条件がある場合は case.

+BEGIN_SRC haskell :results output

myabs x = case (x >= 0) of True -> x otherwise -> -x

main = do print $ myabs 1 print $ myabs 0 print $ myabs (-1)

+END_SRC

+RESULTS:

:
1
0
1

関数定義の場合は次のように書いた方がいい.

+BEGIN_SRC haskell :results output

myabs x | x > 0 = x | x == 0 = 0 | otherwise = -x

main = do print $ myabs 1 print $ myabs 0 print $ myabs (-1)

+END_SRC

+RESULTS:

:
test
1
0
1
cond による if

condif を書いてみる.

+BEGIN_SRC scheme :session :results output

(define my-if (lambda (predi consq altna) (cond (predi consq) (else altna)))) (define Card-2 (lambda (x) (my-if (= x 1234) 'ok! oops!))) (write (Card-2 1234))

+END_SRC

+RESULTS:

An error occurred.

今度は 1 ですらエラーになった. 組み込みの if ではエラーにならなかったので, 組み込みの if とは違うことはわかる.

まずは呼び出し側の Card-3 を直して対応しよう.

+BEGIN_SRC scheme :session :results output

(define my-if (lambda (predi consq altna) (cond (predi consq) (else altna)))) (define Card-3 (lambda (x) (my-if (= x 1234) (lambda () 'ok!) (lambda () oops!)))) (write (Card-3 1234)) (newline) (write ((Card-3 1234)))

+END_SRC

+RESULTS:

:120:11 ()>

ok!

とりあえず動くには動く. しかし ((Card-3 1234)) と二重に括弧でくくらないといけない.

my-if を直してみる.

+BEGIN_SRC scheme :session :results output

(define my-if (lambda (predi consq altna) (cond (predi (consq)) (else (altna))))) (define Card-3 (lambda (x) (my-if (= x 1234) (lambda () 'ok!) (lambda () oops!)))) (write (Card-3 1234))

+END_SRC

+RESULTS:

ok!

() を 1 つ my-if に押しつけた. 引数の評価を遅らせることで特殊形式を模倣した. このアイデアは名前呼びで定義されたプログラムを Scheme のような値呼びの言語で実行するときに役立つとのこと.

andor

述語 (predicate) and, or も特殊形式. not は特殊形式ではない.

$1 < x < 9$ を示す不等式は次の関数で表現できる.

+BEGIN_SRC scheme :session :results output

(define dom1-9 (lambda (x) (and (< 1 x) (< x 9)))) (write (dom1-9 1)) (newline) (write (dom1-9 1.1)) (newline) (write (dom1-9 8.9)) (newline) (write (dom1-9 9))

+END_SRC

+RESULTS:

f

t

t

f

「以上」「以下」に関しても関数サンプルを.

+BEGIN_SRC scheme :session :results output

(define or-less5 (lambda (x) (or (< x 5) (= x 5)))) (define or-more5 (lambda (x) (or (> x 5) (= x 5)))) (write (or-less5 4.9)) (newline) (write (or-less5 5)) (newline) (write (or-less5 5.1)) (newline) (write (or-more5 4.9)) (newline) (write (or-more5 5)) (newline) (write (or-more5 5.1))

+END_SRC

+RESULTS:

t

t

f

f

t

t

本来は組み込みの <= を使えばいい.

+BEGIN_SRC scheme :session :results output

(define ol5-S (lambda (x) (<= x 5))) (define om5-S (lambda (x) (>= x 5))) (write (ol5-S 4.9)) (newline) (write (ol5-S 5)) (newline) (write (ol5-S 5.1)) (newline) (write (ol5-S 4.9)) (newline) (write (ol5-S 5)) (newline) (write (ol5-S 5.1))

+END_SRC

+RESULTS:

t

t

f

t

t

f

ドット対とリストは (pair) と呼ばれる. 対象が対 (pair) かどうかを判定する述語が pair?: 空リストはペアではない. 対象が空リストかどうかを判定するのは null?.

+BEGIN_SRC scheme :session :results output

(write (pair? 1)) (newline) (write (pair? '())) (newline) (write (pair? '(1 . 2))) (newline) (write (pair? '(1 2 3))) (newline) (write (null? '())) (newline) (write (null? '(1 2)))

+END_SRC

+RESULTS:

f

f

t

t

t

f

pair? はクオートをつけないとエラーになった. このあたりがやはりいまだによくわかっていない. ドット対が何なのかよくわかっていない.

ペアではないことを判定する関数を作る.

+BEGIN_SRC scheme :session :results output

(define nonpair? (lambda (x) (not (pair? x)))) (define atom? (lambda (x) (and (nonpair? x) (not (null? x))))) (write (atom? '(1 2))) (newline) (write (atom? '())) (newline) (write (atom? 'atom))

+END_SRC

+RESULTS:

f

f

t

次のコードがエラーになる.

+BEGIN_SRC scheme :session :results output

(define nonpair? (lambda (x) (not (pair? x)))) (write (nonpair? '())) (define atom? (lambda (x) (and (nonpair? x) (not (null? x)))))

+END_SRC

+RESULTS:

An error occurred.

エラーメッセージは次の通り.

scheme@(guile-user)> ice-9/boot-9.scm:703:29: In procedure map: ice-9/boot-9.scm:703:29: Syntax error: unknown file:19:0: definition in expression context, where definitions are not allowed, in form (define atom? (lambda (x) (and (nonpair? x) (not (null? x)))))

In ice-9/boot-9.scm: 703:29 0 (map # (((("p…" …) …) . #) …))

しかし次のコードは動く.

+BEGIN_SRC scheme :session :results output

(define nonpair? (lambda (x) (not (pair? x)))) (write (nonpair? '(1 2))) (newline) (write (nonpair? '()))

+END_SRC

+RESULTS:

f

t

何でだろう?

B.5 型の確認と構文の拡張

Scheme は型を厳しく精査するシステムではない.

B.5.1 非数値データの型

次のような述語がある.

型チェックの手続を導入する.

+BEGIN_SRC scheme :session :results output

(define type-check (lambda (x) (define form (lambda (str) (display "This is ") (display str))) (cond ((procedure? x) (form "a procedure: ") x) ((number? x) (form "a number: ") x) ((pair? x) (form "a pair: ") x) ((null? x) (form "a null: ") x) ((symbol? x) (form "a symbol: ") x) ((string? x) (form "a string: ") x) ((char? x) (form "a char: ") x) ((boolean? x) (form "a boolean: ") x) ((vector? x) (form "a vector: ") x) (else (display "may be a special form: ") x) ) (newline))) (type-check car) (type-check 3.14) (type-check '(a b)) (type-check '()) (type-check 'a) (type-check "ab") (type-check #\a) (type-check #t) (type-check #(1 2)) ;(type-check if) ;; エラーになる

+END_SRC

+RESULTS:

This is a procedure:
This is a number:
This is a pair:
This is a null:
This is a symbol:
This is a string:
This is a char:
This is a boolean:
This is a vector:

上の結果で procedure:number: のあとに変数の値が出るはずなのだが出ない. コードの転記ミスだろうか? また (type-check if) がエラーになる. 処理系の問題だろうか?

+BEGIN_SRC scheme :session :results output

(define type-check (lambda (x) (define form (lambda (str) (display "This is ") (display str))) (cond ((procedure? x) (form "a procedure: ") x) ((number? x) (form "a number: ") x) ((pair? x) (form "a pair: ") x) ((null? x) (form "a null: ") x) ((symbol? x) (form "a symbol: ") x) ((string? x) (form "a string: ") x) ((char? x) (form "a char: ") x) ((boolean? x) (form "a boolean: ") x) ((vector? x) (form "a vector: ") x) (else (display "may be a special form: ") x) ) (newline))) (type-check (number? 3.14)) (type-check (and))

+END_SRC

+RESULTS:

This is a boolean:
This is a boolean:

B.5.2 数値データの型

素数夜曲とプログラミング D.1- Scheme, Python, Haskell, JavaScript 第6回

組み込み関数による数値計算

床関数, 天井関数

基本的な言語仕様ばかりやっていてもうんざりするので適当に先に進める.

実数 $x$ に対して $x$ 以下で $x$ に一番近い整数を返す 床関数 floor() から. いわゆるガウス記号のようによく使われる記号表記もあるし, 数学的にきちんと定義することもできるが, めんどいので説明は省略.

いわゆる小数点以下を切り捨てる関数だ. ただこれ, 小数点以下切り捨てに見えるのは正の数だけだ. 実際には $x$ を越えない最大の整数を返す関数だから負の数では切り上げのように見える.

文章でわけがわからないなら実物を見ればいい.

+BEGIN_SRC scheme :session :results output

(write (floor 3)) (newline) (write (floor 3.14)) (newline) (write (floor -2.72)) (newline) (write (floor -2.14))

+END_SRC

+RESULTS:

3
3.0
-3.0
-3.0

次はこの逆, $x$ 以上で $x$ に一番近い整数を返す天井関数 ceiling().

+BEGIN_SRC scheme :session :results output

(write (ceiling 3)) (newline) (write (ceiling 3.14)) (newline) (write (ceiling -2.72)) (newline) (write (ceiling -2.14)) (newline)

+END_SRC

+RESULTS:

3
4.0
-2.0
-2.0

床関数と天井関数には次の関係がある.

この相互関係を調べるために次の関数を定義しよう.

+BEGIN_SRC scheme :session :results output

(define (room n) (list 'ceiling= (ceiling n) 'floor= (floor n))) (write (room 3)) (newline) (write (room -3)) (newline) (write (room 3.14)) (newline) (write (room -3.14)) (newline) (write (room -2.72)) (newline) (write (room 2.72)) (newline) (write (room -2.14)) (newline) (write (room 2.14))

+END_SRC

+RESULTS:

(ceiling= 3 floor= 3)
(ceiling= -3 floor= -3)
(ceiling= 4.0 floor= 3.0)
(ceiling= -3.0 floor= -4.0)
(ceiling= -2.0 floor= -3.0)
(ceiling= 3.0 floor= 2.0)
(ceiling= -2.0 floor= -3.0)
(ceiling= 3.0 floor= 2.0)

そして次の衝撃的な記述が出てきた.

また, 数学的問題の解決が主となるので, 特に断らない限り「自然数は $0$ を含まない」ものとする.

私は自分の数学でふだん使うとき自然数は $0$ を含む派なので, これを無視することがちょくちょくあるかもしれない. 読者のあなたは適当に気をつけてほしい.

Python 版
Haskell 版
JavaScript 版
三角関数

引数は弧度法で指定する.