D言語でもboost::anyをする.(任意の型の値を入れられるコンテナをつくる)

きっかけ

Twitterかどこかで,Type Erasureなる文字をみた. そういえば,以前JavaGenericsの実装がType Erasureを用いてるみたいなことを調べたことがあったけど(あれはListみたいなものがListになっているみたいなものっぽくて,型変数が消えた状態で識別されるみたいな話っぽいけど),Type Erasureってなんだろうって思ってぐぐってみた

そうすると,次の記事がでてきた猿でも分かった~型消去技法とは - 418 I'm a teapot

なるほど,要するにダックタイプができるってことっぽい.

もう少し詳しく書くと,2つのクラスに継承関係がなくても,例えば共通の関数fを実装しているということがわかれば,それらを中身の差異はあれど,fという点においては共通であるから次のようなことができる.

import std.stdio;

/*
   AとBは独立の型であって,継承関係はない.
 */
class A {
  void f() {
    writeln("A#f is called!");
  }
}

class B {
  void f() {
    writeln("B#f is called!");
  }
}

// A, Bはともに,void f();という共通のシグネチャをもっているから,void f()だけをもつプレースホルダーをつくる.
abstract class Holder {
  abstract void f();
}

// 実際に値を格納するコンテナをつくる.
class Holder_Container(T) : Holder {
  T obj;
  this (T obj) { this.obj = obj; }
  override void f() { obj.f(); }
}

void main() {
  Holder[] objs;

  // Holder_ContainerはHolderを継承しているからHolderの配列に追加することが可能
  objs ~= new Holder_Container!(A)(new A); // これは末尾に追加する演算
  objs ~= new Holder_Container!(B)(new B);

  foreach (obj; objs) {
    obj.f; // それぞれ,A.fとB.fを呼び出している.
  }  
}

C++だとvirtualキーワードを用いていたけど,Dではvirtualはないので,abstractにする.(こうすると,プレースホルダにもなる.)

こうすると,HolderというAとBをつなぐインターフェースのようなものを介して扱うことができて,静的な型がついていてもダックタイプができる.

で,書くまでもないけど,継承関係がない場合を上に書いたけど,ダックタイプでもなんでもない普通の継承関係がある場合は次のようにかけて,当然ちゃんと動く.(明示的にインターフェースを書く)

import std.stdio;

interface HasF {
 void f();
}

class A : HasF {
  void f() {
    writeln("A#f  is called!");
  }
}

class B : HasF {
  void f() {
    writeln("B#f  is called!");
  }
}

void main() {
  HasF[] hf;

  hf ~= new A; 
  hf ~= new B;

  foreach (e; hf) {
    e.f;
  } 
}

これは基本なので説明するまでもない.

実装する.

とりあえず,先程の記事を読み進めると,boost::anyというものがあって,これは任意の型(実際は制約があるっぽい?けど)を入れることのできる型で,これも型消去を用いて実装されているらしい.

なるほど.これはDで実装するしかないよね,となった.

D言語には似たようなものとしてstd.variantにVariantがあるけど,あれは格納したい型の最大のサイズを計算して,その大きさのunionを持つということで値を保持している. 今回作るものはそういうものではなくて,型は静的に解決されている.

とりあえず,boost::anyのコードをみるのもいいけど,実際にC++で実装している記事を見たので,それをDに持ってくることにした.

boost::anyを実装してみる - (void*)Pないと

で,実装した.

import std.traits,
       std.meta;

class Any {
  private {
    /*
      インターフェース
     */
    abstract class _AnyBase {
      abstract TypeInfo type();
      abstract _AnyBase clone();
    }

    /*
      実際に値を保持するクラス
     */
    class _Any(T) : _AnyBase {
      T value;

      this (T value) {
        this.value = value;
      }

      override TypeInfo type() const {
        return typeid(T);
      }

      override _AnyBase clone() {
        return new _Any!T(this.value);
      }

      ~this () {}
    }

    /*
        内部で値を保持する(こいつがミソ) (ある型の値を持っているのに型情報が型に含まれてないのがポイント!)
     */
    _AnyBase obj;
  }

  public {
    /*
      コンストラクタ
      - 引数が無いもの
      - 一般の値を引数にできるもの 
      - Any型が引数のもの
      の3種類.
     */
    this () {}
    
    this(T)(T value) {
      this.obj = new _Any!T(value);
    }

    this(Any any_obj) {
      if (any_obj.obj !is null) {
        this.obj = any_obj.obj.clone();
      } else {
        this.obj = null;
      }
    }

    // TがAnyとは無関係であるということを言わないと,opAssignは定義できないので
    Any opAssign(T)(T value) if (!is(T == Any)) {
      delete this.obj;
      this.obj = new _Any!T(value);
      return this;
    }

    // この関数を用いて値を取り出す.
    T castTo(T)() {
      if (this.obj is null) {
        throw new Exception("this object has no value");
      }
      if ((cast(_Any!T)this.obj) is null) {
        throw new Exception("can not cast into incompatible type");
      } else {
        return (cast(_Any!T)this.obj).value;
      }
    }

    // 型情報を返す.
    TypeInfo type() {
      return this.obj.type;
    }
  }
}

import std.stdio;

class K {
  int value;
  this (int value) {
    this.value = value;
  }
}

void main() {
  Any a = new Any;

  a = 10;
  writeln(a.type);
  writeln(a.castTo!int);

  a = [1, 2, 3, 4, 5];
  writeln(a.type);
  writeln(a.castTo!(int[]));

  a = new K(123);
  writeln(a.type);
  writeln(a.castTo!(K).value);
}

えーと,結論からいうと これがなんでできているかというと,

内部にはまず,2つのクラスがある._AnyBase型と_Any!T型である. そして,_Any!T_AnyBaseを継承している.ここで型変数Tが消えていることに注目.

つまり,実際に値を保持するobjは_AnyBase型であり,型情報(型変数)をもたない! つまり,どんな型Tがきて_Any!Tがつくられても,_AnyBaseはそれを受け入れることができるから,どんな型でも保持できるってワケ

まとめると

  • 実際に値を保持するのは_AnyBase objで,これは内部ではobj = new _Any!T(T型の値)というように使われる(_Any!T_AnyBaseを継承しているので,_AnyBaseに入れることができる)
  • _AnyBaseは中身(値であったりその型)には言及していない.つまり,どんな_Any!Tもうけとれる ← ここがミソ

まとめ

すごく久しぶりにブログを書いたので,ブログの書き方を忘れてしまったので,すごい適当な記事になってしまった.

coins入学後にやりたいこと。(coins Advent Calendar 12/5)

はい。この記事はcoins Advent Calendar 5日目の記事です。 この記事では、私α改がcoins入学後にやりたいことをぼんやりと書こうと思います。

自己紹介

私は、2016年度AC入試でcoins(筑波大学 情報学群 情報科学類)に合格したα改です。 2017年に入学します。 ちなみに、私は浪人生です... 2016年3月に福井県鯖江高校を卒業して半年ぐらい浪人した後、ACで筑波に受かりました。 先輩方、よろしくお願いいたします。もちろん、ACで合格した方やこれから一般入試や推薦入試で合格される同級生の方もよろしくお願いいたします。

なんかAC入試について書こうかなぁって少し思ってはいたのですが、どの程度の事をここに記載してもいいのかどうかがわからなかったのでACについて書くことは避けようと思います。

coinsでやりたいこと

さて、本題のcoinsに入ったらやりたいなぁ...と個人的に思ってることをいくつか書きます。

  • D言語の勉強会
  • 技術系の勉強会(fukuitech(後述)の筑波版)
  • Kernel VM探検隊の開催
  • 輪読会

とりあえず、この4つがcoinsでやりたいイベントですね。あとはARCで研究活動を行いたいという考えもありますが... D言語の勉強会はもうそのまんまで、普通にD言語の勉強会を開催したいと言う感じです。 また、技術系の勉強会についてですが、私は現在福井県に住んでいます。そして、そこで高校2年生のときから(ちょうど2年ぐらい前) 福井技術者のつどい(a.k.a. fukuitech)という技術系の勉強会を運営しています。正確には、高校3年生のときは受験のため運営から離れましたが 高校2年生のときと、浪人が終わった今は再び運営に戻りました。(12/3にも福井技術者のつどい その7を開催しました。) 福井技術者のつどいの特徴は、大雑把に言うと開かれた勉強会と言うことです。 どのように開かれているか、ですがおもに2つの意味合いから私は開かれた勉強会であると考えています。

1つめは、福井技術者のつどいがノージャンルの勉強会と言うことです。 基本的に、勉強会は何か特定のテーマを定めて行うのが普通だと思います。例えば、Android開発やOS開発など、なにか特定の分野に特化することが多いと思います。 しかし、福井技術者のつどいは違います。発表者が自由に好きなことを発表します。その発表の分野は非常に多岐にわたります。 私は、D言語に関する発表を主に行っていますが、他にも、物理学の第一原理計算に関する発表や物理シュミレーションに関する発表を行う方もいれば PythonRubyなどでWeb開発やWebアプリケーションのフレームワークに関する発表を行う方、さらにはネットワーク・セキュリティに関する発表を行う方もいらっしゃいますし本当に多くの分野の発表が行われます。 これにはいくつかの狙いがあります。たとえば、参加者数や参加者の層を拡大するということです。分野を広げることにより様々な分野に興味関心のある方があつまり 異なる分野間での交流が期待できます。さらに、一度の勉強会でさまざまな分野に関する発表を聞くことは個人的にとてもお得に感じます。 このように、発表の内容に関する制約を課していないということが私が開かれた勉強会であると考える理由の一つです。

2つめは、発表の外部への発信です。具体的には、基本的に福井技術者のつどいでの発表はYouTube Liveを持ちいてリアルタイムに外部に配信を行っています。 リアルタイムに中継することは、当日会場に来られない人や、その他多くの人に発表を聞いてもらうことを狙いとしています。 また同時に、福井技術者のつどいでは発表中にその発表を聞いてるほかの参加者がTwitterなどのSNSを用いて発表に関する実況などを行うことを運営側で推奨しています。 聴きながらのツイートは発表者にとって失礼に当たると思われるかもしれないが、発表者と聴講者の双方に大きな利点があると考えるからです。 発表者の利点として、リアルタイムな反応を後から確認することが出来ます。例えば、「わかりにくい」とツイートがあれば、改善するなどのフィードバックが可能になります。 また、自分の発表に関するツイートは嬉しいので励みにもなります。加えて、Twitterで実況をすると外部の技術者がストリーミング配信を通じてその発表を聞くことに繋がる可能性もあります。 聴講者側の利点は、発表中に聴講者同士で意見交換や質問が可能となることです。 なお、2回目以降はストリーミング配信を勉強会終了後にYouTubeの福井技術者のチャンネルで公開しています。 また、外部から情報を受信することも考えています。過去に、兵庫県尼崎市からの参加者が当日体調不良で欠席したがSkypeを用いて発表を行った実績があります。

以上の理由から、私は福井技術者のつどいは開かれた勉強会であると考えます。

そして私は、筑波でもこのような形式の勉強会を行いたいと考えています。 ただ、福井技術者のつどいはKernel VM探検隊という私の好きな勉強会のスタイルを参考にしています。 そのため、上に書いたKernel VM探検隊の開催じにこれを兼ねてしまうかもしれません(それが可能かどうかはわかりませんが。) それかcoins LTという形式で行うかもしれません。そのへんは先輩たちに伺いたいと思います...

はいあとは輪読会ですね。 具体的には、SICP, structure and interpretation of computer programs, 計算機プログラムの構造と解釈の輪読を行いたいと考えています。 また、余裕がアレば、TaPL, Type and Programming Languages, 型システム入門 プログラミング言語と型の理論も読めればなぁ...と考えています。

他にもいろいろとやりたいことはありますが、あまりにもその多くを個々に書いてしまうと後々辛くなってしまいそうなのでこの辺にしようと思います。

改めまして、coinsならびにITFの先輩方、来年からよろしくお願いいたします。