...ing logging 4.0

はてなブログに移行しました。D言語の話とかいろいろ。

型Tと数値nを与えられたとき”T[1][2][2][3][3][3]...[n]をn回繰り返し”という型の配列を作成する

コンパイルタイムプログラミング - 主題のない日記

昨晩、これと同じことがC++で出来るかどうか質問を受けた。5という数字が与えられたときに、0,1,2,2,3,3,3,4,4,4,4,5,5,5,5,5という数列をコンパイル時に生成できるかという問題である。

本の虫: C++0xによるコンパイル時の配列生成

を読んで,D言語だったら(上記の問題とは異なる問題であるものの)表題の型を計算することが「普通に」簡単にできるよなーって思った.

import std.conv;

string array(int dim)
{
    string s;
    foreach (i; 0..dim)
    {
        foreach (_; 0..i+1)
        {
            s ~= "[" ~ to!string(i+1) ~ "]";
        }
    }
    return s;
}

template Cycle(T, int n)
{
    mixin("alias " ~ T.stringof ~ array(n) ~ " Cycle;");
}

void main()
{
    Cycle!(int, 0) a;
    static assert(is(typeof(a) == int));

    Cycle!(int, 1) b;
    static assert(is(typeof(b) == int[1]));

    Cycle!(int, 2) c;
    static assert(is(typeof(c) == int[1][2][2]));

    Cycle!(int, 3) d;
    static assert(is(typeof(d) == int[1][2][2][3][3][3]));
}

あれ? to!string って前からコンパイル時に実行可能だったっけ?

core.sync.*を使ってみる

色々教えてもらってすごくありがたいけれど,全然理解できなくてしょんぼり.勉強が足りなさすぎる・・・orz
とりあえず動いているように見える・・・が?

import std.stdio, core.thread, core.sync.condition, core.sync.mutex;
class Actor
{
	private const(int)[][] arr_;
	private bool disposed_;
	private Thread consumer_;
	private Condition condition_;
	private Mutex mutex_;
	private int count;
	private int sum;
	
	this()
	{
		condition_ = new Condition(mutex_ = new Mutex);
		consumer_ = new Thread({
			synchronized (mutex_) // ロックを取得
			{
				for(;;)
				{
					while (arr_.length == 0)
					{
						condition_.wait(); // notifyされるまでロックを解放(=スレッド停止)
						if (disposed_) break;
					}
					writeln(count++, ": ", arr_[0]);
						// MEMO: 文字列の表示がおかしいけどcountは正しいのでwriteln内部に何かある?
					foreach (e; arr_[0])
					{
						sum += e;
					}
					writeln(sum); // MEMO: 合計も正しい
					arr_ = arr_[1..$];
				}
			}
		});
		consumer_.start();
	}
	void send(const(int)[] arr)
	{
		synchronized (mutex_)
		{
			arr_ ~= arr;
			condition_.notify(); // waitしているスレッドを再度ロックさせてそのスレッドを再開
		}
	}
	void dispose()
	{
		synchronized (mutex_)
		{
			disposed_ = true;
			condition_.notify(); // waitしているスレッドを再度ロックさせてそのスレッドを再開
		}
	}
	@property bool disposed() const
	{
		synchronized (mutex_)
		{
			return disposed_;
		}
	}
}
void main()
{
	auto a = new Actor;
	auto tg = new ThreadGroup;
	// producer1
	tg.create = {
		for (int i; i < 5; i++)
		{
			a.send([1, 2, 3, i]);
		}
	};
	// producer2
	tg.create = {
		for (int i; i < 5; i++)
		{
			a.send([4, 5, 6, i]);
		}
	};
	tg.create = {
		readln();
		a.dispose();
	};
	tg.joinAll();
}
0: [1, 2, 3, 0]
6
1: [1, 2, 3, 1]
1: [1, 2, 3, 1]
13
2: [1, 2, 3, 2]
21
3: [1, 2, 3, 3]
30
4: [1, 2, 3, 4]
40
5: [4, 5, 6, 0]
55
6: [4, 5, 6, 1]
71
7: [4, 5, 6, 2]
88
8: [4, 5, 6, 3]
106
9: [4, 5, 6, 4]
125

出力結果が少しおかしい.

Issue 5328 - The addressof-expression that should be rejected is accepted

本文では全然説明してないですが「"m"がインスタンスメソッドなのに"&A.m"と書けてしまっているけどこれバグじゃないの.クラス定義の中ではちゃんとエラーになるけど外ではエラーにならないで実行時にならないと問題が発覚しなくなっているよ」というつもりで報告したら,「それは仕様です」と返ってきたので考え直してみました.

// a.d
void f(void function() m) { m(); }

class A
{
    this()
    {
        f(&A.m); // rejects-invalid => OK
    }

    /+static+/ void m() {}
}

void main()
{
    f(&A.m); // accepts-invalid => BAD!!
}
$ dmd a.d
a.d(7): Error: function a.f (void function() m) is not callable using argument
types (void delegate())
a.d(7): Error: cannot implicitly convert expression (&this.A.m) of type void
delegate() to void function()

You will get the following result if you remove A.this constructor.

$ dmd a.d
object.Error: Access Violation

一部わからないところがあったり全体についてもあんまり自信はないけれど自分で読むために訳したので載せておきます.あえて話しているかのように訳してみました.

It's a feature, not a bug. You can take the address of methods without providing an object reference (i.e. no this pointer, or what would be the context member of method delegates).

それはバグじゃなくて仕様だよ.あなたはオブジェクトを作らなくてもメソッドのアドレスを取れるよ(つまりthisポインタや他の何らかのメソッドデリゲートのコンテキストメンバがなくても・・・?).

The returned function pointer is simply what the class' vtable contains. The function signature is the same as the signature of a proper delegate to the method. It doesn't make sense to call the function pointer by itself (wrong calling convention, this pointer is missing), but it's useful for other things.

返された関数ポインタは,単にクラスのvtableを含むものだ.その関数シグネチャは,メソッドへの適切なデリゲートのシグネチャと同じだよ.単独でその関数ポインタを呼ぶことは意味をなさない(呼び出しコンベンションが間違っているからポインタは不正だね),でもそれは他のことに役立つよ.

E.g. you can use the type of the function pointer value in meta programming to get the method's signature. That's very important for meta programming. You could just instantiate a new object, and then analyze the delegate, but why should you need to construct a dummy object? For the actual value of the function pointer there are also valid uses. Think about serialization of delegates, for example.

例えば,あなたはメソッドのシグネチャを得るために,メタプログラミングで関数ポインタの値の型を使うことができる.それはメタプログラミングにとって非常に重要だね.あなたは今新しいオブジェクトをインスタンス化してからデリゲートを取れるけど,どうしてダミーのオブジェクトを作る必要があるのだろう?そしてまた関数ポインタの実際の値のためには有効な用途がある.例えば,デリゲートのシリアライゼーションについて考えてみて欲しい.

Note that the delegate property .funcptr returns a similarly non-sensical function pointer. The context is missing and the calling convention is incompatible. If you call it, you get an access violation as well.

デリゲートプロパティ.funcptrが同様に無意味な関数ポインタを返すことに注意しよう.そのコンテキストはなくなっていて,そして,呼び出しコンベンションには互換性がない.あなたがそれを呼ぶなら,同じように一般保護違反が起こるよ.

What is happening here is that the language "designers" just didn't feel like introducing a dedicated separate function type for this purpose. Instead, they reused the "normal" function pointer type, even if calling it makes no sense. Basically it's a quick language hack that endangers type-safety and confuses users.

ここで起こっていることは,言語「設計者」が丁度この目的のために専用の別々の関数型を導入する気がなかったということだ.その代わりに,たとえそれを呼ぶことが意味をなさないとしても,彼らは「通常の」関数ポインタタイプを再利用した.基本的に,それは型安全性を危険にさらして,ユーザを混乱させるお手軽な言語ハックだ.

Btw. the code inside A.this is not accepted, because the "A." from "&A.m" just qualifies the member "m". Inside the ctor it's exactly the same as writing "&m". And &m is a delegate, not a function. So everything is alright. Though this slight syntactic ambiguity is another hint that D as a language is actually quite unorthogonal and full of special cases. Maybe "&typeof(this).m" in the ctor would do the same as the &A.m in main()?

ところで,A.thisの内側のコードは,「&A.m」の「A.」がまさにメンバ「m」を制限するから,受け入れられないんだ.それはまさにctorの中に「&m」を書くことと同じだね.そして,&mはデリゲートであって関数ではない.それですべては問題ないんだ.このわずかな統語的曖昧性がもう一つのヒントであるけれども,言語としてのD言語は実は全く直交していない特例でいっぱいだ.多分,ctorでの「&typeof(this).m」は,main()での&A.mと同じように動くんじゃないかな?

5328 – The addressof-expression that should be rejected is accepted

おっしゃるようにメタプログラミングのために関数シグネチャを得る方法は必要だけれど,それを呼び出せてしまってはよくないのでコンパイル時にエラーにすることはできないんでしょうか.

  • "m"がインスタンスメソッドのとき,そのデリゲートを得るには,クラス定義の内側では"&m"もしくは"&this.m"と書き,外側では"&obj.m"と書く.
  • "m"がクラスメソッドのとき,その関数ポインタを得るには,クラス定義の内外で"&A.m"と書く.

これではダメなんですかね.
そういうことを英語で説明できたらいいんですけど難しい・・・.


あと,デリゲートのシリアライゼーションについてはどんな議論ができるのか全然想像できないのでこれについてはコメントできません.

non-static member functionのアドレスを取る

import win32.windows;
import core.thread;
import std.stdio;

class A
{
	this()
	{
		EnumWindows(&A.enumWindowsProc, 0); // ここではコンパイルエラーになるのに
	}
	
	/*static*/ BOOL enumWindowsProc(HWND hwnd, LPARAM lparam)
	{
		return TRUE;
	}
}

void main()
{
	EnumWindows(&A.enumWindowsProc, 0); // ここではならない
}

std.concurrency.spawnで実行しているスレッドでのassertが捕捉されない

ずっとassertが使えなくて困っていたのですが,ふと思いついて試してみたところ,
AssertError型を自分で捕まえるtry-catchを自分で書かないといけないことがわかりました.

import std.stdio;
import std.concurrency;

void f()
{
    //try
    //{
        writeln("1");
        assert(false);
    //}
    //catch(Throwable t)
    //{
    //  writeln(t);
    //}
}

void main()
{
    auto tid = spawn(&f);
    readln();
}

コメントアウトをしたままだと

1

コメントを外すと

1
core.exception.AssertError@a(9): Assertion failure

となります.

追記

assertなのでcatchした後に再スローした方がいいかも.

catch (Throwable t)
{
  writeln(t);
  throw t;
}

Null object Pattern

メソッドを呼ぶ前にnullチェックを呼ばなくてもよくするパターン.

import std.stdio;

// 誰かが作って中身を変えられないコード ////////
enum 乱数 = 1;
interface I { void m(); }
class A : I { override void m(){ writeln("hi"); } }
I foo(){ if (乱数%2) return new A; else return null; }
////////////////////////////////////////////////

class NullObject : I {
	override void m() { /+ do nothing +/ }
}
I bar(I i){ if (i !is null) return i; else return new NullObject; }
void main() {
	//if (i is null) {} else { i.m(); } // これの代わりに
	I i = bar(foo());
	f(i);
}
void f(I i) { i.m(); } // nullチェック要らず

hi

Decorator pattern

インスタンスを包含して機能を拡張するパターン.

import std.stdio;

interface Ice { string name(); }
class VanillaIce : Ice { override string name() { return "バニラアイス"; } }
class ChocorateSomeIce : Ice {
	Ice ice;
	this(Ice ice) { this.ice = ice; }
	override string name() { return "チョコ" ~ ice.name(); }
}
class GreenTeaIce : Ice { override string name() { return "抹茶アイス"; } }

void main() {
	Ice v = new VanillaIce();
	Ice cv = new ChocorateSomeIce(new VanillaIce());
	Ice cg = new ChocorateSomeIce(new GreenTeaIce());
	writeName(v);
	writeName(cv);
	writeName(cg);
}
void writeName(Ice ice) { writeln(ice.name); }

バニラアイス
チョコバニラアイス
チョコ抹茶アイス