2017年10月31日火曜日

2017年10月30日月曜日

手続き的なscanlの説明


手続き的なscanlの説明 foldの動作をxへの代入、再代入と捉えると分かりやすくなります。 直接foldの動きを見るのではなく、 まずscanlの動作を見てみます。 scanlの型は Prelude> :t scanl scanl :: (a -> b -> a) -> a -> [b] -> [a] となっています。 scanlは、2引数の関数と初期値と何らかのリストを引数にとり、リストを返す関数です。 実際に動かしてみましょう。 Prelude> scanl(\x y -> x + y) 0 [1..3] [0,1,3,6] scanlが内部で何をやっているのか想像してみます。 まずscanlが呼び出されたとき、変数xに初期値、(この場合は「0」)を代入します。 次に変数yにリストの最初の要素(この場合は「1」)を代入します。 次にx+yを計算して結果の「1」をxに再代入します。 次にyにリストの次の要素「2」を代入し、x+yを計算します。この時xは1になっていますので, 結果は「3」です。これをxに再代入します。 次にyにリストの次の要素「3」を代入し、x+yを計算します。この時xは3になっていますので,結果は「6」です。これをxに再代入します。 リストの終わりに到達したので、リスト[0,1,3,6]を出力して終了します。 scanlはxに値が代入された時はすべてその値をリストに保存するようです。 こう見ますとfoldlはscanlと動作は同じで終了時にxの値のみを出力して終了する、と捉えることができます。 Prelude> :t foldl foldl :: (a -> b -> a) -> a -> [b] -> a foldlの型です。 同様にscanrの動作もみてみます。 scanrはxとyの役割が逆転しており、リストの作り方も異なっています。 Prelude> scanr (\x y -> x / y) 2 [3..5] [1.875,1.6,2.5,2.0] pythonで動きを追ってみると >>> x=5. >>> y=x/y >>> y 2.5 >>> x=4 >>> y=x/y >>> y 1.6 >>> x=3 >>> y=x/y >>> y 1.875 代入、再代入はyに対しておこなわれます。 yに代入があった時、リストの先頭に値を保存します。 Prelude> 1.875:1.6:2.5:2.0:[] [1.875,1.6,2.5,2.0] haskellはソースコード上での、変数への値の再代入を許していません。 そのため繰り返しのための構文が用意されていません。 それが可能な言語、例えばPythonでは次のようなコードが書けます。

x=0
for y in [1,2,3]: 
 x=x+y  print x
これは上記の説明のfoldと全く同じです。 つまりfoldは一般的な言語の繰り返し処理をHaskellで書くための一つの方法になっているわけです。

(PythonのFor文は他の凡百の言語のFor文とはまったく異なります。どちらかというとMapに近い。ただ書き換えのできるグローバル変数も使えるので非常に自由度が高い。)




fold を手続き的にとらえると考えやすくなる処理があります。

Data.List モジュールには insert という関数が用意されています。

Prelude> import Data.List
Prelude Data.List> :t insert

insert :: Ord a => a -> [a] -> [a]

ソート済みのリストに一つ要素を加えてソート順を崩さずに新たなリストを返す関数です。
他の関数が一般的なリスト処理の関数なのに比べてやや特殊な感じがあります。
たぶん「挿入ソートを実装してください」というメッセージだと思います。
挿入ソートは再起でも書けるとは思うのですが、いきなり fold を使ったほうがわかりやすい。

Prelude Data.List> foldl (\x y -> insert y x) [] [2,7,4,0]
[0,2,4,7]

まず初期値は空リストです。
x に 代入されます。
y には リストの第一要素が代入されます。
x y をひっくり返して insert 関数を適用します。
結果 x に [2] が y に 7 が代入されます。
7 を [2]  に insert し [2,7]  ・・・・(略)

この操作をリストの終わりまで繰り返します。 これでソート完了。

flip を使って書き直すと

Prelude Data.List> foldl (flip insert) [] [2,7,4,0]
[0,2,4,7]

さらに

Prelude Data.List> foldr insert [] [2,7,4,0]
[0,2,4,7]

foldl と foldr では動作が異なりますが、この場合は結果は同じです。



も参照ください。

フィボナッチ数



f6 の実行例

*Main> last $ take 10 f6
[55,34,21,13,8,5,3,2,1,1,0]

これは

foldl (\x@(a:b:_) _ -> (a+b):x) [1,0] [0..8]

と同じです。

フィボナッチ数列の隣り合う 2 項の比は黄金比に収束Wします。

$ runghc fibo2.hs | runghc gr.hs

~
~
~
1.618033988749895
1.618033988749895
1.618033988749895
~
~
~
Ctrl-Cを押して終了。

2017年10月28日土曜日

シェルピンスキーのギャスケット


pbmファイルでシェルピンスキーのギャスケットを描きます。
まずパスカルの三角形をつくります。



$ runghc -XParallelListComp pascal.hs 3
[1]
[1,1]
[1,2,1]
[1,3,3,1]
[1,4,6,4,1]
[1,5,10,10,5,1]
[1,6,15,20,15,6,1]
[1,7,21,35,35,21,7,1]


奇数部を1、偶数部を0に置き換えます。

$ runghc -XParallelListComp sierpinski.hs 10 | display

描画には10数秒かかります。

コンパイルすればほぼ一瞬で描画するようになります。



http://senjounosantafe.blogspot.com/2019/02/blog-post.html

上記のページに

Python のコードを出力し
Turtle グラフィックスで
シェルピンスキーのギャスケットを再帰的に描画するHaskellのコードを置いています。


また1次元のセル・オートマトンにルール90を適用することでもシェルピンスキーのギャスケットを描画できるそうです。




実行例
$ runghc ca.hs




main を

main =  loop0 fs 31 $ reverse "11100001"

と書き換えるとルール225を描画します。



Intから2進数の文字列に変換するには
「2進、8進、10進、16進の各表現を相互に変換する」のページ

https://github.com/haskell-jp/recipe-collection/blob/master/%E6%95%B0%E5%80%A4/2%E9%80%B2%E3%80%818%E9%80%B2%E3%80%8110%E9%80%B2%E3%80%8116%E9%80%B2%E3%81%AE%E5%90%84%E8%A1%A8%E7%8F%BE%E3%82%92%E7%9B%B8%E4%BA%92%E3%81%AB%E5%A4%89%E6%8F%9B%E3%81%99%E3%82%8B.md


を参考に。

具体的には

ghci> import Numeric
ghci> import Data.Char
ghci> showIntAtBase 2 intToDigit 123 "" -- 123を2進数表記
"1111011"

ただし8桁になるように先頭にゼロを埋めること。

"1111011" -> "01111011"



参考ページ
Elementary Cellular Automata

http://atlas.wolfram.com/01/01/



その他のルールも適用可能にし精細な画像を描くにはこちら。

http://hhg2the-haskell.blogspot.com/2019/02/blog-post.html

マンデルブロ集合

pbmファイルに書き出してマンデルブロ集合を描きます。(画像サイズは1000x1000に固定しています。)

コンパイルします。

$ ghc -O2 -threaded -rtsopts --make -XFlexibleContexts -eventlog mandel.hs
$ ./mande -2 2 2 3000 +RTS -N2 -l | display

displayコマンドにパイプします。
動作を確認。
$ threadscope mamdel.eventlog

殆どの時間2Coreで動いています。


Data.Array.Repa.IO.BMP

モジュールには

readImageFromBMP :: FilePath -> IO (Either Error (Array U DIM2 (Word8, Word8, Word8)))

writeImageToBMP :: FilePath -> Array U DIM2 (Word8, Word8, Word8) -> IO ()

という関数がありBMPファイルの読み書きができる。


画像サイズもコマンドラインから与えます。


$ ./mandel2 -2 2 2 1000 3000 +RTS -N2 -l


1次元の配列に対し map し、 reshape すればよいので、コード自体はわかりやすい。
index のTable (Int のタプルの配列)を作るのに時間がかかっているようなので、
配列を連結するやり方も試みた。



また  mandel2.hsにおいてIndexをIntではなくWord16に置き換えてみたが効果は微妙だった。

世の中にはGPUを使ってマンデルブロー集合を極めて高速に描画するプログラムもあるらしい。

2017年10月27日金曜日

画像ビューアー




この画像(IMGP2850.JPG)を表示します。

ただこのやり方だと元画像の大きさそのままで表示されます。
サイズを変更して表示するには一旦Pixbufのインスタンスをつくってから
Buttonに貼り付けます。



かんたんな画像のビューアーをつくります。 まずディレクトリーを選択するダイアログ。



dirというファイルにリダイレクトしておきます。

$ runghc dirchooser.hs > dir

ビューアー

ただ表示するだけではあまりにも芸がないのでちょっとした操作をしてみます。

ソースコード

まず補助的関数を集めたMyImageモジュール


一括処理を選択します。



画像の回転指定



確認



MyTable モジュール

最後にConvertコマンドを実行します。



スクリプトは

runghc dirChooser.hs > dir
runghc select.hs > tmp
cat tmp | runghc image.hs > tmp1
cat tmp1 | runghc confirm.hs > tmp2
cat tmp2 | runghc mkCom.hs

アイコンが必要です。

keep.png  left.png  right.png  updown.png




gtk2hs


gtk2hsのインストールは

sudo apt install libghc-gtk-dev

先にGHCを単独でインストールしてしまうとgtk2hsを加えるときに非常に時間がかかります。
Gtk2hsを使う予定がなくてもいきなり入れてしまったほうが良い。
もちろんこれでGHCも使えるようになります。



このコードを実行してウィンドウが表示されればインストールは成功です。

myPlayer

-- pipe.hs import System.Process import System.Environment main :: IO () a:_ IO [FilePath] randomize lst = do let c = length lst ...