maze (2)

Table of Contents

M-x haskell-debug

前回の迷路発生器がHungUPしちゃうんで、追跡したい。haskellはコンパイル が通れば、十中八九は動くなんて言う誇大広告を出している人が居たな。 都合の悪い事も隠さずに、リアルに行きます。

コードのざっと見

これ大事。病院へ行っても、患者に向きあわないでパソコンばかりに向き合っ ている医者がいるな。

generateMazeは、起動関数。実質は、generateMaze' で、やっている。勿論、 再帰ね。再帰だと、終了条件をまずおさえる。選んだポジションがゴールだっ たら、データを整えて終了。

ポジションが範囲外だったら、やり直し。

それ以外は、次のポジションを見做ろう。例えで悪いけど、酔客の千鳥足を再 現してる。ただし、少し動物的で、自分が歩いた場所は、小便をしてマーキン グ。マーキングされた場所は避けるように振る舞う。

こうして、歌舞伎町で呑んでた親父が、あっちにフラフラこっちにグラグラし てく。都庁のゴールまで辿りつけたら終了って訳。

かの有名なアインシュタイン博士も、これを研究した。呑べえの変わりが、花 粉。それで、水の分子に衝突してランダムに動く様をね。ブラウン運動とりっ ぱな名前が付いている。

ChatGPTに質問

で、コードの中で疑問が有ったので、相棒に相談さ。

modify (\gen -> snd (next gen)) は、具体的に何をやるコードですか?

`modify` 関数は、`Control.Monad.State` モジュールで提供されている関数で、State モナド内で状態を変更するために使用されます。状態モナド内で、特定の変更関数を適用して状態を更新する際に使用されます。

ここでの `modify (\gen -> snd (next gen))` は、ランダムジェネレータの状態を更新するためのコードです。`State StdGen Maze` モナド内で、現在のランダムジェネレータの状態を取得し、次の状態を新しい状態として設定しています。具体的には、`next` 関数は新しいランダムジェネレータの状態と次のランダムな値の組を返す関数です。ここでは `snd (next gen)` を用いて、新しいランダムジェネレータの状態だけを取得しています。

このコードの目的は、状態モナド内でランダムジェネレータの状態を更新する ことです。ランダム性を導入する際には、状態モナドを使用してランダムジェ ネレータの状態を管理することがよく行われます。


すごいH本からの例。乱数発生器の種が同じだと、同一の乱数が出現する。だ から、常に種は更新しろとな。

ghci> random (mkStdGen 100) :: (Int, StdGen)
(9216477508314497915,StdGen {unStdGen = SMGen 712633246999323047 2532601429470541125})
ghci> random (mkStdGen 100) :: (Int, StdGen)
(9216477508314497915,StdGen {unStdGen = SMGen 712633246999323047 2532601429470541125})
ghci> random (mkStdGen 222) :: (Int, StdGen)
(8290090024907493913,StdGen {unStdGen = SMGen 1065517446611481277 9720658696779892243})
ghci> random (mkStdGen 222) :: (Int, StdGen)
(8290090024907493913,StdGen {unStdGen = SMGen 1065517446611481277 9720658696779892243})

change code for debugging

そのままのコードではdebuggerでの観察に不向きだ。だって、本当に見たい所 が、whereを使って、関数内関数になってるから。whereを削除して、 generateMaze' を、トップレベルに引き上げる。オイラーの趣味で別名にした。 それから、オフサイドルールに抵触しないように、最低でも2行分は、位置変 更しとく。

generateMaze :: IO Maze
generateMaze = do
    gen <- newStdGen
    let maze = evalState (zzz start []) gen
    return maze

zzz :: Point -> [Point] -> State StdGen Maze
zzz currentPos visited
        | currentPos == goal = do
            modify (\gen -> snd (next gen))
    :

後に続く行も左シフトしたい。大量な行があるな。確か一遍に移動できたはず。 C-M-q が、それなんだけど、機能しない。haskell-modeのマニュアルを参照。

そこで思わぬ嬉しい機能を発見したぞ。そう、ghci上のdebuggerは何をやるに しろ、:setpとかって、コロンを前置しなければならない。そんな無駄から開 放されるのだ。

start debug

C-c C-l to start ghci and loading module

普通にemacsを起動して、コードをコンパイル。同時にghciの窓も出す。続け て、 M-x haskell-debug も起動。ghciの窓が割られる。新らしい窓はdebuggerのコ ンパネになる。

その窓から、break pointを設定し、stepして、コードを起動する。下記は、 emacsのmini-bufferに出てくる案内と、それへの入力を示す。

press b -> Function: Debugging -> zzz -> Breaking on function: zzz
press s -> Expression to step through: -> main -> Computation paused.

後は、cするなりsするなり自由だ。n/p は、ヒストリーの確認になる。いわゆ る :forward と :back だ。凄く入力が省力化されてる。ありがたいこっちゃ。

Debianでは、ちゃんと動くんだけど、Arch/FreeBSDでは、下記のメッセージが 出現して、動かない。ちゃんとhaskell-debug.elがメンテされていない可能性 が有るな。両OS共、メジャーじゃないからね。

Computation completed without breaking. Reload the module and retry? (y or n)

気になるのは、こんなのがチラ見される事だ。暇な時lispファイルを調べてみ るか。

Unable to parse breakpoint from string: [/tmp/newmaze/app/Main.hs:(56,8)-(59,27)]

debug log

こうして実行を進めた時のログの例。裸のGHCi画面からdebuggerを起動した時 の状態に少し表示が追加されてるのが見て取れる。

Debugging newmaze

t - trace an expression, s - step into an expression, b - breakpoint
d - delete breakpoint, a - abandon context, c - continue
p - previous step, n - next step
g - refresh

Context

main - Main.hs (stopped)

do
                    nextMoveIdx <- state $ randomR (0, length unvisitedMoves - 1)
                    let nextMove = unvisitedMoves !! nextMoveIdx
                    zzz nextMove (currentPos : visited)

_result :: StateT StdGen Data.Functor.Identity.Identity Maze = _
currentPos :: Point = (1,1)
unvisitedMoves :: [Point] = (0,1) : _
visited :: [Point] = [(1,1),(2,1),(1,1)]
46                  [] -> zzz (head visited) (tail visited)
 :

  20 currentPos : visited
  19 `notElem` visited
  18 snd currentPos
   :
   2 snd currentPos
   1 snd currentPos + 1

Breakpoints

0 - Main (28:11)
1 - Main (28:32)
2 - Main (36:11)
3 - Main (36:72)
4 - Main (39:23)

Modules

Main - Main.hs

結果

上の例を見ていて、はたと気がついた。ポジションに(0,1)って、そりゃ無い よな。ありえない値だ。その発生源は?

nextMoveIdx <- state $ randomR (0, length unvisitedMoves - 1)

乱数の範囲が0からって、それは非常に悪い設定だ。普通は、1からでしょ。変 更してみたら、曲がりなりにも動く場合が出てきた。

ghci> :main
生成された迷路:
 *** Exception: Prelude.!!: index too large
ghci> :main
生成された迷路:
^CInterrupted.   -- HungUpしたので強制停止
ghci> :main
生成された迷路:
..########
#..#######
##..######
###.######
###....###
######..##
#######.##
#######..#
########..
##########

Exceptionってのは、新宿署の鮫島警部に捕縛されましたって事かな。ループ しちゃって、ゴールに辿りつけないのは、新宿公園やら新宿御苑あたりを永遠 とさ迷い続けてるんだな。ああ、鮫島警部を知らない人は、 こちら を参照。

もう、十分に堪能したんで、次なる手。

ChatGPTで詐欺にあった

別な手を提示してって指示すると、 mazes-of-valhalla をcabalから入れて、次のコードを実行しろと、、

import Data.Grid
import Data.Maze.Builder
import Data.Maze.Render
import System.Random
  :

自信たっぷりです。じゃ、実行。

[sakae@arch tmp]$ cabal install mazes-of-valhalla
cabal: Unknown package "mazes-of-valhalla".

そんなの無いじゃん。カリフォーニアを震源地とする、国際詐欺です。有りも しない徳川の埋蔵金を採掘しませんかと、地図を提示してだまかす手口ですよ。

maze.c to Main.hs

更なる次の手。奥村先生のアルゴリズム辞典に掲載されてた、迷路をやっても らおう。一応、動作確認。

[sakae@deb src]$ ./a.out
###########
..#.......#
#.#.###.#.#
#.#.#.#.#.#
#.###.###.#
#.....#...#
###.#.#.#.#
#.#.#.#.#.#
#.###.###.#
#..........
###########

問題ないので、作成指示。こういう事をやってる人って遭遇した事ないんだけ ど、需要は無いのかしら。レガシー遺産であるcobolをjavaに変換するっての は聞いた事あるけど、あれは専用のトランスレーターだったしなあ。 今だと、そんなのchatGPTに任せられるよ。

迷路作成プログラム maze.c と同等機能をhaskellで出力して

出来たコードは、またまた永久ループ品でしたよ。

System.Randomのちょっとした事

上で乱数発生を試した時、種が2つのセットになってた。なんで? Random.hsにこんなコメントが掲載されてた。

--     'StdGen', the standard pseudo-random number generator provided in this
--     library, is an instance of 'RandomGen'. It uses the SplitMix
--     implementation provided by the
--     <https://hackage.haskell.org/package/splitmix splitmix> package.
--     Programmers may, of course, supply their own instances of 'RandomGen'.

んで、splitmixを見る(SplitMix.hs)。

-- Guy L. Steele, Jr., Doug Lea, and Christine H. Flood. 2014.
--  Fast splittable pseudorandom number generators. In Proceedings
--  of the 2014 ACM International Conference on Object Oriented
--  Programming Systems Languages & Applications (OOPSLA '14). ACM,
--  New York, NY, USA, 453-472. DOI:
--  <https://doi.org/10.1145/2660193.2660195>

Guy L. Steele, Jr さんって、CommonLispの設計に関与、そしてschemeの発明 者という有名な方。今度は乱数に首を突っこんできなさった。

乱数の種を割ると言うか複製に都合が良い様に設計したとな。メニコア時代の 要請なんだな。Java,PHP,javascriptとかでも利用されてるとな。

nextWord64 :: SMGen -> (Word64, SMGen)
nextWord64 (SMGen seed gamma) = (mix64 seed', SMGen seed' gamma)
  where
    seed' = seed `plus` gamma

そして乱数自体の発生も高速性が要求されるんで、軽い演算を採用。勿論、簡 易な発生方法なので、暗号とかの機密が要求される場面で使うのは禁止。 でも、カジュアルに乱数が欲しい場合には、どんどん利用してね。

種の生成方法が掲載されてた。どこかの モンサント みたいに、種を特許にしてないのが良いな。人類の発展に貢献ですよ。

-- >>> readMaybe (show (mkSMGen 42)) :: Maybe SMGen
-- Just (SMGen 9297814886316923340 13679457532755275413)
--
instance Read SMGen where
    readsPrec d r =  readParen (d > 10) (\r0 ->
        [ (SMGen seed gamma, r3)
        | ("SMGen", r1) <- lex r0
        , (seed, r2) <- readsPrec 11 r1
        , (gamma, r3) <- readsPrec 11 r2
        , odd gamma
        ]) r

オイラーの目も、やっと内包表記にパターンマッチする様になったな。にして も、芸術的な記述だな。


This year's Index

Home