kubo39's blog

ただの雑記です。

いまさらマンデルブロー集合を書くので、なるべく型安全っぽくを意識した

プログラミングRustをちらちら読んでいたら並列化の例としてあったので、いまさらマンデルブロー集合を実装した。

元のコードがRustなのでなるべく型安全な感じに書いてみたつもり。

  • 他の言語でfind相当のものがcountUntilという名前で、findは別の操作を行う関数だった。紛らわしい。
  • PNGファイルの出力はimageformatsというライブラリを使った。ヘッダから自動で画像形式を推定してくれる、手軽に使えるので便利。
  • nogcというライブラリを使えば型変換をnogcでできるようだが、結局write_imageGC必要とするので用いなかった。
  • 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/jailingD言語に置き換えてみました。

これです。

目的

実用的にはLinuxであればどの環境でもPerl5が入っていることは期待できるし外部依存もないので特に意義はないです。 あくまで学習目的というやつです。コードサイズも小さいし、いいかなって。

jailing

説明はここを読んで。

もう少しカスタムできるツールだとfirejailだとかnsjailとかあるけど、このくらいの分量だと学習しやすくてありがたいです。 というかこれだけで立派なchroot+jailになるのいいですね。

D言語

Q: なんでD言語なの?
A: 言わせんな恥ずかしい

書いてみての雑感。

  • わりと標準ライブラリでなんとかなるところがよい?でもけっきょく executeShell ばっかりになるもんなあという気もしないではない。
  • あとはなんか std.path.buildPath なんかはきっちり書きたい感出せる気がする。
  • あ、FFIextern (C) だけすればなんとかなるのでだいぶよい。

D言語で書けばバイナリを配るだけ作戦が使える、のだけど元がPerlPythonスクリプトの場合だと書き換え自体がメリットになりにくい。 jailingのようなツールだとそんなに速度とかいらなさそうだし、どうせプロセス起動が遅いのでさほど変わらんだろう。 Rubyとかだったらスクリプトと別に処理系入れるの面倒じゃない?みたいなところに対してメリットが言えそうなんだけど。 なおこの作戦はGoやRustでも別によいという話もある。

jl

だいたい似たような動機のプロジェクトで yoshitsugu/jl というRustのやつがありました。

Rustだと caps とか libmount とかライブラリがあったりして外部コマンドを使わないで実装できるのはいい感じがします。実行時のメモリフットプリントとかにうれしい。 あと clap みたいなコマンドラインパーサがあるとCLI書くとき便利。 ここでは使われていないけど他にもstructoptとかも便利系のやつです。

ところでjlはコマンド実行も外部プロセスにしてしまってるけどここは CommandExtexec 関数を使うべきでは。

ベイルート・ロンドン・リバプール旅行記

3/25~4/2でベイルート(レバノン)、ロンドン・リバプール(イギリス)旅行に行っていたので雑に記録を残しておく

ベイルート

  • 直行便がないのでフランクフルト経由。フランクフルトのラウンジで食べたパン・ソーセージとビールがこの旅で一番うまかった可能性がある。
  • 到着後もろもろの事情で国防軍の出迎え&謎の力ですんなり滞在ビザを取得できてしまった。
    • 滞在ビザは長蛇の列で質問も結構長くやってたぽいので助かった。
  • ホテルはめっちゃいいホテル
    • やたらおいしいチョコレートが置いてあった
    • ベイルート市内からちょっと離れてるけど景色も綺麗だった
  • 近辺を散策していると生々しい銃痕の痕が。現在でも緊迫した状況を感じる。
  • ホテルで朝食をとっているとどんどんアラブ料理を勝手に運んできてくれてとてもよかった。ヨーグルトやフルーツジュースがめちゃ美味しい。
  • 市内散策ツアー
    • 前後を長銃もった兵士に囲まれるバスツアー
    • ベイルート国立博物館にいったけどローマ時代の遺跡はみられず延々鉱石をみることになった
    • アラブ料理のレストラン。なかなか美味しかったけどホテルのほうがおいしかった。ここでもチョコレートケーキ、そして美味しい。
  • 出国はけっこうたいへんで、3回は危険物チェックゲートを通ったとおもう。

ロンドン

  • ヒースローでかすぎ
  • むこうだとビートルズピンク・フロイドは別格らしい
  • ロンドンはそこかしこで中国語や日本語がきける
  • ホテルはそこそこ
  • イカー・ストリートでホームズ博物館へ。とくになにかあるわけでもないが雰囲気が味わえるのでけっこうよかった。
  • 本場のパブでフィッシュ&チップスとビール、まあこんなもんか
  • コベント・ガーデンでめちゃくちゃ歌うまいおっさんが歌っててよかった
  • 夕飯はチャイナタウンで中華
    • 普通の中華料理だけどイギリスじゃ別格にうまいってわけw
  • 本場ピカデリー・サーカスレ・ミゼラブルをみた
    • ジャベール役の人がめちゃくちゃよかった、多くの人が最後の登場のところでスタンディング・オベーションしていた
    • ソロで特によかったのは star, on my own, bring him home かな

リバプール

  • ホテルは建物すごかったけど設備とかつらかった
    • 朝食がびっくりするほどまずかった。あいつらこれうまいとおもってるんすか・・
    • リバプールはどこで食ってもイマイチだったけど
  • ビートルズゆかりの地を訪ねるツアーに参加
  • 中心部はけっこうオシャレタウン
    • 変なおっさんが火の輪くぐりのパフォーマンスしてた、日本じゃできないだろうな
  • 郊外は労働者の街といった感じ
    • 治安もあまりよくなさげ
  • アンフィールドでliverpool vs spurs観戦
    • 住宅地に突然あらわれるスタジアム
    • 肖像画ダルグリッシュ、ファウラー、ジェラード!など
    • あまりうまくないバンドが「wish you were here」や「Don't look back in anger」を歌ってた。ビートルズは逆に歌いにくい?
    • 生You'll never walk aloneに白熱した試合展開に泣きそうになる

けものフレンズ2を総括する

人にはそれぞれの感想がある、これは自分のために整理するくらいの意味合い

よかったところ

  • ゴマちゃんことゴマすりクソバードがいいキャラだった
  • フレンズのキャラデザがよかった
  • ごまちゃんやチーターの声優演技がよかった
  • 次回予告
    • かわいい
  • ペパプライブの映像クオリティ
    • ここは一期よりもよかったと思えるところ
  • キュルル ロシアのスパイ説

だめだめだったところ

  • 尾崎のサーバル演技がほんとうにひどい
    • 一期の演技が好きだったのでこれはわりと苦痛だった
    • かなりいらいらするレベル
  • サーバルbot感、自我を持っていないような感じ
    • すっごーいbotになっていた
    • 一期サーバルはかばんちゃんのために怒ったりツッコミ役やったり、しっかりキャラクターがあったのに
  • 違和感しか感じないかばんさん
    • サーバルちゃんやラッキーさんをいきなり呼び捨てにしているところ
    • それ自体が悪いというより背景がまったくないので違和感しかない
  • 全体的に脚本がひどすぎる
    • 話として単純におもしろくない
    • 前後のつながりに意味がなさすぎる
  • 設定のガバ
  • キュルルの叡智パート全体的にいらなかったし意味わからなかった
    • 7話でリレーにするのまじで意味がわからなかった
  • フレンズ型セルリアン弱すぎて話盛り上がる要素がない
    • 「緊迫感がまったくない」って言われてたけどほんとうにそのとおりとしかいいようがない
  • まじでいらなかったフウチョウコンビ、というかあれなんだったの?
    • メインの話にもぜんぜん絡まないし単体としてもなんも影響がない
    • 歌も演技も微妙なのでゴリ押しだったんだろうな

おきもち

  • だめなところがとことんだめだった
  • もっとひどいアニメ(例をあげるならいくらでもあるが魔法戦争とか)があるがそれがこの作品を許す理由にはならない
  • 制作陣のSNS対応もけっこうひどいなとおもった
    • アンチにあれだけ絡まれると精神的にもつらいだろうけどそれ抜きでも邪悪だよな
  • けフ2好きな人がいてもいいとおもうしそこを叩くのはよくない
    • ただけフ2好きな人はアニメ作品としての出来ではなくてけものフレンズプロジェクト自体が好きなんだろうなとはおもう
    • で、この人たちは現行の「けものフレンズプロジェクト」のやり方を(消極的かもしれないにしろ)認めているわけで、やっぱりわかりあえないんだろうな、と
  • キュルル叩かれすぎでは
    • 人間の子供としてはあんなものじゃないかなあ、そんなに悪人的な描写なかったとおもうんだけど

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