KOTET'S PERSONAL BLOG

#dlang コンパイル時fizzbuzzと謎のaliasSeqOf

Created: , Last modified:
#dlang #tech

これは1年以上前の記事です

ここに書かれている情報、見解は現在のものとは異なっている場合があります。

D言語のfizzbuzz - kubo39’s blog を読んで、コンパイル時fizzbuzzはforeachを使えば短く書けるのでは?と思い実際に書いてみることにした。

自分の力で書いてみる

int test(alias n)()
{
    foreach (i; 0 .. n)
    {
        static if (i % 3)
            pragma(msg, i);
    }
    return 0;
}

enum foo = test!15;

まず上のようにforeach内でstaticなあれこれはできない。

$ dmd -c fizzbuzz.d
fizzbuzz.d(5): Error: variable i cannot be read at compile time
fizzbuzz.d(11): Error: CTFE failed because of previous errors in test

以下のように計算の過程でforeachを使うのは大丈夫。

auto test(alias n)()
{
    int[] result;
    foreach (i; 0 .. n)
    {
        if (i % 3)
            result ~= i;
    }
    return result;
}

enum foo = test!15;
pragma(msg, foo);
$ dmd -c fizzbuzz.d
[1, 2, 4, 5, 7, 8, 10, 11, 13, 14]

つまりこうすれば繰り返しを手で書かずに済む。 ただし出力は1つずつ行わなければいけない、みたいなレギュレーションがあったらダメ。

string fizzbuzz(alias n)()
{
    import std.algorithm : map;
    import std.conv : to;
    import std.range : array, iota, join;

    return iota(1, n + 1)
    .map!(a => (!(a % 15)) ? "fizzbuzz" : (!(a % 5)) ? "buzz" : (!(a % 3)) ? "fizz" : a.to!string)
    .join("\n")
    .array
    .to!string;
}

pragma(msg, fizzbuzz!15);
$ dmd -c fizzbuzz.d
1
2
fizz
4
buzz
fizz
7
8
fizz
buzz
11
fizz
13
14
fizzbuzz

aliasSeqOfを使ったバージョン

lsを間違えて(中略)コンパイル時にD言語くんが通り過ぎるコマンド - Qiita

int fizzbuzz(alias n)()
{
    import std.meta : aliasSeqOf;
    import std.algorithm : map;
    import std.conv : to;
    import std.range : array, iota, join;

    foreach (i; aliasSeqOf!(iota(1, n + 1)))
    {
        static if (!(i % 15))
            pragma(msg, "fizzbuzz");
        else static if (!(i % 3))
            pragma(msg, "fizz");
        else static if (!(i % 5))
            pragma(msg, "buzz");
        else
            pragma(msg, i);
    }
    return 0;
}

enum foo = fizzbuzz!15;
$ dmd -c fizzbuzz.d
1
2
fizz
4
buzz
fizz
7
8
fizz
buzz
11
fizz
13
14
fizzbuzz

以下のコードを$ dmd -c fizzbuzz.d -vcg-astすると

void main()
{
    import std.meta : aliasSeqOf;
    import std.range : iota;
    import std.random : uniform;
    import std.stdio : writeln;

    int n = uniform(1, 6);
    foreach (i; aliasSeqOf!(iota(1, 6)))
        if (n == i)
            writeln(i ^^ 2);
}

ifに展開されていた。 aliasSeqOfはレンジを"alias sequence"に変換するもの……だそうだがちょっと何が起きているのか理解できていない。 aliasSeqOf!(iota(1, 6))はいったいforeachに何を渡したことになるんだろう? aliasSeqOfを使わずに手書きで同じ現象を起こすにはどうしたら良いんだろう? 今後の課題にしようと思う。

import object;
void main()
{
	import std.meta : aliasSeqOf;
	import std.range : iota;
	import std.random : uniform;
	import std.stdio : writeln;
	int n = uniform(1, 6);
	unrolled {
		{
			enum int i = 1;
			if (n == 1)
				writeln(1);
		}
		{
			enum int i = 2;
			if (n == 2)
				writeln(4);
		}
		{
			enum int i = 3;
			if (n == 3)
				writeln(9);
		}
		{
			enum int i = 4;
			if (n == 4)
				writeln(16);
		}
		{
			enum int i = 5;
			if (n == 5)
				writeln(25);
		}
	}
	return 0;
}
// 以下略