星田さんの記事に対するコメント。
まず最初に。重大な事を。
言語が変わっとる・・・・・・。
Lazy Racketと言う言語は、Haskellみたいな「遅延評価ベース」のプログラミング言語(Scheme)で、実験実装ですね。
それ使ってるから
ちょっと動くのかどうか調べようと思って仮の引数を入れたんですけど・・なんだこれは・・? delay・・?これが噂の遅延評価って事なのか?引数は色々と変えてみたんですけど全部これ。
そうなってるの(笑)。
LanguageをフツーのRacketに戻しましょう(笑)。
スクショ撮ってくれてて良かったです(笑)。
さて。
以前からよく聞く「名前空間」に関係するんだろうけど問題は「別にこう書いてもSchemeで動くのか」だけど。まあ、そのうち動かしておかしかったら普通に関数名だけにするとして#'は残しておこうか。
全くその通り。名前空間の問題です。ANSI Common LispはLisp-2。Scheme/RacketはLisp-1です。
言い換えると、ANSI Common Lispは名前空間が最低でも変数用と関数用に分かれている。だから「関数名」としてシンボルを指定したい時、(function ...)で指定しないとならない(ショートカットが#'です)。
一方、Scheme/Racketは名前空間は全体で一つしかないです。
従って、#'(シャープクオート)は要りません。
(define (describe-paths location edges)
(apply append (map describe-path (cdr (assoc location edges)))))
だからこれで構いません。
全般的にはこうなるでしょう。
#lang racket
(define *nodes* '((living-room (you are in the living-room.
a wizard is snoring loudly on the couch.))
(garden (you are in a beautifug garden.
there is a well in front of you.))
(attic (you are in the attic.
there is a giant welding torch in the corner.))))
(define (describe-location location nodes)
(cadr (assq location nodes)))
(define *edges* '((living-room (garden west door)
(attic upstairs ladder))
(garden (living-room east door))
(attic (living-room downstairs ladder))))
(define (describe-path edge)
`(there is a ,(caddr edge) going ,(cadr edge) from here.))
(define (describe-paths location edges)
(apply append (map describe-path (cdr (assq location edges)))))
一つTips。
ANSI Common Lispは総称的(generic)な設計が成されています。
総称的、ってのはどんなデータ型相手だろうと、同じような目的の為には一つの関数で対応する事です。
CL-USER> (defparameter *ls* '(1 2 3 4 5)) ;; リストを定義
*LS*
CL-USER> (length *ls*) ;; リストの長さを調べるにはlengthを使う
5CL-USER> (elt *ls* 2) ;; リストの二番目の要素を返すにはeltを使う
3
CL-USER> (defparameter *arr* #(1 2 3 4 5)) ;; 配列を定義
*ARR*
CL-USER> (length *arr*) ;; 配列の長さを調べるのにもlengthを使う
5
CL-USER> (elt *arr* 3) ;; 配列の三番目の要素を返すのにもeltを使う
4
CL-USER>
一方、Racket/Schemeはそうじゃない。一つの関数に複数のデータ型を対応させる、と言うような設計方法は基本取ってないのです。
> (define *ls* '(1 2 3 4 5)) ;; リストを定義
> (length *ls*) ;; lengthはリスト専用の長さを測る関数
5
> (list-ref *ls* 2) ;; list-refはリスト専用の要素を返す関数
3
> (define *arr* #(1 2 3 4 5)) ;; ベクタを定義
> (vector-length *arr*) ;; vector-lengthはベクタ専用の長さを測る関数
5
> (vector-ref *arr* 3) ;; vector-refはベクタ専用の要素を返す関数
4
>
一見、Racket/Schemeは面倒臭いですね。データ型にそれぞれ「専用関数」が定義されてるし、違うデータ型に適用すれば当然エラーとなります。
ただし、インタプリタで話すと、Schemeの方が「速い」「軽い」です。
と言うのも、「引数に与えられたデータの型」を調べる必要がないから、です。
ANSI Common Lispは一見便利なんですが、最初に与えられたデータの型を調べないとならない。
結果、Schemeに比べると「判別にどうしても時間がかかる」ので、実行速度的には不利なんです。
ただ、仕様上、ANSI Common Lispは「必ずコンパイラを持ってないとならない」。
言い換えるとどんなANSI Common Lispのプログラムでも「コンパイルされる」のが前提で、その時点で型が決まったりするケースが多いんですよね。プログラマが型を指定してコンパイルする事も可能だし、最適化を施せば結果C++辺りと遜色がない速いコードを吐き出す事も可能です。
要するに、ANSI Common Lispは「コンパイルありき」の設計になっていて、その辺が仕様上コンパイラを持たないSchemeとの違いで、言い換えると、インタプリタを使ったインタラクティブな開発時点での「速度に関するデメリット」は大した問題じゃないだろう、と判断されてるようです。
んで何でこんな話をしたか、と言うと。
ANSI Common Lispのassocって関数も総称的な関数なんですよ。
一方、Racket/Schemeだとassoc系の関数が3つあって、「等価性判定用関数」が違うんです。
- assq -> 等価性判定にeq?を使う
- assv -> 等価性判定にeqv?を使う
- assoc -> 等価性判定にequal?を使う
上に行くほど比較スピードは速いんですが、使える型が限定的、になっていく。
一方、assocはANSI Common Lispのデフォルトでのassocとほぼ同等な機能です。
んで、このプログラムのように、「シンボル相手に検索する」のが明らかな場合は、assqを使うべきです。理由は「その方が速い」から。
と言うより、Lispでは基本的には、検索型のデータ構造を作成する場合、Land of Lispが見せてるように、キーをシンボルにするのがまず無難なんです。
シンボルだと等価性判定をする場合、ポインタ比較、と言う高速な比較法が適用される為、連想リストのキーとして使うには都合が良いのです。結果、Racket/Schemeだとassqを使う率が必然的に高くなるのです。
まぁ、もちろん、もっと先ではハッシュテーブル(Pythonでの辞書型)を使うようになっていくんでしょうけどね。