いまさらマンデルブロー集合を書くので、なるべく型安全っぽくを意識した
プログラミングRustをちらちら読んでいたら並列化の例としてあったので、いまさらマンデルブロー集合を実装した。
元のコードがRustなのでなるべく型安全な感じに書いてみたつもり。
- 他の言語でfind相当のものがcountUntilという名前で、findは別の操作を行う関数だった。紛らわしい。
- PNGファイルの出力はimageformatsというライブラリを使った。ヘッダから自動で画像形式を推定してくれる、手軽に使えるので便利。
- nogcというライブラリを使えば型変換をnogcでできるようだが、結局
write_image
がGC必要とするので用いなかった。 - tupleの中の要素はinoutと組み合わせられないのか、
stack based variables can be inout
だそう。
ソースコードは以下。
import core.stdc.stdlib : exit; import std.complex; import std.conv : to, ConvException; import std.parallelism : parallel; import std.range : chunks; import std.stdio; import std.typecons : Nullable, Tuple, tuple; import imageformats : write_image; Nullable!uint escapeTime(uint limit)(Complex!double c) @nogc nothrow pure @safe { auto z = complex(0.0, 0.0); foreach (i; 0 .. limit) { z = z * z + c; if ((z.re * z.re + z.im * z.im) > 4.0) return typeof(return)(i); } return (typeof(return)).init; } alias Pair(T) = Nullable!(Tuple!(T, T)); Pair!T parsePair(T, char separator)(string s) pure @safe if (__traits(isArithmetic, T)) { import std.algorithm : countUntil; immutable index = s.countUntil(separator); if (index == -1) return (typeof(return)).init; try { T l = s[0 .. index].to!T; T r = s[index + 1 .. $].to!T; return typeof(return)(tuple(l, r)); } catch (ConvException) return (typeof(return)).init; } @safe unittest { assert(parsePair!(int, ',')("").isNull); assert(parsePair!(int, ',')("10,").isNull); assert(parsePair!(int, ',')("10,20").get() == tuple(10, 20)); assert(parsePair!(int, ',')("10,20xy").isNull); assert(parsePair!(double, 'x')("0.5x").isNull); assert(parsePair!(double, 'x')("0.5x1.5").get() == tuple(0.5, 1.5)); } Nullable!(Complex!double) parseComplex(string s) pure @safe { immutable pair = parsePair!(double, ',')(s); if (pair.isNull) return (typeof(return)).init; return typeof(return)(complex(pair[0], pair[1])); } @safe unittest { assert(parseComplex("1.25,-0.0625").get() == complex(1.25, -0.0625)); assert(parseComplex(",-0.0625").isNull); } Complex!double pixelToPoint(Tuple!(size_t, size_t) bounds, Tuple!(size_t, size_t) pixel, Complex!double upperLeft, Complex!double lowerRight) @nogc pure nothrow @safe { immutable width = lowerRight.re - upperLeft.re; immutable height = upperLeft.im - lowerRight.im; return complex(upperLeft.re + pixel[0] * width / bounds[0], upperLeft.im - pixel[1] * height / bounds[1]); } @nogc @safe unittest { assert(pixelToPoint(tuple!(size_t, size_t)(100, 100), tuple!(size_t, size_t)(25, 75), complex(-1.0, 1.0), complex(1.0, -1.0)) == complex(-0.5, -0.5)); } void render(ref ubyte[] pixels, Tuple!(size_t, size_t) bounds, Complex!double upperLeft, Complex!double lowerRight) @nogc @safe in { assert(pixels.length == bounds[0] * bounds[1]); } do { foreach (row; 0 .. bounds[1]) { foreach (column; 0 .. bounds[0]) { immutable point = pixelToPoint(bounds, tuple(column, row), upperLeft, lowerRight); immutable count = escapeTime!(255)(point); pixels[row * bounds[0] + column] = count.isNull ? 0 : cast(ubyte)(255 - count.get()); /* ensure count.get <= 255 */ } } } void writeImage(string filename, const ubyte[] pixels, Tuple!(size_t, size_t) bounds) { write_image(filename, bounds[0], bounds[1], pixels); } version(unittest) { void main() {} } else { void main(string[] args) { if (args.length != 5) { stderr.writeln("Usage: mandelbrot FILE PIXELS UPPERLEFT LOWERRIGHT"); stderr.writefln("Example %s mandel.png 1000x750 -1.20,0.35 -1,0.20", args[0]); exit(1); } immutable bounds = parsePair!(size_t, 'x')(args[2]).get(); immutable upperLeft = parseComplex(args[3]).get(); immutable lowerRight = parseComplex(args[4]).get(); auto pixels = new ubyte[bounds[0] * bounds[1]]; auto bands = pixels.chunks(bounds[0]); foreach (top, band; parallel(bands)) { auto bandBounds = tuple(cast() bounds[0], 1UL); immutable bandUpperLeft = pixelToPoint(bounds, tuple(0UL, top), upperLeft, lowerRight); immutable bandLowerRight = pixelToPoint(bounds, tuple(cast() bounds[0], top + 1UL), upperLeft, lowerRight); render(band, bandBounds, bandUpperLeft, bandLowerRight); } writeImage(args[1], pixels, bounds); } }
追記
imageformatsのnogc版のimagefmtというライブラリがあった。
https://github.com/lgvz/imagefmt
これを使ってちょっとリファクタリングしたコードもはっておく。
import core.stdc.stdlib : exit; import std.complex; import std.conv : to, ConvException; import std.parallelism : parallel; import std.range : chunks; import std.stdio; import std.typecons : Nullable, Tuple, tuple; import imagefmt : write_image; Nullable!uint escapeTime(uint limit)(Complex!double c) @nogc nothrow pure @safe { auto z = complex(0.0, 0.0); foreach (i; 0 .. limit) { z = z * z + c; if ((z.re * z.re + z.im * z.im) > 4.0) return typeof(return)(i); } return (typeof(return)).init; } alias Pair(T) = Nullable!(Tuple!(T, T)); Pair!T parsePair(T, char separator)(string s) pure @safe if (__traits(isArithmetic, T)) { import std.algorithm : countUntil; immutable index = s.countUntil(separator); if (index == -1) return (typeof(return)).init; try { T l = s[0 .. index].to!T; T r = s[index + 1 .. $].to!T; return typeof(return)(tuple(l, r)); } catch (ConvException) return (typeof(return)).init; } @safe unittest { assert(parsePair!(int, ',')("").isNull); assert(parsePair!(int, ',')("10,").isNull); assert(parsePair!(int, ',')("10,20").get() == tuple(10, 20)); assert(parsePair!(int, ',')("10,20xy").isNull); assert(parsePair!(double, 'x')("0.5x").isNull); assert(parsePair!(double, 'x')("0.5x1.5").get() == tuple(0.5, 1.5)); } Nullable!(Complex!double) parseComplex(string s) pure @safe { immutable pair = parsePair!(double, ',')(s); if (pair.isNull) return (typeof(return)).init; return typeof(return)(complex(pair[0], pair[1])); } @safe unittest { assert(parseComplex("1.25,-0.0625").get() == complex(1.25, -0.0625)); assert(parseComplex(",-0.0625").isNull); } Complex!double pixelToPoint(T)(Tuple!(T, T) bounds, Tuple!(T, T) pixel, Complex!double upperLeft, Complex!double lowerRight) @nogc pure nothrow @safe { immutable width = lowerRight.re - upperLeft.re; immutable height = upperLeft.im - lowerRight.im; return complex(upperLeft.re + pixel[0] * width / bounds[0], upperLeft.im - pixel[1] * height / bounds[1]); } @nogc @safe unittest { assert(pixelToPoint!int(tuple(100, 100), tuple(25, 75), complex(-1.0, 1.0), complex(1.0, -1.0)) == complex(-0.5, -0.5)); } void render(T)(ref ubyte[] pixels, Tuple!(T, T) bounds, Complex!double upperLeft, Complex!double lowerRight) @nogc @safe in { assert(pixels.length == bounds[0] * bounds[1]); } do { foreach (row; 0 .. bounds[1]) { foreach (column; 0 .. bounds[0]) { immutable point = pixelToPoint!T(bounds, tuple(column, row), upperLeft, lowerRight); immutable count = escapeTime!(255)(point); pixels[row * bounds[0] + column] = count.isNull ? 0 : cast(ubyte)(255 - count.get()); /* ensure count.get <= 255 */ } } } int writeImage(T : int)(string filename, const ubyte[] pixels, Tuple!(T, T) bounds) nothrow @nogc { return write_image(filename, bounds[0].to!int, bounds[1].to!int, pixels); } version(unittest) { void main() {} } else { int main(string[] args) { if (args.length != 5) { stderr.writeln("Usage: mandelbrot FILE PIXELS UPPERLEFT LOWERRIGHT"); stderr.writefln("Example %s mandel.png 1000x750 -1.20,0.35 -1,0.20", args[0]); exit(1); } immutable bounds = parsePair!(int, 'x')(args[2]).get(); immutable upperLeft = parseComplex(args[3]).get(); immutable lowerRight = parseComplex(args[4]).get(); auto pixels = new ubyte[bounds[0] * bounds[1]]; auto bands = pixels.chunks(bounds[0]); foreach (top, band; parallel(bands)) { auto bandBounds = tuple(cast() bounds[0], 1); immutable bandUpperLeft = pixelToPoint(bounds, tuple(0, top.to!int), upperLeft, lowerRight); immutable bandLowerRight = pixelToPoint(bounds, tuple(cast() bounds[0], top.to!int + 1), upperLeft, lowerRight); render(band, bandBounds, bandUpperLeft, bandLowerRight); } return writeImage(args[1], pixels, bounds); } }
zshを自前でビルドして使う
zshにパッチあてて使ってるんだけど、ビルド方法とか一応メモしておく。
zshのビルドは楽にできる、まあ今回の作業では必要なかったのだけれど。
$ apt install -y yodl # 依存ライブラリ, あとPerlとかも必要 $ ./Util/preconfig $ ./configure $ make
パッチ自体は特にとりあげるほどの内容ではないので割愛。
補完にパッチをあてた場合は、fpathを上書きしてそっちを優先的にみるようにする必要がある。 設定ファイルは邪魔なので読み込まないようにした。
$ zsh -d -f # no-rcs && no-global_rcs $ fpath=("$(pwd)"/Completion/Unix/Command "${fpath[@]}") $ autoload -Uz compinit $ compinit -C $ nm --demangle= # 補完で `dlang' が出てくるパッチの確認
一応パッチ自体はPR出した。マージされるかわからないけど, わりとすんなり修正できるのでみなさんもzshにパッチ投げてみるといいかもしれません。 (そのあとちゃんとマージされたのでコントリビュッタです)
jailingをD言語にポーティング、やってみた!
kazuho/jailing をD言語に置き換えてみました。
これです。
目的
実用的にはLinuxであればどの環境でもPerl5が入っていることは期待できるし外部依存もないので特に意義はないです。 あくまで学習目的というやつです。コードサイズも小さいし、いいかなって。
jailing
説明はここを読んで。
もう少しカスタムできるツールだとfirejailだとかnsjailとかあるけど、このくらいの分量だと学習しやすくてありがたいです。 というかこれだけで立派なchroot+jailになるのいいですね。
D言語
Q: なんでD言語なの? A: 言わせんな恥ずかしい
書いてみての雑感。
- わりと標準ライブラリでなんとかなるところがよい?でもけっきょく
executeShell
ばっかりになるもんなあという気もしないではない。 - あとはなんか
std.path.buildPath
なんかはきっちり書きたい感出せる気がする。 - あ、FFIは
extern (C)
だけすればなんとかなるのでだいぶよい。
D言語で書けばバイナリを配るだけ作戦が使える、のだけど元がPerlやPythonのスクリプトの場合だと書き換え自体がメリットになりにくい。 jailingのようなツールだとそんなに速度とかいらなさそうだし、どうせプロセス起動が遅いのでさほど変わらんだろう。 Rubyとかだったらスクリプトと別に処理系入れるの面倒じゃない?みたいなところに対してメリットが言えそうなんだけど。 なおこの作戦はGoやRustでも別によいという話もある。
jl
だいたい似たような動機のプロジェクトで yoshitsugu/jl というRustのやつがありました。
Rustだと caps とか libmount とかライブラリがあったりして外部コマンドを使わないで実装できるのはいい感じがします。実行時のメモリフットプリントとかにうれしい。 あと clap みたいなコマンドラインパーサがあるとCLI書くとき便利。 ここでは使われていないけど他にもstructoptとかも便利系のやつです。
ところでjlはコマンド実行も外部プロセスにしてしまってるけどここは CommandExt
の exec
関数を使うべきでは。
ベイルート・ロンドン・リバプール旅行記
3/25~4/2でベイルート(レバノン)、ロンドン・リバプール(イギリス)旅行に行っていたので雑に記録を残しておく
ベイルート
- 直行便がないのでフランクフルト経由。フランクフルトのラウンジで食べたパン・ソーセージとビールがこの旅で一番うまかった可能性がある。
- 到着後もろもろの事情で国防軍の出迎え&謎の力ですんなり滞在ビザを取得できてしまった。
- 滞在ビザは長蛇の列で質問も結構長くやってたぽいので助かった。
- ホテルはめっちゃいいホテル
- やたらおいしいチョコレートが置いてあった
- ベイルート市内からちょっと離れてるけど景色も綺麗だった
- 近辺を散策していると生々しい銃痕の痕が。現在でも緊迫した状況を感じる。
- ホテルで朝食をとっているとどんどんアラブ料理を勝手に運んできてくれてとてもよかった。ヨーグルトやフルーツジュースがめちゃ美味しい。
- 市内散策ツアー
- 出国はけっこうたいへんで、3回は危険物チェックゲートを通ったとおもう。
ロンドン
- ヒースローでかすぎ
- むこうだとビートルズとピンク・フロイドは別格らしい
- ロンドンはそこかしこで中国語や日本語がきける
- ベイルートに比べると外国行った感はないかも
- ホテルはそこそこ
- ベイカー・ストリートでホームズ博物館へ。とくになにかあるわけでもないが雰囲気が味わえるのでけっこうよかった。
- 本場のパブでフィッシュ&チップスとビール、まあこんなもんか
- コベント・ガーデンでめちゃくちゃ歌うまいおっさんが歌っててよかった
- オー・ソレ・ミオと誰も寝てはならぬを歌っていたけどほんまプロ級
- 夕飯はチャイナタウンで中華
- 普通の中華料理だけどイギリスじゃ別格にうまいってわけw
- 本場ピカデリー・サーカスでレ・ミゼラブルをみた
- ジャベール役の人がめちゃくちゃよかった、多くの人が最後の登場のところでスタンディング・オベーションしていた
- ソロで特によかったのは star, on my own, bring him home かな
リバプール
けものフレンズ2を総括する
人にはそれぞれの感想がある、これは自分のために整理するくらいの意味合い
よかったところ
- ゴマちゃんことゴマすりクソバードがいいキャラだった
- フレンズのキャラデザがよかった
- ゴマすりクソバードとバンドウイルカがかわいかった
- ごまちゃんやチーターの声優演技がよかった
- 次回予告
- かわいい
- ペパプライブの映像クオリティ
- ここは一期よりもよかったと思えるところ
- キュルル ロシアのスパイ説
だめだめだったところ
- 尾崎のサーバル演技がほんとうにひどい
- 一期の演技が好きだったのでこれはわりと苦痛だった
- かなりいらいらするレベル
- サーバルのbot感、自我を持っていないような感じ
- 違和感しか感じないかばんさん
- サーバルちゃんやラッキーさんをいきなり呼び捨てにしているところ
- それ自体が悪いというより背景がまったくないので違和感しかない
- 全体的に脚本がひどすぎる
- 話として単純におもしろくない
- 前後のつながりに意味がなさすぎる
- 設定のガバ
- キュルルの叡智パート全体的にいらなかったし意味わからなかった
- 7話でリレーにするのまじで意味がわからなかった
- フレンズ型セルリアン弱すぎて話盛り上がる要素がない
- 「緊迫感がまったくない」って言われてたけどほんとうにそのとおりとしかいいようがない
- まじでいらなかったフウチョウコンビ、というかあれなんだったの?
- メインの話にもぜんぜん絡まないし単体としてもなんも影響がない
- 歌も演技も微妙なのでゴリ押しだったんだろうな
おきもち
argc = 0 でプロセスを動かす
DMDのコード内ではargcが0な環境を想定してコードが書かれているが、そういう環境は存在するのだろうか。
調べてみるとわりとすぐにstackoverflowがみつかった。
c - executing a process with argc=0 - Stack Overflow
posix_spawnを使えばできるようなので、実際に試してみる。
#include <spawn.h> #include <stdlib.h> int main(int argc, char** argv, char** envp) { pid_t pid; char* zero_argv[] = {NULL}; posix_spawn(&pid, "/home/kubo39/dlang/dmd-2.085.0/linux/bin64/dmd", NULL, NULL, zero_argv, envp); int status; waitpid(&pid, &status, NULL); return 0; }
たしかにargcが0のときの処理が実行されているようだ。
$ ./a.out Error: missing or null command line arguments $ echo $? 0
ちなみにrustcの場合は Error: couldn't determine self executable name
と表示され、goでは panic: runtime error: index out of range
になった。
Isabelleちょっとやってます、という近況報告?
ここ最近Isabelleを触っている。 Coqと比べると日本語の資料が少ないのでつらい。
とりあえずいろいろわからないことが出てきたので並べてみる。
2以上の自然数を扱うと簡約できない
例としてmap関数を定義する。
fun map :: "('a \<Rightarrow> 'b) \<Rightarrow> 'a list \<Rightarrow> 'b list" where "map _ [] = []" | "map f (x # xs) = (f x) # (map f xs)" value "map (plus 3) (1 # 2 # [])"
そのまま自然数の3や5を命題に含めると解くことができない。
lemma test_map1: "map (\<lambda> x. plus 3 x) ((Suc 1) # 0 # (Suc 1) # []) = (5 # 3 # 5 # [])" sorry
(Suc (Suc 1))
みたいにすると解ける。
lemma test_map2: "map oddb (Suc 1 # 1 # Suc 1 # (Suc (Suc (Suc (Suc 1)))) # []) = (False # True # False # True # [])" apply (simp) done
もちろんこれを続けるのは限界があるので、なんか方法を見つけないといけない。
命題にforallの有無がどう影響を与えるかわかっていない
別の命題を解いてしまっているのではないかという懸念がある。 goalだけみると同じにみえるけどよくわかってない。
forallがないとうまくいかない場合
以下は Failed to apply proof method
と怒られる。
lemma mult_eq_0: "n * m = 0 \<longrightarrow> n = 0 \<or> m = 0" apply (simp) done
\<forall> n m::nat.
を命題に足すと No subgoals
までできる。
lemma mult_eq_0: "\<forall> n m::nat. n * m = 0 \<longrightarrow> n = 0 \<or> m = 0" apply (simp) done
forallがあるとうまくいかない場合
以下は Cannot determine type b.
と怒られる。
theorem not_true_is_false: "\<forall> b::bool. b \<noteq> True \<longrightarrow> b = False" apply (case_tac b) apply (simp_all) done
\<forall> b::bool.
を命題から消すと No subgoals!
までできる。
theorem not_true_is_false: "b \<noteq> True \<longrightarrow> b = False" apply (case_tac b) apply (simp_all) done