Ceylon : Quick introduction 邦訳(2)



Quick introductionの邦訳その2です。

ミックスイン継承

javaと同じように、Ceylon にはクラスとインターフェースがあります。クラスは単一のスーパークラスと任意の数のインターフェースを継承できます。インターフェースは任意の数の他のインターフェースを継承でき、Object 以外の、クラスを拡張することはできません。Java とは異なり、インターフェースは具象メンバを定義することができます。つまり、Ceylon はミックスイン継承(mixin inheritance)と呼ぶ、制限された多重継承をサポートするということになります。

interface Sized {
 
    shared formal Integer size;
 
    shared Boolean empty {
        return size==0;
    }
}
 
interface Printable {
 
    shared void printIt() {
        print(this);
    }
}
 
object empty satisfies Sized & Printable {
 
    shared actual Integer size {
        return 0;
    }
}


Ceylon において、インターフェースとクラスの違いは、インターフェースが状態を持たないことです。つまり、インターフェースは他のオブジェクトの参照を直接持ことはありません。これは、初期化ロジックを持たず、直接インスタンス化されることがないということになります。Ceylon はどんな種類のスーパータイプの「線形化」の実行する必要性を避けています。


多様属性(Polymorphic attributes)

Ceylon には、伝統的な感覚で言うフィールドを持っていません。その代わり属性は多様的で、オブジェクト指向言語におけるメソッソのように、サブクラスにて派生されます。

属性が単純な値である場合は

String name = firstName + " " + lastName;


ゲッターの場合は

String name {
    return firstName + " " + lastName;
}


また、ゲッターとセッターのペアの場合は

String name {
    return fullName;
}
 
assign name {
    fullName := name;
}

のようになります。


Ceylon ではクラスの状態は、常にクラスのクライアントから完全に抽象化されるため、つまらないゲッターとセッターを書く必要はありません。


型安全な null と安全な型縮小

Ceylon では NullPointerException やそれに類するものはありません。Ceylon は null と成りうる値やメソッドが null を戻す場合には明示的に宣言することを要求します。例えば、name が null となる場合があれば、次のように宣言しなければなりません。

String? name = ...


この記述は、実際には以下の省略系です。

String|Nothing name = ...

String? 型の属性は、実際にはStringのインスタンスか、null値(Nothing クラスのインスタンスのみ)を要求します。Ceylon は、特別な if (exists ...) 構成を使い、null 値をチェックすることなしには String? 型の値を使うことができません。、

void hello(String? name) {
    if (exists name) {
        print("Hello, " name "!");
    }
    else {
        print("Hello, world!");
    }
}


同様に、Ceylon には ClassCastException もありません。その替りに if (is ...) と case (is ...) により1手順で型の確認と型の縮小化が行えます。実際、前述のコードは、以下の記述のショートカットでもあります。

void hello(String? name) {
    if (is String name) {
        print("Hello, " name "!");
    }
    else {
        print("Hello, world!");
    }
}

列挙サブタイプ(Enumerated subtypes)

オブジェクト指向プログラミングにおいて、型の全てのサブタイプを扱うような長い switch ステートメントを書くことは、たいてい良くない慣習とされています。これは拡張性に欠けてしまうのです。新しいサブタイプを加えると、たちまち switch ステートメントが壊れてしまいます。そのためオブジェクト指向のコードでは、しばしばサブクラスで派生できるように、親クラスに抽象メソッドを使うようにリファクタリングされます。

しかしながら、この種のリファクタリングが適さない種類の問題があります。大抵のオブジェクト指向言語では、この種の問題は visitor パターンを使うことで解決しています。不幸なことに、visitor クラスは switch より冗長であり、クラスの拡張性も犠牲になっています。一方で visitor パターンには大きな優位性があるのも事実であり、新しいサブタイプを加え、visitor に反映を忘れた場合にはコンパイルエラーとして検知できます。

Ceylon はこの2つのジレンマを解消します。親型を定義する時に、サブ型のリストを列挙して指定することができます。

abstract class Node() of Leaf | Branch {}


そして我々は、全ての列挙サブ型を扱う switch文 を書くことができます。

Node node = ... ;
switch (node)
case (is Leaf) { ... }
case (is Branch) { .... }


ここで、Node のサブ型を加えた場合、我々は Node の定義条件を追加しなければならず、新しいサブ型を処理しない全ての switch 文についてエラーを報告します。



次回に続く...