Purescript (3)

いつも散歩してる道中に地域の公会堂が有るんだけど、その脇にテントが張られて、何やら 作業が行われていた。夏祭り恒例の、ねぶたもどきの臨時作業場。

今年の出し物は、アニメに出て来る猫ちゃんみたいだ。作業に参加されてる親御さんのお子さん からリクエストを募った結果だろうかね? アニメでは良く見る猫なんだけど、オイラーは生憎 その猫の名前を知らない。

骨組みが本格的。工事現場で使う鉄筋を曲げて、溶接してたぞ。きっと親御さんの中に、その 筋の人が居るのだろう。

手の空いた人が三々五々集まって、のんびり作っていたけど、毎週日曜日の夜は、ビアパーティーをやってたぞ。つまみはバーベキューとは豪勢な事だ。こうやって、地域の親睦とおとーさん 達の呑む理由を付けているんだろうね。飲み会だけ、オイラーも混ぜておくれよ。

そろそろ、その手製のねぶたも出番がやってくるな。見物に出かけてみるかな。

数独

例の数独な人の前期講習が終了したみたい。5回の講義だったそうだ。最後の会はそれぞれの 受講生が作ったプログラムの寸評会だったらしい。

まともに動くまでに持って行けた人は彼だけだったらしい。色々な問題を解かせてみましょって 事になり、 数独問題集の、一番難しいのをやらせたら、途中でハング したとか。講師の作った数種のプログラムでも解けなかったらしい。奥が深いと感嘆して ましたよ。ナンプレの雑誌を買ってきて、問題を入力してWolframに解かせているとか。

入力を効率よく行うため、OpenOfficeのEXCELでマクロを組んでいるそうな。難しい問題程 入力が減るので、1問5分ぐらいで解けると言ってたぞ。で、そのEXCEL相当のマクロを いじっていて、pythonがどーたらとか言われたらしい。無理してVBAにこだわる事はないよな。

ちなみに、彼の作った400行ぐらいのWolframプログラムを見せてもらったけど、チンプンカンプンでした。よくこんな長いプログラムを初めて触るプログラムで書くものだと水を向けると、 ほとんどは、命令のサンプルを拾ってきて繋ぎ合わせただけと、謙遜してた。

で、一体短く書こうとしたらどうなる?って思って探ってみると、

Mathematica で数独を解くプログラムが10行で書けるらしい

なんと、10行ほどで書けるらしい。MathematicaとかWolframはオイラーには敷居が高いと思う ので、他の言語ではどうかなと思い、Haskellに的を絞って調べてみた。

sudoku by haskell

あの人が分かり易い解説をしてた。

Here are a few Sudoku solvers coded up in Haskell

数独の平凡な解法(C言語)

そして、本家でも腕に自信がある人が、ドヤ顔を晒していたぞ。

関数型プログラミング

そうかと思うと、こちらは大学の講義資料になるかな。前半で関数型プログラミングについて 説明し、後半で数独を説明してた。Haskellにとっては打って付けの部門なんでしょうな。

Haskell Tutorial and Cookbook

そして、探索中に見つけた、資料本。この発行元は、Purescriptの例本と一緒の所のように 思えるぞ。Wolframと比べて資料が豊富でありがたい。

Purescript のいろいろ

今まで、目についたものを挙げておく。

Common Operators

地味だけど、大事なやつ。どこに定義が置いてあるか知れて参考になる。

24-days-of-purescript-2016

全世界を股にかけたアベンドカレンダーかな。今年も実施されるのだろうか?(ちと、気が早い!)

QuickCheck

これ本家のHaskellで、売りの一つに挙げられていたな。普通は、テストデータを作成者が 用意するんだけど、それだと無意識のうちにやばそうなデータを避ける危険性がある。 じゃ、テストデータは機械任せてしまおうってのが発端だ。

> import Test.QuickCheck
> quickCheck \n -> n + 1 == 1 + n
100/100 test(s) passed.
unit

匿名関数をテスト対象にした。nにランダム値を割り当てて、100回テストするとな。

> quickCheck \n -> n + 1 === n
/tmp/qc/.psci_modules/node_modules/Control.Monad.Eff.Exception/foreign.js:25
    throw e;
    ^

Error: Test 1 (seed 345909321) failed:
-134422 /= -134423
    at Object.exports.error (/tmp/qc/.psci_modules/node_modules/Control.Monad.Eff.Exception/foreign.js:8:10)
    at /tmp/qc/.psci_modules/node_modules/Test.QuickCheck/index.js:218:103
    at /tmp/qc/.psci_modules/node_modules/Data.Foldable/index.js:143:76
    at /tmp/qc/.psci_modules/node_modules/Data.Foldable/index.js:414:24
    at /tmp/qc/.psci_modules/node_modules/Data.Foldable/index.js:587:46
    at /tmp/qc/.psci_modules/node_modules/Data.Function/index.js:41:24
    at __do (/tmp/qc/.psci_modules/node_modules/Test.QuickCheck/index.js:217:130)
    at __do (/tmp/qc/.psci_modules/node_modules/Test.QuickCheck/index.js:254:68)
    at Object.__do [as $main] (/tmp/qc/.psci_modules/node_modules/PSCI.Support/index.js:43:64)
    at Object.<anonymous> (/tmp/qc/.psci_modules/index.js:1:88)

そして、素晴らしい事?に、purescriptのエラーまで、見つけてくれました。

PSCi in the Browser

こちらは、psciからWebに直接アクセス出来るっていう触れ込み。lxdeなDeskTop上で、firefoxを使って試したら、ちゃんとキャンバスに描画してくれたよ。

で、立ち上がってくるサーバーは、localhostなんで、Windows10上のfirefoxからはアクセス 出来ない。何とかならないかね? python君に、proxyは無いのって聞いてみたけど、そうそう 都合の良いものはデフォでは提供されていない。

proxyなんて、結構使うから、それ用のアプリがきっと有るはずと思い、探してみたよ。

socat で簡易プロキシサーバーを立てよう

socatってのが有名みたいだ。OpenBSDのportにも入っていたんで、一般的なものだろう。

deb9:graphics-vis$ pulp psci -- -p 6809
Serving http://localhost:6809/. Waiting for connections...
                    ^
		    +------------------------+
		                             ^
deb9:~$ socat -v tcp-listen:8080,fork  tcp:localhost:6809
                     ^
	      http://deb9IP:8080/        ;; on Windows10 firefox

作戦はこうだ。8080でリスンしてるproxyにWindows10からアクセスする。するとその要求が ポートフォワーディングされて、psci側で建てた、6809なポートのWebサーバーに接続 されるって仕組み。

これで動くはずなんだけど、何故かfirefoxが応答せず。-vを付けているんで、素通りする パケットがモニター出来、それによると動くはずなんだけどね。よう分からん。これぐらいに しておくかな。

本気のHaskell

Purescriptもいいんだけど、この暑い時期。調べようとすると、どうしてもパソコンに向かう 事になり、熱中症が心配なんで、涼しい所で読書する。

すごいH本を拾い読みする毎日。近頃のHaskell環境は、cabal hell を嫌って、stackに 移行してるのが特徴だな。それと、ermacsのIntero for Emacsを組み合わせるのがオイラーの流儀に なりつつある。

cabal hell を避けるためにstackが導入したのが、 Stackageだ。末永く保守されんことを望みます。 それから、stackをまともに使いたい人は、見ておくと吉ってのにも出会ったので、メモしておく。 本気で Haskell したい人向けの Stack チュートリアル

で、何か例が無いかと思ったのだ。どんなモジュールにどんな関数が定義されてるか? それはもう、googleならぬ、Hoogleで調べるのが 定番かな。検索サイトなんで、セカンドオピニオンも必要だろう。そういう向きには、 Hayooが、カウンターとして機能するな。ググルにヤッホーときたら、MS風のやつもありそうだけど、まだ見つけていない。

で、これらを使って探すのは、C語で言ったら、ヘッダーを漁って、どんな関数が有るかを 見つけ出すようなものだ。C語でコンパイルする場合、その定義の実態がライブラリィーに なってるので、正しいライブラリィーを指定出来ないと、コンパイルの成功はおぼつかない。

Haskell(ghc)の場合も事情は一緒で、関連あるモジュールをまとめたパッケージと言う 単位で、コンパイル時に指定する必要が有る。C語の時は、阿吽の呼吸で指定してたけど、 Haskellの時はどうするか?

答えは、コンパイル時のエラーを良く見ろらしい。そういう事例が上で紹介されたので、 やってみる。例題には、 第5章 パーサコンビネータ―小さなパーサを組み合わせて大きなパーサを作るを選んでみた。そしてstack環境も、 ClearLinuxだけじゃなくて、FreeBSDにも作っておくことにした。pkgになってるstackを 取ってきて、stack ghic 出来るようにするんだ。

例題は、csv.hsって名前になってるんで、src/Main.hsって名前で置いた。モジュール名も CSVからMainに変更。これで、コンパイルしてみる。(プロジェクトの土台は、stack new hack simple して作る)

[fb11: hack]$ stack build
hack-0.1.0.0: configure (exe)
Configuring hack-0.1.0.0...
hack-0.1.0.0: build (exe)
Preprocessing executable 'hack' for hack-0.1.0.0...
[1 of 1] Compiling CSV              ( src/Main.hs, .stack-work/dist/x86_64-freebsd/Cabal-1.24.2.0/build/hack/hack-tmp/CSV.o )

/usr/home/sakae/hack/src/Main.hs:4:1: error:
    Failed to load interface for `Text.Parsec'
    It is a member of the hidden package `parsec-3.1.11'.
    Perhaps you need to add `parsec' to the build-depends in your .cabal file.
    Use -v to see a list of the files searched for.

/usr/home/sakae/hack/src/Main.hs:5:1: error:
    Failed to load interface for `Text.Parsec.String'
    It is a member of the hidden package `parsec-3.1.11'.
    Perhaps you need to add `parsec' to the build-depends in your .cabal file.
    Use -v to see a list of the files searched for.

--  While building package hack-0.1.0.0 using:
      /usr/home/sakae/.stack/setup-exe-cache/x86_64-freebsd/Cabal-simple_mPHDZzAJ_1.24.2.0_ghc-8.0.2 --builddir=.stack-work/dist/x86_64-freebsd/Cabal-1.24.2.0 build exe:hack --ghc-options " -ddump-hi -ddump-to-file"
    Process exited with code: ExitFailure 1

stack build -v すれば詳細が出て来るけど、余り役に立たない。それよりも、上記エラーで、 parsec足りんぞ、ぼけ が重要。さっそくこれを hack.cabal に追加する。

[fb11: hack]$ cat hack.cabal
  :
executable hack
  hs-source-dirs:      src
  main-is:             Main.hs
  default-language:    Haskell2010
  build-depends:       base >= 4.7 && < 5
                     , parsec

最後の行に追加。ちゃんと指定されたバージョンを定義するのが筋なのかどうかは、知らない。 これで、またbuildする。

/usr/home/sakae/hack/src/Main.hs:24:21: error:
    Ambiguous occurrence `crlf'
    It could refer to either `Text.Parsec.crlf',
                             imported from `Text.Parsec' at src/Main.hs:4:1-18
                             (and originally defined in `Text.Parsec.Char')
                          or `CSV.crlf', defined at src/Main.hs:48:1

次なるエラーがお出ましだ。crlfが二重定義されてるぞ。どうするねん? こういうエラーは C語の時もよくお目にかかるな。今回は、Main側をコメントアウトしとくか。 emacsでソースを開いたら、crlfが赤くなってエラーを示していたぞ。なかなかやるな。

/usr/home/sakae/hack/src/Main.hs:1:1: error:
    The IO action `main' is not defined in module `Main'

次なるエラーは、Mainモジュールにはmainが必要でしょってやつ。C語でも、うっかりしてると 同じ事を言われる。まあ、ghcと言えども、裏側はC語ですからね。

[fb11: hack]$ stack exec hack
[["hoge","fuga"],["1234","5678"]]

これでやっと動いた。記念に変更点をまとめておく。

[fb11: hack]$ diff -u /tmp/csv.hs  src/Main.hs
--- /tmp/csv.hs 
+++ src/Main.hs 
@@ -1,4 +1,4 @@
-module CSV where
+module Main where

 import Control.Applicative ((<*),(*>))
 import Text.Parsec
@@ -44,8 +44,8 @@
 comma :: Parser Char
 comma = char ','

-crlf :: Parser Char
-crlf = cr *> lf
+-- crlf :: Parser Char
+-- crlf = cr *> lf

 lf :: Parser Char
 lf = char '\x0a'
@@ -55,3 +55,7 @@

 dquote :: Parser Char
 dquote = char '"'
+
+main = do
+  parseTest csv "hoge,fuga\r\n1234,5678\r\n"
+

今まで培ってきたC語のそれと対比させると、Haskell怖くないよってのが実感出来るな。

*Main> :show packages
active package flags:
  -package-id parsec-3.1.11-113irVHGgd88sRnywByDNw
  -package-id base-4.9.1.0
*Main> :show imports
:module +*Main -- added automatically
*Main> :show linker
----- Linker state -----
Pkgs: [parsec-3.1.11, text-1.2.2.1, binary-0.8.3.0,
       containers-0.5.7.1, mtl-2.2.1, transformers-0.5.2.0,
       bytestring-0.10.8.1, deepseq-1.4.2.0, array-0.5.1.1, base-4.9.1.0,
       integer-gmp-1.0.0.1, ghc-prim-0.5.0.0, rts-1.0]
Objs: []
BCOs: []

ghciの中で補完が効くのは地味にありがたい。上記のようにすいすいと確認出来る。プログラム上指定したのは、基本となるbaseとparsecだけだけど、リンク時に関連品をごちゃごちゃと 使ってるって事かな。

debugger on ghci

なんでも、ghciの中でdebuggerが使えるそうだ。

5.5. GHCiのデバッガ

GHCi debugger を使ってみた

試してみようとして題材を先に挙げたsudokuを10行でってのをHaskellに移植されたものに してみた。プロジェクトの中でやってみると、何も応答が無かったり、mainを実行しても 結果を表示しないという変な事になってたので、ソースだけを取り出してきて、裸のghci で実行する。

[fb11: tmp]$ stack ghc Main.hs
[1 of 1] Compiling Main             ( Main.hs, Main.o )
Linking Main ...
[fb11: tmp]$ ls
Main*           Main.hi         Main.hs         Main.o          tmux-1001/
[fb11: tmp]$ ./Main
[7,3,9,1,5,4,2,8,6]
[4,8,6,7,2,3,1,9,5]
[1,2,5,9,6,8,4,3,7]
[3,6,2,5,8,9,7,4,1]
[8,5,1,2,4,7,9,6,3]
[9,7,4,6,3,1,5,2,8]
[5,4,8,3,7,2,6,1,9]
[6,1,3,4,9,5,8,7,2]
[2,9,7,8,1,6,3,5,4]
1
True

コンパイルして、実行。次は、ghciにロードして、breakする場所を物色する。

[fb11: tmp]$ stack ghci
Configuring GHCi with the following packages:
GHCi, version 8.0.2: http://www.haskell.org/ghc/  :? for help
Loaded GHCi configuration from /tmp/ghci947/ghci-script
Prelude> :l Main
[1 of 1] Compiling Main             ( Main.hs, interpreted )
Ok, modules loaded: Main.
*Main> :browse
all2d ::
  (Foldable t, Foldable t1) => (a -> Bool) -> t1 (t a) -> Bool
replacePart :: (Int, Int) -> t -> [[t]] -> [[t]]
containingRow :: (Int, t) -> [a] -> a
containingCol :: (t1, Int) -> [[t]] -> [t]
containingBox :: (Int, Int) -> [[t]] -> [t]
search :: (Enum t, Eq t, Num t, Show t) => [[t]] -> Bool
goal :: (Show a, Num a, Eq a) => [[a]] -> Bool
report :: (Num a, Show a) => [[a]] -> Bool
deepen :: (Enum t, Eq t, Num t, Show t) => [[t]] -> Bool
candidates :: (Eq t, Enum t, Num t) => (Int, Int) -> [[t]] -> [t]
table :: [[Integer]]
main :: IO ()
*Main> :break deepen
Breakpoint 0 activated at Main.hs:21:14-76
*Main> main
Stopped in Main.deepen, Main.hs:21:14-76
_result :: Bool = _
checkall :: Foldable t1 => (t2 -> Bool) -> t1 t2 -> Bool = _
ij :: (Int, Int) = _
xss :: [[Integer]] = [[0,0,0,1,....],[4,0,6,7,....],[1,2,0,0,....],
                      [3,0,0,0,....],[0,0,1,0,....],....]

こんな引数で実行するって事だな。止まった所のソースも確認出来るとな。

[Main.hs:21:14-76] *Main> :list
20  report xss = all (\xs -> (trace.show) xs True ) xss && (trace.show.signum.head.head) xss True
21  deepen xss = checkall search [replacePart ij x xss | x <- candidates ij xss]
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
22      where

止まった所からステップ実行。deepenの結果を得るには、checkallを実行しますって事だな。 次に実行する所が ^^^^^^^ で示されているけど、GUIだと赤くなって教えてくれるとかするんかな。

[Main.hs:21:14-76] *Main> :step
Stopped in Main.deepen.checkall, Main.hs:24:25-50
_result :: Bool = _
f :: t2 -> Bool = _
xs :: t1 t2 = _
[Main.hs:24:25-50] *Main> :list
23          ij = head $ head [[(i,j) | (x,j) <- zip xs [0..] , (==0) x ] | (xs,i) <- zip xss [0..] , (any (==0)) xs ]
24          checkall f xs = all (\x -> f x || True) xs
                            ^^^^^^^^^^^^^^^^^^^^^^^^^^
25  candidates ij xss = [n | n <- [1..9] , all (notElem n) [row, box, col] ]
[Main.hs:24:25-50] *Main> :set stop :list
[Main.hs:24:25-50] *Main> :step
Stopped in Main.deepen, Main.hs:21:59-75
_result :: [Integer] = _
ij :: (Int, Int) = _
xss :: [[Integer]] = [[0,0,0,1,....],[4,0,6,7,....],[1,2,0,0,....],
                      [3,0,0,0,....],[0,0,1,0,....],....]
20  report xss = all (\xs -> (trace.show) xs True ) xss && (trace.show.signum.he
ad.head) xss True
21  deepen xss = checkall search [replacePart ij x xss | x <- candidates ij xss]
                                                              ^^^^^^^^^^^^^^^^^
22      where

セットコマンドで、止まった時に実行するコマンドを定義しておけるとな。

[Main.hs:23:14-113] *Main> :history
-1  : candidates:row (Main.hs:27:15-34)
-2  : candidates (Main.hs:25:45-53)
-3  : candidates (Main.hs:25:40-70)
-4  : candidates (Main.hs:25:31-36)
-5  : candidates (Main.hs:25:21-72)
-6  : deepen (Main.hs:21:59-75)
-7  : deepen:checkall (Main.hs:24:25-50)
-8  : deepen (Main.hs:21:14-76)
<end of history>

バックトレース出来ない代わりに、ヒストリーが参照出来るとな。

そして、一度BPを貼ってしまうと、以後BPをdeleteしても、元の状態に復帰しないようだ。 (mainを実行しても結果を表示しない)ghciを再起動して最初からやらないとダメみたい。 よって、debuggerは、最終手段と心得よ、だな。(どうも、実行結果がキャッシュされてしまい、思わぬ挙動となると推測される)

ああ、そうか。mainの実行結果がバインドされるんで、再度、右辺を実行する必要は無いって 判断か。mainに頼らず、右式をその場で入力すれば、何度でも面倒な探索・決定が実行 されるよ。

*Main> main
True
*Main> main
True
*Main> print $ search table
[7,3,9,1,5,4,2,8,6]
[4,8,6,7,2,3,1,9,5]
[1,2,5,9,6,8,4,3,7]
[3,6,2,5,8,9,7,4,1]
[8,5,1,2,4,7,9,6,3]
[9,7,4,6,3,1,5,2,8]
[5,4,8,3,7,2,6,1,9]
[6,1,3,4,9,5,8,7,2]
[2,9,7,8,1,6,3,5,4]
1
True

例で使ったsudokuを動かそうとしたら、モジュール Listが見当たらんと言われた。 そこで、 Haskell Hierarchical Librariesを探して、どの階層にあるか特定したよ。 基本的に、どこかのグループに所属してるって事だな。

Compiler,Control,Data,Debug,Distoribution,Foreign,GHC,GHCi,Graphics,Language,Numeric,Prelude,SizedSeq,System,Text,Trace,Unsafe 結構色々な分類だな。

ちょいと忘れてしまった関数名等を調べるには、下記が便利だ。まあ、ここに列挙されてる 関数ぐらいは頭の片隅に入れておくのが吉。

Haskell reference