Help us understand the problem. What is going on with this article?

さくっとD言語(万年初心者による初心者向け記事)

More than 5 years have passed since last update.

D言語 Advent Calendar 5日目の記事を書かせていただきます。

皆さんの記事、どれも難しそうですね...(自分にとって)。そこで自分からは、D言語の手軽な一面を紹介していきたいと思います。
トンデモなことを言ってしまっていたら申し訳ないです。

何するの?

今回、自分は小さなプログラムを作ります。バインディングの話を少し盛り込んでいきます。その後、自分が知りうるところの、D言語の使われ方を簡単に紹介できればと考えています。

さくっとプログラム

カレンダーをざっと見たとき、4日目にalpha_kai_NETさんからTwitterAPIのお話があると伺っていたので、「POSTするだけのプログラム」作りたいなあ...と考えていました。
が、ご存知の通りalpha_kai_NETさんがgtkDと組み合わせて作ってくれました。バンザイ。

これでおしまい、となってはしょうがないので、自分は別のGUIを用いたいと思います。
そんなとき学校の同じ同好会に所属する方が、「enlightenmentは最高のWM」と仰っていました。なるほど確かにカッコイイ。
ちょうどC向けのライブラリ、Enlightenment Foundation Libraries (EFL)があるそうなので、これを使いましょう。

Cとのインターフェイス

前置きが長くなってしまったので、先にコードを出します。ドン。
(これも長いです...)

defl.d
module defl;

extern(C) nothrow:

enum NULL = cast(void*)0;

struct _Eo_Opaque;

alias _Eo_Opaque Eo;
alias Eo Evas_Object;
alias Eo Evas;

alias int Evas_Coord;

alias ubyte Eina_Bool;

alias Evas_Smart_Cb = void function(void*, Evas_Object*, void*);
alias Evas_Object_Event_Cb = void function(void*, Evas*, Evas_Object*, void*);

enum EVAS_HINT_EXPAND =  1.0;
enum EVAS_HINT_FILL   = -1.0;

enum EINA_FALSE = (cast(Eina_Bool)0);
enum EINA_TRUE  = (cast(Eina_Bool)1);

enum Elm_Policy:int
{
   ELM_POLICY_QUIT,
   ELM_POLICY_EXIT,
   ELM_POLICY_THROTTLE,
   ELM_POLICY_LAST
};

enum Elm_Policy_Quit:int
{
   ELM_POLICY_QUIT_NONE = 0,
   ELM_POLICY_QUIT_LAST_WINDOW_CLOSED
};

enum Elm_Wrap_Type:int
{
   ELM_WRAP_NONE = 0,
   ELM_WRAP_CHAR,
   ELM_WRAP_WORD,
   ELM_WRAP_MIXED,
   ELM_WRAP_LAST
};

enum _Evas_Callback_Type:int
{
   EVAS_CALLBACK_MOUSE_IN,
   EVAS_CALLBACK_MOUSE_OUT,
   EVAS_CALLBACK_MOUSE_DOWN,
   EVAS_CALLBACK_MOUSE_UP,
   EVAS_CALLBACK_MOUSE_MOVE,
   EVAS_CALLBACK_MOUSE_WHEEL,
   EVAS_CALLBACK_MULTI_DOWN,
   EVAS_CALLBACK_MULTI_UP,
   EVAS_CALLBACK_MULTI_MOVE,
   EVAS_CALLBACK_FREE,
   EVAS_CALLBACK_KEY_DOWN,
   EVAS_CALLBACK_KEY_UP,
   EVAS_CALLBACK_FOCUS_IN,
   EVAS_CALLBACK_FOCUS_OUT,
   EVAS_CALLBACK_SHOW,
   EVAS_CALLBACK_HIDE,
   EVAS_CALLBACK_MOVE,
   EVAS_CALLBACK_RESIZE,
   EVAS_CALLBACK_RESTACK,
   EVAS_CALLBACK_DEL,
   EVAS_CALLBACK_HOLD,
   EVAS_CALLBACK_CHANGED_SIZE_HINTS,
   EVAS_CALLBACK_IMAGE_PRELOADED,

   EVAS_CALLBACK_CANVAS_FOCUS_IN,
   EVAS_CALLBACK_CANVAS_FOCUS_OUT,
   EVAS_CALLBACK_RENDER_FLUSH_PRE,
   EVAS_CALLBACK_RENDER_FLUSH_POST,
   EVAS_CALLBACK_CANVAS_OBJECT_FOCUS_IN,
   EVAS_CALLBACK_CANVAS_OBJECT_FOCUS_OUT,

   EVAS_CALLBACK_IMAGE_UNLOADED,

   EVAS_CALLBACK_RENDER_PRE,
   EVAS_CALLBACK_RENDER_POST,

   EVAS_CALLBACK_IMAGE_RESIZE,
   EVAS_CALLBACK_DEVICE_CHANGED,
   EVAS_CALLBACK_LAST
};
alias _Evas_Callback_Type Evas_Callback_Type;

int elm_init(int argc, char **argv);
void elm_run();
int elm_shutdown();
void elm_exit();

Eina_Bool elm_policy_set(uint policy, int value);
void elm_win_autodel_set(Eo *obj, Eina_Bool autodel);
void elm_win_borderless_set(Eo *obj, Eina_Bool borderless);
Evas_Object *elm_win_util_standard_add(const char *name, const char *title);
Evas_Object *elm_box_add(Evas_Object *parent);
void elm_win_resize_object_add(Eo *obj, Evas_Object *subobj);
Evas_Object *elm_entry_add(Evas_Object *parent);
void elm_entry_line_wrap_set(Eo *obj, Elm_Wrap_Type wrap);
void elm_box_pack_end(Eo *obj, Evas_Object *subobj);
void elm_object_focus_set(Evas_Object *obj, Eina_Bool focus);
void elm_entry_text_style_user_push(Evas_Object *obj, const char * style);
char* elm_entry_markup_to_utf8(const char* s);
char* elm_entry_entry_get(Evas_Object* obj);

void evas_object_size_hint_weight_set(Eo *obj, double x, double y);
void evas_object_show(Evas_Object *obj);
void evas_object_size_hint_align_set(Eo *obj, double x, double y);
void evas_object_smart_callback_add(Evas_Object *obj, const char *event, Evas_Smart_Cb func, const void *data);
void evas_object_event_callback_add(Evas_Object *obj, Evas_Callback_Type type, Evas_Object_Event_Cb func, const void *data);
void evas_object_resize(Eo *obj, Evas_Coord w, Evas_Coord h);

今回は、テキストボックスだけ出て、入力→ctrl+Enterで送信、というプリミティブなものを考えているので、それに必要なインターフェイスだけ用意します。
最初の方にstruct _Eo_Opaque;とありますが、これはincompleted typeです。D言語でも特に問題なく記述できます。

C向けの呼び出し規約の指定は、少し前まで、

extern(System) {
...
}

と書いていたものですが、最近では、

extern C nothrow:
...

と書くのが主流みたいです(参考?)。D言語で作った関数をCのAPIに渡して例外が発生したとき、セグメンテーション違反で落ちるのでnothrowが無難です(経験則)。

これをヘッダー用にコンパイルしておきます(.diファイルが生成されます)。

dmd -H -c defl.d

プログラム本体

上記のEFLのDバインディングと、Twitter4Dを利用してPOSTするだけプログラムを書きます。
「動けばいいや」と思っているタチなのでトンデモ感満載ですが、書きなぐった感じがお手軽感も出ていいかな、と思います。

qiita.d
import defl;
import core.runtime;
import std.stdio;
import std.string;

void main()
{
  auto args = Runtime.cArgs;
  elm_init(args.argc, args.argv);

  scope(exit){
    elm_run();
    elm_shutdown();
  }

  Evas_Object* win, box, en, icon;

  elm_policy_set(Elm_Policy.ELM_POLICY_QUIT, Elm_Policy_Quit.ELM_POLICY_QUIT_LAST_WINDOW_CLOSED);

  win = elm_win_util_standard_add("", "Tweet");
  elm_win_autodel_set(win, EINA_TRUE);
  elm_win_borderless_set(win, EINA_TRUE);

  box = elm_box_add(win);
  evas_object_size_hint_weight_set(box, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
  elm_win_resize_object_add(win, box);
  evas_object_show(box);

  en = elm_entry_add(win);
  evas_object_size_hint_weight_set(en, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
  evas_object_size_hint_align_set(en, EVAS_HINT_FILL, EVAS_HINT_FILL);
  elm_entry_line_wrap_set(en, Elm_Wrap_Type.ELM_WRAP_MIXED);
  evas_object_smart_callback_add(en, "changed", &_en_renew_cb, NULL);
  evas_object_event_callback_add(en, Evas_Callback_Type.EVAS_CALLBACK_KEY_DOWN, &_en_keydown_cb, NULL);
  evas_object_event_callback_add(en, Evas_Callback_Type.EVAS_CALLBACK_KEY_UP, &_en_keyup_cb, NULL);
  elm_box_pack_end(box, en);
  elm_entry_text_style_user_push(en, "DEFAULT='font=Sans font_size=16'");

  evas_object_show(en);

  elm_object_focus_set(en, EINA_TRUE);

  evas_object_resize(win, 300, 0);

  evas_object_show(win);
}


extern(C) void _en_renew_cb(void *data, Evas_Object *obj, void *event) nothrow
{
  debug{
    debuglog = elm_entry_markup_to_utf8(elm_entry_entry_get(obj)).cs2ds;
  }
}

bool debuglog(T)(T str) nothrow
{
  try{
    stderr.writeln = format("%s", str);
  } catch(Throwable e){
    // 例外の握りつぶし
    debug{
      debuglog(e);
    }
    return false;
  }
  return true;
}

int ctrl_f = 0;

extern(C) void _en_keydown_cb(void *data, Evas *e, Evas_Object *obj, void *event_info) nothrow
{
  const char* ptr = *cast(const char **)event_info;
  string keyname = ptr.cs2ds();
  debug{
    debuglog = "keydown : " ~ keyname;
  }

  ctrl_f |=   (("Control_R" == keyname) << 1)
            | (("Control_L" == keyname) << 0);
  if(ctrl_f && ("Return" == keyname)) {
    scope(exit) {
      elm_exit();
    }

    debug{
      text.debuglog;
    }

    try{
      import twitter4d;
      string text = elm_entry_markup_to_utf8(elm_entry_entry_get(obj)).cs2ds.stripRight;
      auto t4d = new Twitter4D([
        "consumerKey"       : "ほけ",
        "consumerSecret"    : "ほげ",
        "accessToken"       : "ぼけ",
        "accessTokenSecret" : "ぼげ"
      ]);
      t4d.request("POST", "statuses/update.json", ["status" : text]);
    } catch(Throwable e) {
      e.debuglog;
    }
  }

  debug{
    debuglog = "etrl_f:";
    ctrl_d.debuglog;
  }
}

extern(C) void _en_keyup_cb(void *data, Evas *e, Evas_Object *obj, void *event_info) nothrow
{
  const char* ptr = *cast(const char**)event_info;
  string keyname = ptr.cs2ds();
  debug{
    debuglog = "keyup : " ~ keyname;
  }

  int erace =   (("Control_R" == keyname) << 1)
              | (("Control_L" == keyname) << 0);
  ctrl_f &= ~erace;

  debug{
    debuglog = "etrl_f:";
    ctrl_d.debuglog;
  }
}

string cs2ds(const char* ptr) nothrow
{
  string str = "";
  for(int i = 0; *(ptr + i) > 0; ++i) {
    str ~= *(ptr + i);
  }
  return str;
}

...特にD言語の強み、というのが見せられないので残念、かつ申し訳ないです。
gtkDのように整備されたライブラリであれば、alpha_kai_NETさんが書かれていたように、すっきりと記述できます。

コンパイルします。

dmd qiita.d twitter4d.d `pkg-config --libs elementary | sed -e "s/-l/-L-l/g"` -L-lcurl

dmdでは、リンカにお伝えする情報は、-Lが先頭に必要なため、上記のようになっています(もっときれいな書き方教えて下さい...)。

実際の動作の様子は、以下のようになります。

demo.gif

Twitter4Dのようなライブラリがあると、こういうのもさくっと作れて楽しいですね。感動もひとしおです。

D言語は、Cとの接続がしやすいように設計されています *
機会があれば、思い出してみてください。

その他

さくっとD言語を使うシーンを2つほど紹介したいと思います。あくまで自分の知りうる限りで、おもしろいなあと感じたものを挙げます。

競技プログラミング

競技プログラミングでD言語が使われるようになった話は、ずいぶん前にありましたが、
競技プログラミング人口の増加も相まってか、競技D言語erの数も増加しているように感じます。

最近のプログラミングコンテストでは、CODE FESTIVALの本戦にAvocadro(アボカドロ)さんや、大堀龍一 (Ryuichi OHORI)さんが出ていましたね。

また、CODE RUNNERの予選では、使用言語のアンケートが実施され、以下の様な結果でした。
https://twitter.com/ExactlyN/status/528910660948615169
3.9%、一見少なく思えますが、参加者全体で約200名なので4%くらいと思って、200 \times 0.04 = 8[名]
あれ? 少ない?

とは言えど、今ではtwitterで「D言語」と検索すると前のように内輪っぽくなくて様々な顔が見れるようになったのでホクホクです。
CODE RUNNERの本戦では、diginatuさんや、ふーさんがD言語erとして出場されていました。

(他にも出ておられる方がいたらすみません...)

電卓用途にD言語

また、電卓用途としてD言語を用いるということもあります
https://twitter.com/k3_kaimu/status/529185988963074048 )。

自分も学校の課題でやたらめったら計算させられる問題に対して、コンパイル時に結果を出しながら遊んでいました(怠慢)。
おすすめです。

おわりに

D言語のお手軽な一面を紹介してきましたが、いかがでしたでしょうか。

「D言語、知ってはいるけど手が出せない...」という方の足がかりに、少しでも貢献できたら幸いです。

だいぶ冗長となってしまいましたが、ここまでスクロールしていただいた方、ありがとうございます。

arukuka@github
Cラッパーの話が好きです。Xlibとか、Waylandなんて来たら下痢するぐらい興奮します(するだけ)。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away