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

D言語のUFCSが好きだ!

More than 3 years have passed since last update.

D言語の特徴的な機能としてUFCSがある

他の言語にも同様の機能があるようですが、D言語のそれには使っていて楽しいと思えるいくつかの特徴があり、好んで使うD言語erも多いようです。(私もよく使ってます!)
私がUFCSするときに感じた、利点などをまとめてみました。
[dmd2.068.2にて動作確認]

UFCSとは

UFCS (Unified Function Call Syntax)とは、関数呼び出しについてのシンタックスシュガーで、ほとんどの関数について、func(obj, ...)obj.func(...)と記述することを許すというシンプルな機能です。
挙動としては、obj.func(...)が存在しない場合、func(obj, ...)の呼び出しを試みるという感じです。
※逆に、obj.func(...)func(obj, ...)と記述することはできません。

D
// これが
writeln("hello,world!");

// こうなる
"hello,world!".writeln;

何がうれしいのか

疑似的なメンバ関数により、ユーザー定義型をシンプルに記述できてうれしい!

UFCSは、オブジェクトの疑似的なメンバ関数のような見た目なので

D
import std.stdio;

/**
 * 大量のヘルパー的なメンバ関数で、クラスの構造がわかりづらくなる
 * また、クラスの定義とヘルパー的なメンバ関数の間に、依存関係ができてしまう
 * かといってヘルパー関数としてクラス外に出すと、関数の呼び出しが使いづらい
 */
class C1
{
    // 型定義がごちゃごちゃ
    private int n_;
    this()          { }
    void helper1()  { "あんなこと".writeln; }
    void helper2()  { "こんなこと".writeln; }
    void set(int n) { n_ = n; }
    void helper3()  { "そんなこと".writeln; }
    int  get()      { return n_; }
    void helper4()  { "どんなこと".writeln; }
}
// C1型に依存
void helper5(C1 x)  { "わいわい".writeln; }
void helper6(C1 x)  { "がやがや".writeln; }

/// そこでUFCS!!
class C2
{
    // 型定義はシンプルに
    private int n_;
    this()          { }
    void set(int n) { n_ = n; }
    int  get()      { return n_; }
}
// 依存性は抑えめに
void helper1(T)(T x) if (is(T : C2)) { "あんなこと".writeln; }
void helper2(T)(T x) if (is(T : C2)) { "こんなこと".writeln; }
void helper3(T)(T x) if (is(T : C2)) { "そんなこと".writeln; }
void helper4(T)(T x) if (is(T : C2)) { "どんなこと".writeln; }
void helper5(T)(T x) if (is(T : C2)) { "わいわい".writeln;   }
void helper6(T)(T x) if (is(T : C2)) { "がやがや".writeln;   }

void main()
{
    // 通常
    auto c1 = new C1;
    c1.set(42);
    writeln(c1.get);
    c1.helper1;   // 型定義にこれ必要?
    c1.helper2;   //  〃
    c1.helper3;   //  〃
    c1.helper4;   //  〃
    helper5(c1);  // 仲間外れでつらい
    helper6(c1);  //  〃

    // UFCS!!
    auto c2 = new C2;
    c2.set(42);
    c2.get.writeln;
    c2.helper1;   // まるでメンバ関数!!
    c2.helper2;   //   〃
    c2.helper3;   //   〃
    c2.helper4;   //   〃
    c2.helper5;   //   〃
    c2.helper6;   //   〃
}

型定義の本体はシンプルな内容に留め、かつ疑似的なメンバ関数として同様の処理を提供できます!
(おそらくこれが本来のUFCSの目的?)

関数をどんどんつなげて記述できるチェイン記法がうれしい!

UFCSと、引数のない関数は、関数呼び出しの()を省略できるというD言語の仕様を組み合わせると

D
int hoge(int n) { return n + 2; }
int fuga(int n) { return n * 3; }
int piyo(int n) { return n / 5; }

void main()
{
    // 変数xの値をhogeした後、fugaして、piyoした結果を表示したい
    auto x = 68;

    // 通常の記述
    import std.stdio;
    writeln(piyo(fuga(hoge(x))));

    // ↓
    writeln(piyo(fuga(x.hoge)));
    // ↓
    writeln(piyo(x.hoge.fuga));
    // ↓
    writeln(x.hoge.fuga.piyo);
    // UFCS !!
    x.hoge.fuga.piyo.writeln;
}

こんな感じで、UFCSチェインして気持ちよく記述できます!
(これがしたくて使っていると思う!)

その他の効能

TODO: 適当なコード片をサンプルとして書き足す
[2015.10.17サンプル追記]

  • ある程度複雑な処理をワンライナーで記述できる!
  • ネストされた()が減ることで、入力が楽で間違えにくい!
  • 視線の方向(左から右)に記述でき、処理の流れが追いやすい!

元ネタ:「1時間以内に解けなければプログラマ失格となってしまう5つの問題が話題に
参考にしました→「Java8で「ソフトウェアエンジニアならば1時間以内に解けなければいけない5つの問題」の5問目を解いてみた

D
import std.range     : iota, array;
import std.conv      : to;
import std.algorithm : map, filter, sum, each;
import std.array     : join, split;
import std.stdio     : writeln;

R combination(R, R sep = [" +", " -", ""])(R elem)
{
    return elem.length < 2
        ? elem
        : sep
            .map!(op => combination(elem[1 .. $])
                  .map!(x => elem[0] ~ op ~ x))
            .join;
}

void main()
{
    iota(1, 10).array
        .to!(string[])
        .combination
        .filter!(a => a.split(" ").to!(int[]).sum == 100)
        .each!(a => a.writeln);
}
  • 関数設計時の引数順序のゆるい指針: とりあえずUFCSでつながるように!

はまり所

  • メンバ関数、ネスト関数、ラムダ式はUFCSできない
D
class C
{
    // メンバ関数
    static int sfoo(int n) { return n * 2; }
           int nfoo(int n) { return n * 2; }
}

void main()
{
    // ネスト関数
    static int sbar(int n) { return n * 2; }
           int nbar(int n) { return n * 2; }
    // ラムダ式
    static auto sbaz = (int n) => n * 2;
           auto nbaz = (int n) => n * 2;

    import std.stdio;
    //1.C.sfoo.writeln;        // NG
    //1.(new C).nfoo.writeln;  // NG
    //1.sbar.writeln;          // NG
    //1.nbar.writeln;          // NG
    //1.sbaz.writeln;          // NG
    //1.nbaz.writeln;          // NG
}

[2015.10.17追記]
→UFCSできない場合、std.functionalpipecomposeでつなげる方法もあるそうです

  • 多段階にはUFCSできない (当たり前)
D
int foo(int a, int b, int c) { return a + b + c; }

void main()
{
    import std.stdio;
    auto a = 1, b = 2, c = 3;
    a.foo(b, c).writeln;    // OK
    //b.a.foo(c).writeln;   // NG
    //c.b.a.foo.writeln;    // NG
}
nak2yoshi
D言語勉強中/ネコが好き
https://twitter.com/nak2yoshi
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
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  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
ユーザーは見つかりませんでした