迷路の博物館

Table of Contents

迷路の博物館

Maze generation 前回の再掲だけど、これ迷路の博物館である。

実に多様な言語で、迷路作成方法が提示されている。

博物館と言えば、思い出すのは、台北の故宮博物館だ。あそこの翡翠で出来た 白菜の彫刻は見事な できだったなあ。他では、ドイツに駐留してる時に勧められて行った、ミュン ヘンの博物館。飛行機の展示が圧巻だった。広くて疲れすぎちゃったのは、良 き思い出だ。

ミュンヘンと言えば、もうすぐオクトバーフェストだなあ。世界最大のビール 祭り。一度だけ、参加した事がある。各醸造場のテントが建ち並び、ステージ では、バンドが演奏。飲めや踊れやの乱痴気騒ぎ。つまみは、ソーセージ、子 豚の姿焼き、チキンの丸焼きとかの狩猟民族系。各 種のジャガイモ料理、ことの他嬉しかったのは、大根に塩をふっただけの素朴 なサラ ダ。但し塩はザルツブルグ製の岩塩。内陸部だから、海の塩じゃないのよ。喉 がひたすら乾くので、幾らでも食べられた。

日比谷オクトーバーフェスト2023 とかで再現実験やってるみたいだけど、何 時やっても、オクトバーフェストなんか。

で、迷路の方は、 Maze generation algorithm こう言うリンクが用意されてた。キュレーターさん、良い仕事してるな。

実に色々な言語で実装されている。オイラーの知らない言語、Rascal なんて のが有るぞ。 CommonLisp,Racket,Clojureとかの括弧言語も健在。さすがにschemeの方言で あるgaucheは無いな。

at elisp

ならば、何処にも有るemacsはどうよ。sbcl + slime なんて贅沢はありえない からね。racket,clojureしかりであります。

ああ、emacsにしてもオイラーの周辺だけか。自分基 準で考えるって、歳をくった証拠だ。まあいい、久しぶりにやってみるか。

elispはemacsの制御用の言語だけど、editorの雄であるvimからの出品は無い のか。vim scriptでは役不足? それとも、 Vim開発者のBram Moolenaar氏が死去 こういう事があったので、喪に服してる?

今や、emacsとviのeditor戦争なんて過去のもの。主戦場は、vs codeと言う新 興勢力との戦いだ。なんでも、あちらは、javascriptが核言語らしい。 JavaScriptとかnode.jsからの出品もあるぞ。拡販を図るべく、詳しい説明が 付属してた。

そういう雑事は置いておいて、 コードをざっと見すると、マッチ・ポンプってか自作自演が出来るように なってる。

頂いたコードの最終行を (generate 12 12) して、少し規模を縮小。

[sakae@arch tmp]$ emacs --script elisp-maze.el > LOG-maze

スクリプトとして実行。結果をリダイレクト。

;; (generate 12 12)
(solve "LOG-maze")

今度は、ちょいと修正。出来上がりの迷路を解決してね。

[sakae@arch tmp]$ emacs --script elisp-maze.el
+   +---+---+---+---+---+---+---+---+---+---+---+
| * |                   |       |           |   |
+   +---+---+---+   +---+---+   +---+---+   +   +
| * |               |   |   |   |   |           |
+   +---+---+---+   +   +   +   +   +   +---+   +
| * |               |       |       |       |   |
+   +---+---+---+   +---+   +   +---+---+   +---+
| *   *   *   *   *   *   *   *   *   * |       |
+---+---+---+---+---+---+---+---+---+   +---+   +
|       |                       | *   * |       |
+   +   +   +   +---+---+---+   +   +---+   +   +
|   |   |   |   |                 *   * |   |   |
+   +   +---+---+   +---+---+   +---+   +   +   +
|   |               |   |   |   |   | * |   |   |
+---+   +---+---+---+   +   +   +   +   +   +   +
|   |   |   |       |       |   |   | * |   |   |
+   +   +   +---+   +   +   +   +   +   +---+   +
|   |   |               |       |     *   *     |
+   +---+---+   +---+   +---+---+---+---+   +---+
|           |       |               | *   * |   |
+---+---+   +---+   +---+---+---+   +   +---+   +
|       |       |       |       |   | * |   |   |
+   +---+---+   +---+   +---+   +---+   +   +   +
|                               |     *   *   * |
+---+---+---+---+---+---+---+---+---+---+---+   +

2つの機能の叩き方を確認して億。

(defun solve (file-name)
  (let* ((maze (read-maze file-name))
         (solution (solve-maze maze)))
    (print-maze maze solution)))

(defun generate (rows cols)
  (let* ((m (new-maze rows cols)))
    (dig-maze m (random rows) (random cols))
    (print-maze m)))

solveが迷路探索だな。迷路地図をファイルから読み込んで、内部形式に変換。 それを使って、迷路を解決。最後に、地図に解決した経路を重ねて表示してる のだろう。

generateの方は、新らしいサイズで迷路の種を作成。それを引数にして、任意の地 点から穴掘り法で、迷路を作成。そして、結果を表示。なんとなく、やってる 事が見えてきた。ここでもlet*が使われているな。順番通りに関数を評価して、 結果をローカル変数に代入しろって奴ね。

これはプレゼントなのか?

このわざわざ、マッチ・ポンプにしてるのは何故? それはサービス精神の発 露だろう。複雑な迷路を自分で解いてみなさい。複雑度は、迷路のサイズに比 例するだろうから、自在に設定できるようにしたよ。

そして、このコードを読めたら、答を簡単に得られるよ、とな。 だから、分離してあるんだ。

昭和一桁生まれの義母は、ボケ防止で、脳トレ本をやっている。主な題材は、 昭和時代の生活の一部分を描写して、それの間違い探し。それから、ナンプレと かクロスワード・パズルなんてのも息抜きについてたなあ。

今度、迷路を持っていってやろう。そんな細かいもの見えるんか? それは大 丈夫。白内障の手術をしたら、視力が良くなりすぎて、余計な物まで見えると 自慢してたからね。

迷路も解くだけじゃ、つまらないな。 クラスタリングによる迷路作成アルゴリズム こういうのを応用して、図形が出てくるようにしたら、面白いぞ。

まちがい探しの画像生成も今なら、AIに任せられそう。クロスワードなら、言 葉を扱かう事に長けたChatGPTが適任だろう。歴史上の有名人を20名組み合わせて、 作ってくださいで、一発生成してくれそう。

ナンプレなら、haskellの良い例題になりそう。奴が提示してき たソースの間違い探しをひたすら出来るからね。前回やったのがその良い例に なってる。オイラーの知らない関数を頓着なく使ってくれるので、実戦向きだ。

M-x ielm

rubyにはirbが有るように、emacs用には、ielmが有る。

 *** Welcome to IELM ***  Type (describe-mode) or press C-h m for help.
ELISP> (cons 1 ())
(1)

思えば、elispのマニュアル付きの素晴しい環境なんだよな。遊ぶにはもってこい。 emacsを入れていれば、自然にこれが使える。世間で流行してるvs codeとやら では、こうはいかないだろう。知らないくせに、勝手な事言うな。この偏屈老 人め。

ちょいと遊んでみる。それには、コードの最後の実行部分を削除して、定義だ けにした物を用意する。そして、それを読み込み。手動でgenerate相当を実行。

ELISP> (load-file "elisp-maze.el")
t
ELISP> (defvar z (new-maze 3 4))
z
ELISP> z
#s(maze :rows 4 :cols 5 :data
         [(wall)
          (wall ceiling)
          (wall ceiling)
          (wall ceiling)
          (visited wall ceiling)
          (wall ceiling)
          (wall ceiling)
          (wall ceiling)
          (wall ceiling)
          (visited wall ceiling)
          (wall ceiling)
          (wall ceiling)
          (wall ceiling)
          (wall ceiling)
          (visited wall ceiling)
          (visited wall ceiling)
          (visited wall ceiling)
          (visited wall ceiling)
          (visited wall)
          (visited wall ceiling)])

ELISP> (print-maze z)
+   +---+---+---+
|   |   |   |   |
+---+---+---+---+
|   |   |   |   |
+---+---+---+---+
|   |   |   |   |
+---+---+---+   +

作成されたホヤホヤの迷路の下地。

ELISP> (dig-maze z 2 3)
nil
ELISP> (print-maze z)
+   +---+---+---+
|               |
+   +---+---+   +
|   |   |       |
+---+   +   +   +
|           |   |
+---+---+---+   +

t
ELISP> z
#s(maze :rows 4 :cols 5 :data
         [(visited wall)
          (visited ceiling)
          (visited ceiling)
          (visited ceiling)
          (visited wall ceiling)
          (visited wall)
          (visited wall ceiling)
          (visited wall ceiling)
          (visited)
          (visited wall ceiling)
          (visited wall ceiling)
          (visited)
          (visited)
          (visited wall)
          (visited wall ceiling)
          (visited wall ceiling)
          (visited wall ceiling)
          (visited wall ceiling)
          (visited wall)
          (visited wall ceiling)])

迷路を作成して、出来栄えを確認。ついでに内部構造も点検。 インタープリタなんで、使用感はhaskellとは雲泥の差だ。コードを理解出来 ていれば、debugger擬きな事も可能だ。

ELISP> (solve-maze z)
 *** Eval error ***  Wrong type argument: number-or-marker-p, nil

続いて迷路解決しようと思ったら、あろう事かエラーになっちまったわい。 次は、本当にdebuggerの発動か。

少し頭を冷やそう。そうそう、これセッションは久しぶりにOpenBSD上なんだ けど、入れているパッケージの関係からか、ielmの応答が返ってこなくなる。 しょうがないので、間に合わせ的に、emacs -Q で、init.elを読まないように して、急場をしのいでいる。何がバッティングしてるのだろう?

pareditのielm-mode-hookを無効にしたら、問題が解消した。便利さを犠牲に しても、emacsでreplが動く事を優先すべきだな。

elisp-slime-nav

ielmについて調べている時、面白いパッケージを発見。これを入れると、C言 語で定義されてるCoreなelisp関数やら、パッケージのソースにTag Jump出来 るようになる。勿論emacsのソースを用意してる、中毒者用である。

使い方は、こんな感じ。randomっていかにも組み込み関数だろう。そう思った ら、そこにーソルを移動させてから、M-. する。そうすると、ソースは何処っ て質問されるんで、下記のようにソースの場所を指定する。

FreeBSDでは、デフォでソースが /usr/local/share/emacs/28.2/src に鎮座してて、 この質問はスキップされた。さすが、わかってるね。

;; elisp-slime-nav
(setq find-function-C-source-directory
      (expand-file-name "/usr/local/share/emacs/28.2/src"))

これが正体だったりします。

Emacs C source dir: ~/src/emacs/src/

これで、

DEFUN ("random", Frandom, Srandom, 0, 1, 0,
       doc: /* Return a pseudo-random integer.
By default, return a fixnum; all fixnums are equally likely.
With positive integer LIMIT, return random integer in interval [0,LIMIT).
With argument t, set the random number seed from the system's entropy
pool if available, otherwise from less-random volatile data such as the time.
With a string argument, set the seed based on the string's contents.

See Info node `(elisp)Random Numbers' for more details.  */)
  (Lisp_Object limit)
{
  if (EQ (limit, Qt))
    init_random ();
  else if (STRINGP (limit))
    seed_random (SSDATA (limit), SBYTES (limit));
  else if (FIXNUMP (limit))
    {
      EMACS_INT lim = XFIXNUM (limit);
      if (lim <= 0)
        xsignal1 (Qargs_out_of_range, limit);
      return get_random_fixnum (lim);
    }
  else if (BIGNUMP (limit))
    {
      struct Lisp_Bignum *lim = XBIGNUM (limit);
      if (mpz_sgn (*bignum_val (lim)) <= 0)
        xsignal1 (Qargs_out_of_range, limit);
      return get_random_bignum (lim);
    }
  return make_ufixnum (get_random ());
}    

fns.cに収納されてるんかとフムフムすれば良い。元の場所に戻るのは、M-, だ。

(cl-defstruct maze rows cols data)

なんて言ういかにもな cl-lib に有りそうな奴も引ける。

(defmacro cl-defstruct (struct &rest descs)
  "Define a struct type.
    :

344行に渡る巨大なコードに、びっくりする。cl-macs.el.gzに収録されてるん だけど、Common Lisp macros からの輸入品。やっぱりねぇと、うなづきましょ う。

又、関数とかには、上記で見た様に、説明文書が付属してる。この文書だけを 参照したいなら、カーソルを合わせておいて、C-h f で良い。変数なら、C-h v かな。

昔、smalltalkにゾッコンな友人が居た。その人曰く、ソースが全部手元で確 認出来るだけじゃなくて、その場で改変も可能と自慢してた。オレオレ smalltalkが簡単にできちゃう。ある意味、節操のないシステム。自己責任の 世界だな。オイラーは、そこまでは要求しないけど、深部まで参照できるのは、 とっても嬉しい限りだ。

debug

17. Lispプログラムのデバッグ を参照。組み込みのdebuggerと拡張版のソースレベルdebugger が用意されてるとな。組み込み版をイネーブルにして、実行。

ELISP> (setq debug-on-error t)
t
ELISP> (solve-maze z)

これで画面が割れて、エラーに至るトレースが出現した。

Debugger entered--Lisp error: (wrong-type-argument number-or-marker-p nil)
  mod(nil 4)
  (* (mod r (progn (or (progn (and (memq (type-of maze) cl-struct-maze-tags) t)) (signal 'wrong-type-argument (list 'maze maz$
  (+ (* (mod r (progn (or (progn (and (memq ... cl-struct-maze-tags) t)) (signal 'wrong-type-argument (list 'maze maze))) (ar$
  (aref (progn (or (progn (and (memq (type-of maze) cl-struct-maze-tags) t)) (signal 'wrong-type-argument (list 'maze maze)))$
  (let ((cell (aref (progn (or (progn (and ... t)) (signal 'wrong-type-argument (list ... maze))) (aref maze 3)) (+ (* (mod r$
  maze-set(#s(maze :rows 4 :cols 5 :data [(visited wall) (visited ceiling) (visited wall ceiling) (visited ceiling) (visited $
  (while (not (equal pt exit)) (maze-set maze (car pt) (cdr pt) 'visited) (let ((exits (drop-visited maze (find-exits maze (c$
  (let (solution (exit (cons (- (progn (or (progn ...) (signal ... ...)) (aref maze 1)) 2) (- (progn (or (progn ...) (signal $
  solve-maze(#s(maze :rows 4 :cols 5 :data [(visited wall) (visited ceiling) (visited wall ceiling) (visited ceiling) (visite$
  eval((solve-maze z) t)
  :

ふうむ、余りを求める関数で、一方がnilだったとな。これらの追跡は長くな りそうなので、次回に回す事にする。

diff elisp and scheme

elispなんて真面目に取り組んだ事が無いので、オイラーの母語であるscheme との違いをChatGPTに列挙して貰った。

Emacs Lisp (Elisp)とSchemeは、どちらもLispプログラミング言語の一種ですが、異なる目的やコンテキストで使用されることが一般的です。以下は、これら2つの言語の主要な違いのいくつかです。

  1. 用途:
    • Elispは、Emacsエディタの拡張やカスタマイズに使用されます。
    • Schemeは一般的にプログラム言語として使用され、様々なアプリケーションやシステムで利用できます。
  2. 実装:
    • ElispはGNU Emacsとその派生製品に固有の言語で、Emacs内で直接実行されます。
    • Schemeは複数の実装があり、例えばGuileやChicken Schemeなどがあります。
  3. 標準ライブラリ:
    • ElispはEmacsの標準ライブラリにアクセスでき、Emacsの機能を直接操作できます。
    • Schemeは通常、独自の標準ライブラリを提供しますが、具体的なライブラリは実装によって異なることがあります。
  4. 文法:
    • ElispとSchemeの文法はいくつかの点で異なります。例えば、関数呼び出しの書き方やリストの表現方法が異なります。
  5. スコープ:
    • Elispではデフォルトでダイナミックスコープを使用します。
    • Schemeは通常、レキシカルスコープを使用します。
  6. 型システム:
    • Elispはダイナミック型付けを採用しており、変数の型は実行時に決定されます。
    • Schemeは通常、静的型付けをサポートしており、変数の型は実行前に決定されます。
  7. エコシステム:
    • ElispはEmacs関連の拡張やパッケージが豊富に存在します。
    • Schemeにも豊富なライブラリとコミュニティがありますが、その主要な用途は通常プログラム開発です。
  8. コードスタイル:
    • ElispとSchemeのコーディングスタイルは異なることがあり、特に命名規則やコードのレイアウトに違いがあります。
  9. デバッグツール:
    • ElispにはEmacs内でのデバッグをサポートするツールが組み込まれています。
    • Schemeのデバッグツールは実装に依存し、一般的にはコマンドラインツールが提供されます。
  10. ポータビリティ:
    • Schemeは言語仕様が比較的一貫しており、異なる実装間でのコードの移植性が高い傾向があります。
    • ElispはEmacsに依存するため、Emacs以外の環境での利用が難しいことがあります。

ElispとSchemeはそれぞれ異なるコンテキストで使用され、異なる設計目標を 持っていますが、どちらもLispの伝統に基づいています。


次の質問。schemeなら(define var 123)して変数を宣言。更新は(setq var 456)の一択なんだけど。elispはどうよ。

ああ、関数呼出にも大きな違いがあったはずだけ ど、また、後で。いつも近くに居るから、何時でも質問できる。

Emacs Lisp (Elisp)では、変数を定義するためにいくつかの方法があります。変数のスコープや利用場面に合わせて適切な方法を選択できます。以下にいくつかの方法を説明します。

  1. `setq`を使用する:

    • `setq`は変数に値を設定するための基本的な方法です。これを使うと、変数を定義し、既存の変数の値を更新することができます。

    ```elisp (setq var 123) ```

    この方法は、グローバルスコープの変数を定義する場合に使用します。ただし、ローカルスコープでの変数定義には適していません。

  2. `defvar`を使用する:

    • `defvar`はグローバルスコープで変数を定義するための特定の命令です。変数が既に定義されている場合でも再定義します。

    ```elisp (defvar var 123) ```

    この方法は、グローバルスコープでの変数を安全に定義する場合に適しています。

  3. `let`を使用する:

    • `let`はローカルスコープで一時的な変数を定義するために使用されます。変数は`let`ブロック内でのみ有効です。

    ```elisp (let ((var 123)) ;; ここでvarを使用できる (message "varの値は %s です。" var)) ```

    この方法は、一時的なローカル変数が必要な場合に使用します。

  4. 関数の引数として変数を受け取る:

    • 関数を定義する際に、引数として変数を受け取ることができます。

    ```elisp (defun my-function (var) ;; varを関数内で使用できる (message "varの値は %s です。" var)) ```

    この方法は、関数内でのみ変数を使用する場合に適しています。

どの方法を選択するかは、変数のスコープと寿命、必要なセマンティクスに依存します。一般的に、グローバルスコープの変数は`defvar`を使用して定義し、ローカルスコープの変数は`let`や関数の引数を使用して定義します。また、変数の再代入が必要な場合は`setq`を使用します。

schemeは変数も関数も扱いが一緒な、いわゆるlisp-1。それに対してemacsは 扱いが別々なlisp-2。その片鱗が、defvar,defunに表われているな。関数と変 数を区別する為、#fn とかやる。 #t/#f に対する、nil以外皆tってのもあるな。


This year's Index

Home