Specript Language Reference rev.0.7.0 www.specript.org

目次 | << 前章 | 次章 >>

7.spec

Specriptでは、言語組み込みのタイプ(integer、decimal、real、boolean、string、 date、timestamp、record、list、map)をベースとして、それらから派生させて新たな タイプをユーザー定義することができます。また、定義済みのユーザー定義タイプを ベースとして、さらに派生させたタイプをユーザー定義することも可能です。このように 新たなタイプを派生的にユーザー定義することを「spec定義」と称します。

なお、Specriptでは、用語「spec」を、言語組み込みのタイプとユーザー定義された タイプの両者を総称するものとして用いています。

7−1.spec定義

spec定義は、

spec名 定義するspecの名称
specベース部/ベースspec 定義するspecがベースとするspec(言語組み込みのタイプ、または他の定義済みspecの名称)
spec派生部 ベースとするspecから派生的に定義する内容

の3点から成ります。

「specベース部(もしくは、「ベースspec」)」には、プリミティブタイプを含む 言語組み込みのタイプのいずれか、もしくは、他の定義済みspecを指定できます。 ベースspecの記述は省略することができます。その場合"record"を 指定したものと認識されます。

3点目の「spec派生部」に定義できる内容は大きく二つあります。一つは要素定義、 もう一つは制約定義です。要素定義、制約定義のそれぞれで、要素property定義、 要素function定義、制約property定義、制約function定義が可能です。

【spec定義の書式(EBNF)】

<spec定義> ::=

( "public" | "private" )? "spec" <spec名> ":" ( <specベース部> )? "{" <spec派生部> "}"

<specベース部> ::=

( <言語組み込みのタイプ> | <他の定義済みspec名> )

<spec派生部> ::=

( <要素property定義> | <要素function定義> | <ブロック外制約property定義> | <ブロック外制約function定義> | <制約定義ブロック> )*

<要素property定義> ::=

"property" <property名> ( ":" ( "not" "null" )? <propertyのspec> )? ( "=" <初期化式> )? ( "#" <制約違反式> )? ";"

<要素function定義> ::=

"function" <function名> ( "(" <引数定義リスト> ")" )? ( ":" <functionの返り値のspec> )? "=" ( <ローカルproperty定義リスト> )? <本体式> ";"

<ブロック外制約property定義> ::=

"constraint" <制約property定義>

<ブロック外制約function定義> ::=

"constraint" <制約function定義>

<制約定義ブロック> ::=

"constraint" "{" ( <制約property定義> | <制約function定義> )* "}"

<制約property定義> ::=

"property" <property名> ( ":" <propertyのspec> )? ( "=" <初期化式> )? ( "#" <制約違反式> )? ";"

<制約function定義> ::=

"function" <function名> ( ":" "boolean" )? "=" ( <ローカルproperty定義リスト> )? <本体式> ( ( "#" <制約違反式> ) | ( "##" <制約違反式参照名> ) )? ";"

【記述例】
  • recordより派生したspec定義
  •     // 要素propertyがあります。
        spec 期間仕様 : record {
            property 開始日:date;
            property 終了日:date;
        }
    
        // 要素propertyと要素functionがあります。
        spec 名簿仕様 : {  // ベースspecが省略されているので"record"と認識されます。
            property 名前:string;
            property 誕生日:date;
            function 年齢:integer =
                ((Today.Month < 誕生日.Month) || (Today.Month == 誕生日.Month && Today.Day < 誕生日.Day))
                    ? Today.Year - 誕生日.Year - 1
                    : Today.Year - 誕生日.Year
                ;
        }
        
  • プリミティブタイプより派生したspec定義
  •     // 要素functionがあります。
        spec 郵便番号仕様 : string {
            function 前3桁:string = this.substring(0, 3);
            function 後4桁:string = this.substring(3);
        }
        
  • 制約function定義あり
  •     spec 期間仕様 : record {
            property 開始日:date;
            property 終了日:date;
            constraint function 開始日は終了日前 =
                (開始日 != null && 終了日 != null) ? 開始日 < 終了日 : true;
        }
    
        spec 郵便番号仕様 : string {
            function 前3桁:string = this.substring(0, 3);
            function 後4桁:string = this.substring(3);
            constraint function 長さ7文字 = this.length == 7;
            constraint function 数字のみ = this =~ "[0-9]*";
        }
        
  • 制約property定義あり
  •     spec 金額仕様 : integer {
            constraint property 上限金額:integer;
            constraint function 上限金額以内 = 上限金額 != null ? this <= 上限金額 : true;
        }
        

    7−1−1.要素property定義

    specの要素としてpropertyやfunctionを追加的に定義することができます。これはクラスに インスタンス変数やメソッドを定義することに似ています。

    recordから派生して定義されるspecにおいてのみ、spec要素としてproperty定義が可能です。 要素property定義とはrecordの要素を具体的に定めるものです。

    要素property定義の書式は、spec要素でない一般のproperty定義と同じです。ただし、 初期化式を省略することができます。

    初期化式のない要素propertyは、その要素propertyが定義されるspecを使用した property定義等において、値が外部的に設定されることを想定したものです。初期化式がなく、 property定義等において値が外部的に指定されることもなかった場合、要素propertyの値は "null"となります。

    初期化式のある要素propertyへは、その要素propertyが定義されるspecを使用した property定義等において、値を新たに設定することはできません。

    【例】
        // ベースspecが省略されているのでspec "S"はrecordからの派生
        spec S : {
            property e1:integer;      // 初期化式がありません。
            property e2:integer = 2;  // 初期化式があります。
        }
    
        // "S"を使用したproperty定義にて、初期化式のない要素property "e1"への値の設定が
        // あります。
        property p:S = {e1 = 1};  // p == {e1 = 1, e2 = 2} となります。
    
        // "S"を使用したproperty定義にて、初期化式のない要素property "e1"への値の設定が
        // ありません。
        property q:S = {};  // q == {e1 = null, e2 = 2} となります。
    
        // "S"を使用したproperty定義にて、初期化式のある要素property "e2"への値の設定が
        // あるのでエラーとなります。
        property r:S = {e1 = 1, e2 = 2};  // コンパイル時エラー
        

    また、初期化式のない要素propertyに対して設定しようとする値に関するspecが、 要素propertyのspecに対して互換性がないとき、コンパイル時エラーまたは実行時エラーと なります。

    specの互換性評価の詳細について第3節を参照してください。
    【例】
        // spec "S"はrecordからの派生
        spec S : {
            property e1:integer;      // 初期化式がありません。
            property e2:integer = 2;  // 初期化式があります。
        }
    
        // "S"を使用したproperty定義にて、要素property "e1"へ設定される値のspecが定義と
        // 合致しません。
        property p:S = {e1 = "a"};  // コンパイル時エラー
        

    定義されたspecに存在しない要素propertyを設定しようとすると、コンパイル時エラーまたは 実行時エラーとなります。

    【例】
        // spec "S"はrecordからの派生
        spec S : {
            property e1:integer;      // 初期化式がありません。
            property e2:integer = 2;  // 初期化式があります。
        }
    
        // "S"を使用したproperty定義にて、要素property "e3"は定義に存在しません。
        property p:S = {e3 = 3};  // コンパイル時エラー
        

    要素propertyの初期化式には、一般のproperty定義同様、任意の式を記述できます。

    詳細について、本節第3項を参照してください。

    要素propertyのspecは、一般のproperty定義同様、省略することができます。この場合、 要素propertyのspecは暗黙に初期化式の評価結果が表すspecであるとされます。

    7−1−2.要素function定義

    要素functionは、プリミティブタイプを含む全てのタイプから派生して定義されるspecに おいて定義可能です。要素functionはクラスのメソッドとほぼ同じ位置付けです。

    【例】
        // integerから派生定義したspec
        spec X : integer {
            function distance(x:integer):integer = abs(x - this);
        }
    
        // その使用
        property p:X = 8;
        property q:integer = p.distance(10);  // q == 2 となります。
    
        // stringから派生定義したspec
        spec 郵便番号仕様 : string {
            function 前3桁:string = this.substring(0, 3);
            function 後4桁:string = this.substring(3);
        }
    
        // その使用
        property zip:郵便番号仕様 = "1234567";
        property zip3:string = zip.前3桁;  // zip3 == "123" となります。
        property zip4:string = zip.後4桁;  // zip4 == "4567" となります。
    
        // recordから派生定義したspec
        spec 名簿仕様 : record {
            property 名前:string;
            property 誕生日:date;
            function 年齢:integer =
                ((Today.Month < 誕生日.Month) || (Today.Month == 誕生日.Month && Today.Day < 誕生日.Day))
                    ? Today.Year - 誕生日.Year - 1
                    : Today.Year - 誕生日.Year
                ;
        }
        

    要素function定義の書式は、spec要素でない一般のfunction定義同様です。引数のspec、 引数のデフォルト値式、引数なしの場合の扱いなど、一般のfunctionと同じです。

    要素propertyと引数なしの要素functionでは、見た目や扱いがほとんど同一となります。 ただし、Java側とデータ交換が行われるとき、propertyとして定義された要素はJava側と 交換されますが、functionとして定義された要素はJava側と交換されません。

    7−1−3.要素propertyの初期化式、要素functionの本体式の記述

    要素propertyの初期化式内、要素functionの本体式内では、以下のproperty、functionが 参照、呼び出し可能です。

    自spec定義内の、他の要素propertyや要素function
    自namespace内に定義されるpropertyやfunction
    所属namespaceから可視であるpropertyやfunction
    【例】
        // 要素function "年齢"の本体式では、自spec定義内の他の要素property "誕生日"を
        // 参照しています。
        spec 名簿仕様 : record {
            property 名前:string;
            property 誕生日:date;
            function 年齢:integer =
                ((Today.Month < 誕生日.Month) || (Today.Month == 誕生日.Month && Today.Day < 誕生日.Day))
                    ? Today.Year - 誕生日.Year - 1
                    : Today.Year - 誕生日.Year
                ;
        }
        
    【例】
    [X.spec]
        namespace A;
    
        property p:string = "xyz";
        
    [Y.spec]
        namespace B;
    
        public function f(a:string):string = ...;
        
    [Z.spec]
        namespace A::N;
        using A;
        using B;
    
        // 要素property "e"の初期化式内では、namespace "A"に定義されるproperty "p"を参照し、
        // namespace "B"に定義されるfunction "f"を呼び出しています。
        spec S : {
            property e:string = f(p);
        }
        

    要素propertyの初期化式内、要素functionの本体式内では、自身を指し示す予約語"this"を 使用できます。

    【例】
        // 自spec定義内の他の要素propertyや要素functionを指し示すとき、"this"に対する
        // 要素アクセスとして記述できます。(この場合の"this"は省略可能です。)
        spec 名簿仕様 : record {
            property 名前:string;
            property 誕生日:date;
            function 年齢:integer =
                ((Today.Month < this.誕生日.Month) || (Today.Month == this.誕生日.Month && Today.Day < this.誕生日.Day))
                    ? Today.Year - this.誕生日.Year - 1
                    : Today.Year - this.誕生日.Year
                ;
        }
    
        // プリミティブタイプから派生定義したspecでは、"this"はそれ自身の値をも表します。
        spec 郵便番号仕様 : string {
            function 前3桁:string = this.substring(0, 3);
            function 後4桁:string = this.substring(3);
        }
        

    自spec定義内の他の要素propertyや要素functionの名称が、spec要素でないpropertyや functionと同一だった場合、要素propertyや要素functionの参照、呼び出しが優先されます。

    【例】
        namespace A;
    
        property p = ...;
    
        function f(a:string):string = ...;
    
        // 要素function "e"の本体式で参照、呼び出ししている"f"と"p"は、
        // いずれも自spec定義内の要素である"f"や"p"を参照、呼び出しすることとなります。
        spec S : {
            property p:string;
            function f(a:string):string = ...;
            function e:string = f(p);
        }
        

    上記例において、namespace "A"に定義されるproperty、functionの参照、呼出しを 行いたい場合は、下記のようにnamespace付き‘フルネーム’とすることで可能となります。

        namespace A;
    
        property p = ...;
    
        function f(a:string):string = ...;
    
        spec S : {
            property p:string;
            function f(a:string):string = ...;
            function e:string = A::f(A::p);
        }
        

    7−1−4.制約function定義

    制約とは、データのとりうる値の範囲について制限、条件を加えるものです。例えば、 stringのデータに対して最大長や受け入れ可能な文字種を定めたり、integerのデータに 対して最小値、最大値を定めたり、recordのデータに対して各要素間で値が充足すべき条件を 定めたりすることができます。

    制約functionには、このような充足すべき条件を表す式を記述します。制約functionは、 「引数なしでbooleanを返すfunction」として定義します。制約functionが"true"を 返したときその制約functionが表す制約を満たしたと理解されます。"false"を 返したときは制約を満たしていないと解釈されます。

    制約functionの返り値のspecは常に"boolean"でなければなりません。この 返り値のspecは、明記しても省略しても構いません。省略した場合でも、本体式の表すspecは "boolean"でなければなりません。

    制約function定義の本体式では、自spec定義内の要素propertyや要素functionを参照、 呼び出ししつつ、その制約functionが表すべき固有の条件判断式を構築します。

    【例】
        // 制約function "長さ7文字"と"数字のみ"が定義されています。
        // spec "郵便番号仕様"であるデータは、これらの制約functionが"true"を返すような
        // 値のみ取ることができます。
        spec 郵便番号仕様 : string {
            function 前3桁:string = this.substring(0, 3);
            function 後4桁:string = this.substring(3);
    
            // "this"の長さが"7"であると"true"を返すような式が制約functionの本体式と
            // なっています。
            constraint function 長さ7文字 = this.length == 7;
    
            // "this"が正規表現"[0-9]*"にマッチすると"true"を返すような式が制約functionの
            // 本体式となっています。
            constraint function 数字のみ = this =~ "[0-9]*";
        }
    
        // 制約function "開始日は終了日前"が定義されています。
        // spec "期間仕様"であるデータは、この制約functionが"true"を返すような値のみ
        // 取ることができます。
        spec 期間仕様 : record {
            property 開始日:date;
            property 終了日:date;
    
            // "開始日"、"終了日"がともに"null"でないとき、"開始日"が"終了日"より
            // 小さい場合に限り"true"を返すような式が制約functionの本体式となっています。
            constraint function 開始日は終了日前 =
                (開始日 != null && 終了日 != null) ? 開始日 < 終了日 : true;
        }
        

    実行時、定義された制約functionを満たさない値が設定された場合、「制約違反」が 発生します。全ての定義された制約functionを満たした場合のみ「制約違反」が発生しません。

    「制約違反」発生時の振る舞いについて第10章第2節を 参照してください。

    また、specの互換性評価に関する本章第3節も参照してください。

    制約functionの本体式内では、要素function同様、以下のproperty、functionが参照、 呼び出し可能です。

    自spec定義内の、他の要素propertyや要素function
    自namespace内に定義されるpropertyやfunction
    所属namespaceから可視であるpropertyやfunction

    ただし、他の制約functionを呼び出すことはできません。

    7−1−5.制約property定義

    制約functionでは、データの充足すべき条件を常に式として記述しなければなりません。 しかし、条件式は同一だがその条件式に対するパラメータ値だけが異なるというケースが ままあります。

    【例】
        // 制約functionで値の上限値を定めています。
        spec 金額仕様(上限10万) : integer {
            constraint function 上限金額以内 = this <= 100000;
        }
    
        // 別の上限値を定めるとき、別途specを定義し、異なる上限値による制約functionを
        // 定義しています。
        spec 金額仕様(上限100万) : integer {
            constraint function 上限金額以内 = this <= 1000000;
        }
        

    上記例のような場合、制約propertyを用いることで、spec定義を一本化することができます。

    【例】
        spec 金額仕様 : integer {
    
            // 制約propertyを定義します。
            constraint property 上限金額:integer;
    
            // 制約functionの本体式は、制約property "上限金額"を参照するかたちになっています。
            constraint function 上限金額以内 = 上限金額 != null ? this <= 上限金額 : true;
    
        }
    
        // "金額仕様"を派生した匿名specで、制約propertyに初期化式を追加しています。
        property 金額(上限10万):金額仕様 {
            constraint property 上限金額:integer = 100000;
        } = ... ;
    
        //
        // "金額仕様"を派生した匿名specで、制約propertyに異なる初期化式を追加しています。
        property 金額(上限100万):金額仕様 {
            constraint property 上限金額:integer = 1000000;
        } = ... ;
        

    制約propertyはただ定義されるだけで、それ自体に機能はありません。制約propertyと ペアで、その制約propertyを参照するような条件式を本体式として持つ制約functionを 定義します。派生specにおいて制約propertyに初期化式を付与することで、実質的に 制約functionへパラメータを引き渡すことができるようになります。

    制約propertyを用いる場合も、「制約違反」が発生するのは、あくまでもその制約propertyを 参照するような条件式を本体式として持つ制約functionにおいてです。

    要素propertyを用いると、そのspecを使用したproperty定義等において値が外部的に設定 される可能性があります。制約propertyへは派生specで初期化式を追加する以外に値を 設定することがでず、specを使用したproperty定義等において外部的に設定されるおそれが ありません。

    ここに示した例のように、制約property使用時、その制約propertyへの値の設定は、 派生した匿名specによって行われるのが通常です。匿名specについては本章 第2節を参照してください。また、specの派生については 次項を参照してください。

    7−1−6.specの派生

    spec定義では、言語組み込みのタイプをベースとする以外に、他の定義済みspecをベースとして、 さらに派生定義することができます。

    【例】
        // "B"はrecordをベースとするspecです。
        spec B : {
            property e1:integer;
            property e2:integer = 20;
            function f1(x:integer):integer = e2 * x;
            constraint function c1 = e1 < e2;
        }
    
        // "D"は"B"をベースとするspecです。
        // 新たな要素property、要素function、制約functionを追加的に定義しています。
        spec D : B {
            property e3:integer;
            function f2:integer = e1 + e3;
            constraint function c2 = e2 < e3;
        }
        

    上記例において、spec "D"には、下記の要素、制約が全て存在する こととなります。

    property e1:integer;
    property e2:integer = 20;
    property e3:integer;
    function f1(x:integer):integer = e2 * x;
    function f2:integer = e1 + e3;
    constraint function c1 = e1 < e2;
    constraint function c2 = e2 < e3;

    特に、spec "D"では、ベースspecで定義される制約function "c1"と spec "D"の定義で新たに定義された制約function "c2"の 両方を満たす必要があります。

    specの派生定義では下記のことが可能です。

    (recordをベースとするspecにおいて、)要素propertyを追加する。
    要素functionを追加する。
    制約propertyを追加する。
    制約functionを追加する。
    初期化式の無い要素propertyに初期化式を追加する。
    初期化式の無い制約propertyに初期化式を追加する。
    初期化式の無い要素propertyのspecをそれの派生specへ再定義する。
    初期化式の無い要素propertyのspecに"not null"制約を追加する。

    一方、下記を行うことはできません。

    初期化式のある要素propertyを、別の初期化式で再定義する。
    要素functionの本体式を、別の本体式で再定義する。
    初期化式のある制約propertyを、別の初期化式で再定義する。
    制約functionの本体式を、別の本体式で再定義する。
    要素propertyのspecの"not null"制約を除去する。
    【例】
        // "B"はrecordをベースとするspecです。
        spec B : {
            property e1:integer;
            property e2:integer = 20;
            property e3:string;
            function f1(x:integer):integer = e2 * x;
            constraint property upper_limit:integer;
            constraint property lower_limit:integer = 0;
            constraint function c1 = upper_limit != null ? e1 <= upper_limit : true;
            constraint function c2 = lower_limit != null ? e1 >= lower_limit : true;
        }
    
        // "D"は"B"をベースとするspecです。
        spec D : B {
    
            // 初期化式の無い要素propertyに初期化式を追加することができます。
            property e1:integer = 10;
    
            // 初期化式のある要素propertyを、別の初期化式で再定義することはできません。
            property e2:integer = 200;  // コンパイル時エラー
    
            // 要素functionの本体式を再定義することはできません。
            function f1(x:integer):integer = e2 / x;  // コンパイル時エラー
    
            // 初期化式の無い制約propertyに初期化式を追加することができます。
            constraint property upper_limit:integer = 100;
    
            // 初期化式のある制約propertyを、別の初期化式で再定義することはできません。
            constraint property lower_limit:integer = -100;  // コンパイル時エラー
    
            // 制約functionの本体式を再定義することはできません。
            constraint function c1 = upper_limit != null ? e2 <= upper_limit : true;  // コンパイル時エラー
    
            // 初期化式の無い要素propertyのspecを派生specへ再定義することができます。
            // "C"はstringをベースとして別途定義されたspecです。
            property e3:C;
    
        }
    
        // spec "C"の定義です。制約functionが追加されています。
        spec C : string {
            constraint function c = this =~ "[0-9]*";
        }
        

    7−2.匿名spec

    7−2−1.一般的な説明

    下記に示す、

    property(要素property、制約property、functionローカルpropertyを含む)定義に おける、propertyのspec
    function(要素function、制約functionを含む)定義における、引数と返り値のspec

    など、specを指定する箇所では、言語組み込みのタイプおよびユーザー定義された任意の specを指定できますが、さらに‘ad-hoc’に構成される「匿名spec」を指定することが できます。

    匿名specとは、spec定義におけるspecベース部とspec派生部を、spec指定する箇所に 直接記述する記法です。

    匿名specのspec派生部においては、明示的なspec定義におけるspec派生部にて定義可能な 内容が全て定義可能です。

    【例】
        // これはspecが"string"であるproperty "p"の定義です。
        property p:string = "123";
    
        // これは、specが「制約function "c"を持つ、stringをベースとした匿名の派生spec」
        // であるproperty "q"の定義です。
        property q:string {
            constraint function c = this ~= "[0-9]*";
        } = "123";
    
        // recordをベースとした匿名specも可能です。
        property r:{
            property e1:integer;
            property e2:integer;
            constraint function c = (e1 != null && e2 != null) ? e1 < e2 : true;
        } = { e1 = 3, e2 = 4 };
        

    匿名specは、匿名specを構成して指定した箇所でのみ使用できるspecとなります。 その匿名specを他のproperty、引数、返り値に対して使用することはできません。

    7−2−2.ネストした匿名spec

    匿名specは、specの要素propertyや要素functionの定義においても使用することができます。

    【例】
        // spec定義時、要素の定義で匿名specを使用することができます。
        spec S : {
    
            // 要素property "e1"のspecが、stringから派生した匿名specとなっています。
            property e1:string {
                constraint function c = this ~= "[0-9]*";
            };
        }
    
        // 匿名specの要素の定義で匿名specを使用することができます。
        property p:{
    
            // 要素property "e1"のspecが、stringから派生した匿名specとなっています。
            property e1:string {
                constraint function c = this ~= "[0-9]*";
            };
    
            property e2:string;
    
        } = { e1 = "1", e2 = "a" };
        

    ネストした匿名spec内では、定義上の親specの要素を参照することができます。

    【例】
        // property "p"のspecは匿名specです。
        property p:{
    
            // 要素property "e1"が定義されます。
            property e1:integer;
    
            // 要素property "e2"が定義されます。"e2"はintegerから派生した匿名specと
            // なっています。
            property e2:integer {
    
                // "e2"のintegerから派生した匿名specには要素function "f"が定義され、
                // その本体式ではproperty "p"の匿名specの要素property "e1"を参照しています。
                function f(a) = a * e1;
    
            };
        }
        

    7−3.specの互換性

    specの互換性とは、ある値もしくは評価結果が、指定されるspecに対して適合するか否かを 示す概念です。例えば、property定義における初期化式の評価結果が、指定されるspecに 対して互換性が‘無い’場合、そのproperty定義はコンパイル時エラーまたは実行時エラーと なります。

    また、is演算子では、互換性の有無をエラーを起こさずにチェックすることができます。

    is演算子については第8章第6節第10項を 参照してください。

    以下に、「互換性が無い」評価される条件を一覧します。一覧のいずれか一つを満たすとき、 specの互換性が無いと評価されます。

    条件 条件を満たさなかったときに発生する実行時エラー
    (数値系タイプ同士の場合を除いて、)ルートタイプが異なる T.B.D.
    specに定義される制約を満たさない T.B.D.
    recordの派生specにおいて、定義に存在しない要素propertyがある T.B.D.
    recordの派生specにおいて、要素propertyがこの一覧に示される条件に 引っかかる T.B.D.

    目次 | << 前章 | 次章 >>


    (c)2007-2008, Specript Development Team Last Updated: 2008-03-06