KLの型システム

多くの言語と異なり、KLは動的な型システム ―Fabricの環境から引き継ぐ を持ちます。複合型の登録は、動的言語(例えば JavaScriptや Python)で実行されているFabric環境を通じ行われます。これらの型は自動的にその実行環境においてKLプログラムから利用可能となります。ただし、このことは言語のセマンティクス(プログラム意味論)に影響をあたえるものではありません。つまりKLの型システムは依然として、純粋に、その言語自体の観点から説明できます。

多くの他のプログラミング言語同様にKLには、他の型へと継承できる定められた数の基本型だけでなく、異なる種の派生型の数々もサポートします。

基本型(Base Types)

KLでの基本型は以下:

Boolean
true もしくは false のいずれかをとる
UInt8
8-bit 符号なし整数(unsigned integer)
Byte
UInt8 のエイリアス(別名)
SInt8
8-bit 符号つき整数(signed integer)
UInt16
16-bit 符号なし整数
SInt16
16-bit 符号つき整数
UInt32
32-bit 符号なし整数
Count
UInt32 へのエイリアス。基数を表すために使用される。
Index
UInt32 へのエイリアス。序数を表すために使用される。
Size
UInt32 へのエイリアス
SInt32
32-bit 符号つき整数
Integer
SInt32 へのエイリアス
UInt64
64-bit 符号なし整数
DataSize
UInt64 へのエイリアス。メモリブロックのサイズを表すために使用される
SInt64
64-bit 符号つき整数
Float32
32-bit IEEE浮動小数点数
Scalar
Float32 へのエイリアス
Float64
64-bit IEEE浮動小数点数
String
ゼロ個以上の文字の連なり

論理型( Boolean Type)

Boolean 型の式の値は、論理真もしくは論理偽です。以下の特徴を持ちます:

  • 定数 true 及び false はそれぞれ論理値である真、偽の Boolean です。
  • 他の型から Boolean 型へのキャスト:
    • 全整数型(例えば UInt32, Byte)は値が 0 ではない時のみ true となります。
    • 全浮動小数点数型(すなわち Float32 and Float64)は値が 0.0 もしくは -0.0 と等しくない場合にのみ true となります。
    • String の値はその長さが 0以上 の場合のみ true となります。
    • 配列と辞書型はカラでない場合のみ true となります。
    • 構造体はそのままでは Boolean 型へキャストできません。キャスト可能にするには引数パラメータにキャストを行いたい構造体をとる Boolean コンストラクタを実装します。 コンストラクタ を参照してください。
  • 演算子
    • 算術演算子 (2項演算子 +, -, *, /, % だけでなく 単独の - and +) はいずれも Boolean の値には無効です。
    • Boolean の値には比較演算子 ==!= のみ有効です。
    • 全2項論理演算子(|, &, ^ and ~) は Boolean 値で有効です。値を単一のビットであるかのように扱います。

以下に Boolean 型の使用例を示します:

operator entry() {
  Boolean a = true;
  report(a);
  Boolean b = a & false;
  report(b);
  report(a != b);
}

出力:

true
false
true

整数型(Integer Types)

UInt8, SInt8, UInt16, SInt16, UInt32, SInt32, UInt64, SInt64 型、そしてエイリアス (Byte, Integer, Size, Count, Index, DataSize), は総称して integer types として知られ、全ての整数を表現します。

UInt8
8-bit 符号なし整数(unsigned integer)
Byte
UInt8 のエイリアス(別名)
SInt8
8-bit 符号つき整数(signed integer)
UInt16
16-bit 符号なし整数
SInt16
16-bit 符号つき整数
UInt32
32-bit 符号なし整数
SInt32
32-bit 符号つき整数
Integer
SInt32 へのエイリアス
UInt64
64-bit 符号なし整数
SInt64
64-bit 符号つき整数
Count
UInt32 へのエイリアス。通常 Size は配列中の要素の数量の数え上げにしようされる。
Index
UInt32 へのエイリアス。通常 Index は配列のインデックスに使用される。
DataSize
UInt64 へのエイリアス。メモリブロックのサイズを表すために使用され、 dataSize メッソドの返り値の型として使用される。
Size
UInt32 へのエイリアス。

整数型の振る舞い:

  • arithmetic, logical, bitwise 演算子は整数型に対し期待通り動作します。
  • 整数型の定数は s32SInt32, u64UInt64, などのように接尾語をともない型付けされます。接尾語を付さない場合、 SInt32 となります。参照 整数定数.

整数型の使用例:

operator entry() {
  Byte b = 64;
  report(b);
  Size s = 45 * Size(b) + 32;
  report(s);
  Integer i = -75 * Integer(s) + 18;
  report(i);
}

出力:

64
2912
-218382

整数型のアトミックメソッド

整数型は組み込み メソッド を持ちます。整数型の値に対しアトミックな操作(不可分操作、atomic operation)をおこないます。アトミックな操作は、ロックフリーなアルゴリズムで実装されています。アトミックな操作については http://en.wikipedia.org/wiki/Linearizability を参照してください。

UInt8 UInt8.atomicAdd!(UInt8 val)
SInt8 SInt8.atomicAdd!(SInt8 val)
UInt16 UInt16.atomicAdd!(UInt16 val)
SInt16 SInt16.atomicAdd!(SInt16 val)
UInt32 UInt32.atomicAdd!(UInt32 val)
SInt32 SInt32.atomicAdd!(SInt32 val)
UInt64 UInt64.atomicAdd!(UInt64 val)
SInt64 SInt64.atomicAdd!(SInt64 val)

整数型の値にアトミックな加算

パラメータ:
  • val – 加算する値
戻り値:

操作前の古い整数型の値

UInt8 UInt8.atomicInc!()
SInt8 SInt8.atomicInc!()
UInt16 UInt16.atomicInc!()
SInt16 SInt16.atomicInc!()
UInt32 UInt32.atomicInc!()
SInt32 SInt32.atomicInc!()
UInt64 UInt64.atomicInc!()
SInt64 SInt64.atomicInc!()

整数型のアトミックなインクリメント

戻り値:操作前の古い整数型の値
UInt8 UInt8.atomicSub!(UInt8 val)
SInt8 SInt8.atomicSub!(SInt8 val)
UInt16 UInt16.atomicSub!(UInt16 val)
SInt16 SInt16.atomicSub!(SInt16 val)
UInt32 UInt32.atomicSub!(UInt32 val)
SInt32 SInt32.atomicSub!(SInt32 val)
UInt64 UInt64.atomicSub!(UInt64 val)
SInt64 SInt64.atomicSub!(SInt64 val)

整数型からアトミックな減算

パラメータ:
  • val – 加算する値
戻り値:

操作前の古い整数型の値

UInt8 UInt8.atomicDec!()
SInt8 SInt8.atomicDec!()
UInt16 UInt16.atomicDec!()
SInt16 SInt16.atomicDec!()
UInt32 UInt32.atomicDec!()
SInt32 SInt32.atomicDec!()
UInt64 UInt64.atomicDec!()
SInt64 SInt64.atomicDec!()

整数型のアトミックなデクリメント

戻り値:操作前の古い整数型の値
UInt8 UInt8.atomicOr!(UInt8 val)
SInt8 SInt8.atomicOr!(SInt8 val)
UInt16 UInt16.atomicOr!(UInt16 val)
SInt16 SInt16.atomicOr!(SInt16 val)
UInt32 UInt32.atomicOr!(UInt32 val)
SInt32 SInt32.atomicOr!(SInt32 val)
UInt64 UInt64.atomicOr!(UInt64 val)
SInt64 SInt64.atomicOr!(SInt64 val)

整数型のアトミックなビット演算「or」

パラメータ:
  • val – 「or」する整数型の値
戻り値:

操作前の古い整数型の値

UInt8 UInt8.atomicAnd!(UInt8 val)
SInt8 SInt8.atomicAnd!(SInt8 val)
UInt16 UInt16.atomicAnd!(UInt16 val)
SInt16 SInt16.atomicAnd!(SInt16 val)
UInt32 UInt32.atomicAnd!(UInt32 val)
SInt32 SInt32.atomicAnd!(SInt32 val)
UInt64 UInt64.atomicAnd!(UInt64 val)
SInt64 SInt64.atomicAnd!(SInt64 val)

整数型のアトミックなビット演算「and」

パラメータ:
  • val – 「and」する整数型の値
戻り値:

操作前の古い整数型の値

UInt8 UInt8.atomicXor!(UInt8 val)
SInt8 SInt8.atomicXor!(SInt8 val)
UInt16 UInt16.atomicXor!(UInt16 val)
SInt16 SInt16.atomicXor!(SInt16 val)
UInt32 UInt32.atomicXor!(UInt32 val)
SInt32 SInt32.atomicXor!(SInt32 val)
UInt64 UInt64.atomicXor!(UInt64 val)
SInt64 SInt64.atomicXor!(SInt64 val)

整数型のアトミックなビット演算「xor」

パラメータ:
  • val – 「xor」する整数型の値
戻り値:

操作前の古い整数型の値

UInt8 UInt8.atomicCAS!(UInt8 oldVal, UInt8 newVal)
SInt8 SInt8.atomicCAS!(SInt8 oldVal, SInt8 newVal)
UInt16 UInt16.atomicCAS!(UInt16 oldVal, UInt16 newVal)
SInt16 SInt16.atomicCAS!(SInt16 oldVal, SInt16 newVal)
UInt32 UInt32.atomicCAS!(UInt32 oldVal, UInt32 newVal)
SInt32 SInt32.atomicCAS!(SInt32 oldVal, SInt32 newVal)
UInt64 UInt64.atomicCAS!(UInt64 oldVal, UInt64 newVal)
SInt64 SInt64.atomicCAS!(SInt64 oldVal, SInt64 newVal)

アトミックな、compare-and-swap 操作を行います ―例えば、整数型の値 oldValnewVal に変更し、値が変更された場合のみ oldVal を返します。

パラメータ:
  • oldVal – 整数型と比較する値
  • newVal – 整数型の値、比較が成功した場合に設定される
戻り値:

操作前の古い整数型の値

UInt8 UInt8.atomicGet!()
SInt8 SInt8.atomicGet!()
UInt16 UInt16.atomicGet!()
SInt16 SInt16.atomicGet!()
UInt32 UInt32.atomicGet!()
SInt32 SInt32.atomicGet!()
UInt64 UInt64.atomicGet!()
SInt64 SInt64.atomicGet!()

Atomically obtains the value of an integer. The operation is “atomic” in the sense that the value is treated as volatile.

戻り値:the value of the integer

浮動小数点数型(Floating-Point Types)

Float32Float64 型 ( Float32 のエイリアスである Scalar も) は総称して浮動小数点数型( floating-point types )として知られ、 IEEE 浮動小数点数を表現する。これらの型はビット幅が異なる::

Float32
32-bit IEEE 浮動小数点数
Float64
64-bit IEEE 浮動小数点数
Scalar
Float32 へのエイリアス

浮動小数点数型の振る舞い

  • 浮動小数点数の定数は JavaScript や C言語と同じ文法を持ち、 Float64 型になる。詳細は 浮動小数点数定数 を参照。
  • 全算術演算子( arithmetic )、論理演算子( logical )が浮動小数点数の値に対して使用できる。ビット演算子は使用不可能です。

以下に浮動小数点数型の使用例を示します:

operator entry() {
  Float32 x = 3.141;
  report(x);
  Float64 y = 2.718;
  report(y);
  Float32 z = x*x + y*y;
  report(z);
}

出力:

3.141
2.718
17.2534

文字列型(String Type)

文字列 型はテキスト文字列 ―すなわちゼロ個以上の文字の連なりを表します。 文字列 型の値は string value として参照されます。

文字列 型のセマンティクスの理解は重要です。文字列の特徴:

  • 文字列とはゼロ個以上の文字の連なり
  • 文字列の長さは、 Size 型であり、最大長は \(2^{31}-1\)
  • 文字列定数はKLソースコード中にインライン指定可能です。JavaScript や Python 同様シングルクォーテーションもしくはダブルクォーテーションマークを使用します。文字列定数に関する詳細は 文字列定数 を参照してください。
  • 文字列は以下の操作とプロパティを持ちます。
    • They have a .length() method which returns the number of characters in the string
    • += 代入演算子により他の文字列を所定の文字列に追記します。
    • + 2項演算子を使用し、2つの文字列をつなぎ合わせ、新たな文字列を作成します。
    • 論理演算子 ==, !=, <, <=, >, >= により比較します。 さらに string.compare(otherString) メソッでは -1, 0, 1 いずれかを返します。それぞれ stringotherString より 未満、等しい、超 に応じます。
    • string[index] を使用し、インデックスによるアクセスが可能です。与えられたインデックスに該当する1文字の文字列を結果として返します。 index\([0...2^{31}-1]\) の範囲である必要があります。
    • string.hash() メソッドにより、文字列の32-bit のハッシュ値を得ます。
  • C/C++言語と異なり、文字列にはヌル文字(ASCII 0)を許容します。
  • 文字列はそれ自体にエンコーディング(符号化)の概念を持ちません。つまりただの連続したバイトにすぎません。その文字列が使用されるアプリケーション空間において、文字列エンコーディングが決定されます。Fabric自体ではUTF-8エンコーディングを使用しますが、Fabricエクステンションでは、文字列をそれぞれ別のエンコーディングに変換する必要がある場合があります。
  • KLの他の全ての型は、キャストし文字列へ変換することができます。この変換によりある値を人間可読な文字列とすることができます。カスタム型においてこの変換 ― function Type.appendDesc(io String string) メソッド ― はオーバーライド可能です。 see 変換関数 を参照してください。

String 型の使用例:

operator entry() {
  String a = "A string";
  report(a);
  report("a has length " + a.length());
  String b = "Another string";
  report(b);
  String c = a + " and " + b;
  report(c);
  b += " now includes " + a;
  report(b);
}

出力:

A string
a has length 8
Another string
A string and Another string
Another string now includes A string

The RTVal Type

The RTVal type is a type that holds a dynamically-typed valued

派生型(Derived Types)

基本型に加え、KLは3つの派生型をサポートします。構造体(structures)配列(arrays)辞書(dictionaries)です。

構造体(Structures)

構造体( structure )ではいくつかの型の値がメモリ上に一緒に配置されます。

構造体は通常、Fabric’s registered type system を使いKL外に定義されますが、KLソースコード内に直接 struct キーワードを使い宣言することもできます。

struct NewType {
  Float32 firstMember;
  String secondMember;
  Integer thirdMemberVarArray[], fourthMemberFixedArray[3];
};

注意:可変長配列は一番最後のメンバとして宣言します。派生型は任意にネストすることが可能です。

注釈

全ての構造体の宣言は、グローバルスコープに置かれます。関数スコープ内での宣言は不可能です。

構造体に関する詳細:

構造体の使用例:

struct MyNewType {
  Integer i;
  String s, t;
};

function entry() {
  MyNewType mnt;
  mnt.s = "Hello!";
  mnt.i = 42;
  mnt.t = "there!";
  report(mnt);
}

出力:

{i:42,s:"Hello!",t:"there!"}

構造体メンバのアライメント

構造体メンバのアライメントは、C言語でのそれと同一です。従い、 Fabric Engine の EDK コードとのインタフェースでは、KLでの構造体に合わせた特別なアライメントの指定は必要ありません。

参考までに、KLの構造体のアライメントに関するルール(つまりC言語のディフォルトと一緒)は:

  • 全ての型は、サイズとアライメントを持つ
  • 基本型のアライメントは、その型のサイズと同一
  • 構造体(全体)のアライメントはその構造体のメンバの型のうち一番大きい物
  • メンバの構造体内でのバイト位置は、次に利用可能な構造体のオフセットからメンバの型のアライメントへ切り上げ選択されます。

構造体の継承

バージョン 1.13.0 で追加.

ある構造体は基となる一つの構造体を継承( inherit もしくは derive )できます。特殊化した構造体は、基底構造体より全てのメンバとメソッドを継承します。

struct SpecializedType : BaseType 構文を使用し継承関係の宣言をします。特殊化した構造体は基底構造体へとノーコストで cast が可能です。 .parent アクセッサにより、明示的にキャストします。

struct Shape {
  Float32 centerX, centerY;
};

struct Circle : Shape {
  Float32 radius;
};

operator entry() {
  Circle c;
  c.centerX = 1;
  c.centerY = 2;
  c.radius = 3;

  report( c );
  report( c.parent );
}

/*
** Output:

{centerX:+1.0,centerY:+2.0,radius:+3.0}
{centerX:+1.0,centerY:+2.0}

*/

注釈

基底型へとキャストすると、その構造体は特殊化した振舞を全て失います。オブジェクト( objects )との違いです。

struct Shape {
  Float32 centerX, centerY;
};

struct Circle : Shape {
  Float32 radius;
};

function printShape( Shape s ) {
  report( s.type() + ": " + s );
}

operator entry() {
  Circle c;
  printShape( c );
}

/*
** Output:

Shape: {centerX:+0.0,centerY:+0.0}

*/

オブジェクト(Objects)

バージョン 1.12.0 で変更: オブジェクト型変数の “empty construction” は不可能になりました

オブジェクト( object )は、メモリ内に幾つかの型の値を一緒に保持するという点において構造体( structure )に似ています。違いは、オブジェクトが「参照によるコピー(copy-by-reference)」であるのに対し、構造体では「値によるコピー(copy-by-value)」な点です。オブジェクトは constructed され内部において参照カウントされます。さらにオブジェクトでは、そのオブジェクトでのサポートが保証されるメソッドの集合の interfaces をサポートします。

オブジェクトは構造体とほぼ同じように使用できます。おおきな違いは、copy-by-referenceな点、コンストラクトされなくてはいけない点です。KLは内部に各オブジェクトの参照番号のトラックを保持し、そのオブジェクトの最後の参照が破棄された際、オブジェクトは開放されます。スコープから外れるか、オブジェクトに null が割り当てられると、オブジェクトは参照を落とします。

KLコード中、 object キーワードを使用しオブジェクトを定義します。このシンタックスは構造体とほぼ一緒です。 structures 参照

// An object with two members
object Obj {
  String s;
  UInt32 n;
};

必要に応じ、オブジェクトはある1つの基底オブジェクトを継承し、さらに1つ以上の interfaces を実装することができます。オブジェクトの名前の後に、継承するオブジェクト、実装するインターフェースを示します。

// An object with two members that implements two interfaces
object MyObjType : BaseObjType, IntOne, IntTwo {
  String s;
  UInt32 n;
};

型にオブジェクトを与えた変数は、そのオブジェクトの名とともに宣言します。

// A variable of type MyObjType
MyObjType obj = null;
report(obj); // reports: null

null は存在しないオブジェクトを参照します。オブジェクトが null であると、report可能ですが、実行時にそのオブジェクトのメンバを参照したり、メソッドを呼び出すと、ランタイムエラーとなります。

どのオブジェクト型の変数も null に設定可能です。 null オブジェクトに対するほとんど操作は、実行時エラーとなります。正常なオブジェクトの作成には、コンストラクトします。コンストラクトする2種類の文法:

MyObjType obj = MyObjType(); // calls the default constructor
report(obj); // reports: {s:"",n:0}
MyObjType obj2(); // also calls the default constructor
report(obj2); // reports: {s:"",n:0}
obj2 = null; // releases the object referenced by obj2
report(obj2); // throws an error

注釈

オブジェクトは”empty constructed”不可能です。明示的にコンストラクトするか、明示的に null に設定します。

Constructorsdestructors を構造体同様、オブジェクトでも指定することができます

// Provide a default constructor
function MyObjType() {
  this.s = "hello";
  this.n = 42;
}

// Provide a constructor that takes parameters
function MyObjType(String s, UInt32 n) {
  this.s = s;
  this.n = n;
}

// later..
MyObjType obj = MyObjType();
report(obj); // reports: {s:"hello",n:42}
obj = MyObjType("foo", 7);
report(obj); // reports: {s:"foo",n:7}

他の同型オブジェクトから特定のオブジェクトをコンストラクトすると、新しいオブジェクトは古いオブジェクトを参照します。この場合、新しいオブジェクトは実際には作られていません。つまり参照を通し、そのうちの1つのオブジェクトに変更が加わると、もう片方のオブジェクトも同様、変更されます。

MyObjType o1("bar", 3);
MyObjType o2 = o1;
o2.s = "baz";
report(o1); // reports: {s:"baz",n:3}

ユーザ独自のコピーコンストラクタを定義することはできません。以下の理由のためです。コピーコンストラクタは既存のオブジェクトに対し、参照を常に1つ加算します。かりに新規オブジェクトをインスタンス化したいのであれば、 clone() メソッド( Object clone() メソッド 参照)か、カスタムメソッドを使用します。

/*
** Example: Duplicating an object using a custom method
*/

// not permitted, custom copy constructor invalid for objects
// function MyObjType(MyObjType o)
// {
//   // ...
// }

object MyObjType
{
  String s;
  Integer n;
};

// return a new object using a custom method
function MyObjType MyObjType.copy()
{
  MyObjType o = MyObjType();
  o.s = this.s;
  o.n = this.n;
  return o;
}

operator entry()
{
  MyObjType o1();
  o1.s = "foo";
  o1.n = 42;

  MyObjType o2 = o1.copy();

  o2.s = "bar";
  report("o1 = " + o1);
  report("o2 = " + o2);
}

/*
** Output:

o1 = {s:"foo",n:42}
o2 = {s:"bar",n:42}

*/

構造体同様、オブジェクトには任意のメソッドを定義できます。これらのメソッドは object.methodName(arg,arg,...) 構文を使用し呼び出します。 null に対するメソッドの呼び出しは実行時例外を引き起こします。

function MyObjType.reportMe() {
  report("reportMe: s="+this.s+" n="+n);
}

MyObjType obj("Fred", 49);
obj.reportMe(); // reports: reportMe: s=Fred n=49

構造体同様、オブジェクトのメンバへのアクセスには object.memberName 構文を使用します。null オブジェクトに対する . (ドット)の使用も実行時例外となります。

オブジェクトのさらなる特徴:

  • オブジェクト型の値が Boolean に変換されると、そのオブジェクトが null ではない場合 true となる
  • equality operators obj1 == obj2 and obj1 != obj2 はオブジェクトがオーバーロドされているか、 null と比較されている場合のみ、有効です。 identity operators obj1 === obj2 and obj1 !== obj2 は常に有効で、同一のオブジェクトを参照してりうかどうかに依って比較されます。 (すなわち変更が双方に影響します)
  • オブジェクト型の値は、 <objectValue>.uid() をサポートしユニークな UInt64 を返します。オブジェクトの同定に使用できます。この値は、そのオブジェクトをそれがサポートするどのようなインターフェースにキャストしたものに対し .uid() を呼び出した結果と同一です。
  • オブジェクトに null を代入するとそのオブジェクトが過去に示した参照全てをドロップします。
  • public, private, protected, permits キーワードにより、オブジェクトのメンバ、メソッドに対するアクセス制御が可能です。 構造体、オブジェクト、インタフェースでのアクセス制御 参照

注釈

オブジェクト型の変数は、構造体を示す事はできません。オブジェクト、またインターフェイスは根本的に構造体とは異なります。 オブジェクト vs 構造体 節参照

オブジェクトの別な使用例:

/*
** Example: Objects
*/

object Foo {
  SInt32 intMember;
  String stringMember;
};

// A non-default constructor for Foo
function Foo(SInt32 i, String s) {
  this.intMember = i;
  this.stringMember = s;
}

operator entry() {
  Foo foo1 = Foo(32, "foo"); // call the non-default constructor
  report(foo1);
  Foo foo2(); // call the default constructor
  report(foo2);
  Foo foo3(foo1); // make foo3 a reference to foo1
  report(foo3);
  foo3.intMember = 20;
  // since foo1 and foo3 refer to the same object, both change!
  report(foo1);
  Foo foo4 = null; // a null object
  report(foo4);
  foo4.intMember = 42; // throws an exception
}

/*
** Output:

{intMember:32,stringMember:"foo"}
{intMember:0,stringMember:""}
{intMember:32,stringMember:"foo"}
{intMember:20,stringMember:"foo"}
null
Error: (stdin):29:3: dereferenced null object
KL stack trace:
[ST] 1 kl.internal.String.SetErrorDataPtrAndLength.AS0()
[ST] 2 function.setError.R.St()
[ST] 3 operator.entry() (stdin):29
[ST] 4 kl.internal.entry.stub.cpu()

*/

Object clone() メソッド

バージョン 1.15.0 で追加.

KLでの他の多くの型同様オブジェクトは、オブジェクトのディープコピーを行う clone() メソッドをサポートします。全てのオブジェクトはディフォルトで clone() を実装しています。独自定義したメソッド <ObjectType>.cloneMembersTo(io <ObjectType> that) を記述し clone() の振舞を、変更できます。このメソッドはオブジェクトの複製処理を行う際、自動的に呼びだされます。以下に示す:

/*
** Example: Object Custom Clone
*/

object Obj
{
  String s;
  Integer n;
};

Obj.cloneMembersTo(io Obj that)
{
  that.s = this.s + " cloned";
  that.n = 2 * this.n;
}

operator entry()
{
  Obj obj1();
  obj1.s = "string";
  obj1.n = 42;
  report("obj1 = " + obj1);

  Obj obj2 = obj1.clone();
  report("obj2 = " + obj2);
}

/*
** Output:

obj1 = {s:"string",n:42}
obj2 = {s:"string cloned",n:84}

*/

The Object refCount() Method

You can query the number of references pointing to a certain object by using the method refCount(), as shown below:

/*
** Example: Object Reference Count
*/

object Foo
{
};

operator entry()
{
  Foo foo();
  Foo newRef = foo;
  Foo newRef1 = newRef;
  report("foo ref count = " + foo.refCount());
}

/*
** Output:

foo ref count = 3

*/

オブジェクトの継承

バージョン 1.13.0 で追加.

構造体と同じように、オブジェクトは他のベースオブジェクトを1つ継承できます。特殊化したオブジェクトは、基底オブジェクトのすべてのメンバとメソッドを引き継ぎます。 struct SpecializedType : BaseType 構文を使用し継承関係を宣言します。

object.parent 構文により、基底オブジェクトから継承している場合、明示的に基底型へとキャストを行います。特殊化したメンバやメソッドが基底オブジェクトと異なる定義を持つ場合、便利です。

object MyBaseObject {
  Float32 f;
  Size s;
};

object MyObject : MyBaseObject {
  Size s;
};

operator entry() {
  MyObject o();
  o.f = 0.5;
  o.s = 2;
  o.parent.s = 1;
  report(o);
}

/*
** Output:

{f:+0.5,s:1,s:2}

*/

インタフェース(Interfaces)

バージョン 1.12.0 で変更: インタフェース型の変数の “empty construction” の使用が不可能になりました

バージョン 1.15.0 で変更: <typeExpr>.createNew() メソッドが追加されました

interface とは、あるオブジェクトが必ず実装しなければならないメソッドの一連の集合です。オブジェクトは、オブジェクトの宣言に単にインタフェースの名前を入れるだけでなくインタフェースの各メソッドを実際に実装することで、インタフェースを実装します。プログラマはどのようなオブジェクトの型であっても、インタフェースを実装するオブジェクトであれば、ファーストクラス型としてインタフェースを使用し参照することができます。

interface キーワードによりインタフェースを定義します。インタフェース定義は構造体や、オブジェクト定義に似ています。メンバやメソッドは指定することを除きます。例えば:

interface MyInt {
  UInt32 foo();
  bar?(io String s);
  Float32 baz!();
};

このサンプルでは、インタフェース MyInt に3つのメソッドを与え、定義します。引数パラメータと、返り値の型は、構造体やオブジェクト同様におきます( methods )。ただし、 function キーワードと、型名は省きます。メソッド呼び出しによりそのオブジェクトが変更されるかどうか明示的に示す ! and ? は機能します。

インタフェースには、それを実装するオブジェクトがサポートしなければならない一連のメソッド「のみ」を指定します。インタフェースを使用するには、そのインタフェースをサポートしたオブジェクトを定義する必要があります。

// An object type that implements MyInt
object MyObj : MyInt {
  UInt32 n;
  String s;
};

function UInt32 MyObj.foo() {
  return this.n;
}

function MyObj.bar?(io String s) {
  s = this.s;
}

function Float32 MyObj.baz!() {
  return 3.14 * this.n++;
}

インタフェースの実装には、オブジェクト型名直後にインタフェースを、そして各メソッドの実装の一覧を列記します。

警告

オブジェクトの実装に、インタフェースに必要なメソッド定義が一つでも欠けていると、コンパイル時エラーとなります。

オブジェクトには複数のインタフェースを実装できます。

interface MyOtherInt {
  fred();
  Float32 baz!();
};

object MyDoubleObj : MyInt, MyOtherInt {
  Boolean b;
};

function UInt32 MyDoubleObj.foo() {
  // ....
}

function MyDoubleObj.bar?(io String s) {
  // ....
}

function Float32 MyDoubleObj.baz!() {
  this.b = !this.b;
  return this.b? -7.5: 14.5;
}

function MyDoubleObj.fred() {
  // ...
}

メソッドを共有するような複数のインタフェースを、オブジェクトがサポートすることは可能です。それにはメソッドを1度だけ実装します。実装したメソッドは全てのインタフェースから共有されます。

インタフェースを定義すると、あるインタフェースを実装したオブジェクトを参照する変数を宣言できます。 int.methodName(arg, arg, ...) 構文により、そのオブジェクトのメソッドを呼び出します。

MyInt myInt = null; // does not refer to an object
myInt = MyObj();
report(myInt); // reports: {n:0, s:""} since it is a MyObj
report(myInt.baz()); reports: 0.0
report(myInt.baz()); reports: 3.14
myInt = MyDoubleObj(); // releases old object
report(myInt); // reports: {b:false}
report(myInt.baz()); reports: -7.5
report(myInt.baz()); reports: 14.5

interface.type() メソッドのより、インタフェースから参照しているオブジェクトの型を確認します。割当もしくはキャストにより、特定のオブジェクトを得ます。これにより、KLでの単純な「弱い(もしくは実行時)型付け」の形式(simple form of weak(or runtime) typing)を可能にします。

if (myInt.type() == MyObj) {
  MyObj myObj = myInt;
}
else if (myInt.type() == MyDoubleObj) {
  MyDoubleObj myDoubleObj = myInt;
}

このケースでは、この式の型は Type 型です。したがって、 <typeExpr>.createNew() メソッドを呼ぶと、新しいオブジェクトインスタンスを作成します。インタフェースは空コンストラクタを使用しインスタンスになります。結果として、 Object interface 型になります。

/*
** Example: <typeExpr>.createNew()
*/

object Obj
{
  String s;
};

operator entry()
{
  Obj obj1();
  obj1.s = "hello";
  report("obj1 = " + obj1);

  Object abstractObject = obj1;

  Obj obj2 = abstractObject.type().createNew();
  report("obj2 = " + obj2);
}

/*
** Output:

obj1 = {s:"hello"}
obj2 = {s:""}

*/

インタフェースを、間違ったオブジェクトへ割当もしくはキャストすると、実行時例外となります。「型がインタフェースな値」を、「型が他のインタフェースである変数」へと割り当てる事もできます。つまり、基礎となるオブジェクトが、第2のインタフェースをサポートする場合、そのオブジェクトの第2のインタフェースへの null ではない参照を得ることができます。第2のインタフェースをサポートしていなければ、実行時例外が発生します。

インタフェースのさらなる特徴

  • Boolean へのキャストがオブジェクト同様正常に可能です。 null を参照しているかどうかをチェックします
  • 比較演算子 int1 == int2, int1 != int2,そして同一比較演算子 int1 === int2, int1 !== int2, では2つのインタフェース同士が、同一(あるは別)のオブジェクトを参照するかどうかを判断します。(以前に参照されていたオブジェクトならなんでも)
  • インタフェース型の値は <interfaceValue>.uid() メソッドをサポートし、インタフェースの参照するオブジェクトの同一性に依存した、ユニークな UInt64 を返します。この値はそのインタフェースの参照するオブジェクトで .uid() を呼んだ時の値と同じになります。
  • public, private, protected, permits キーワードによりインタフェースのメソッドに対するアクセス制御が可能です。 構造体、オブジェクト、インタフェースでのアクセス制御 参照

注釈

インタフェース型の変数では構造体を示すことはできません。オブジェクトとインタフェースは根本的に構造体とは異なります。 オブジェクト vs 構造体 節参照

Object インタフェース

事前に定義された Object と呼ばれる特別なインタフェースが存在します。全てのKLでのオブジェクトで常にサポートします。このインタフェースにより、KL言語の過去のバージョンとの後方互換性と、さらに任意のオブジェクトへの参照を渡すシンプルな方法(C言語での “void pointer”のようなもの)を提供します。この Object インタフェースではメソッドを提供しません。

object MyObj { // implicitly implements Object
  String s;
};

operator entry() {
  Object obj = MyObj();
}

構造体、オブジェクト、インタフェースでのアクセス制御

バージョン 1.15.0 で追加.

構造体、オブジェクト、インタフェースのメンバやメソッドへのアクセスは、 public, private and protected キーワードにより制御されます。これらのキーワードは C++と同じように動作します。

  • public が付けられたメンバやメソッドは、ソースコードどこからでもアクセス可能です。アクセス修飾子がつけられない場合のディフォルトの動作でもあります。
  • private と付けられたメンバやメソッドは、構造体もしくはオブジェクトないのメソッドからのみアクセス可能です。他の場からのアクセスは、コンパイル時エラーとなります。
  • protected と付けられたメンバやメソッドは、その構造体もしくはオブジェクトおよび、それらを継承した構造体もしくはオブジェクトからのみアクセス可能です。それ以外の場からのアクセスはコンパイル時エラーとなります。
/*
** Example: Member and Method Access Controls
*/

struct A
{
  private UInt32 n; // can only be access by methods of A
  protected String s; // can only be accessed by methods of A and structures that inherit from A
};

protected A.bar()
{
}

struct B : A
{
};

public B.foo()
{
  report(this.n); // error since n is private
  report(this.s); // ok since n is protected
  this.bar(); // ok since A.bar() is procted
}

operator entry()
{
  B b;
  report(b.n); // error since n is private
  report(b.s); // error since n is protected
  b.foo(); // ok since B.foo() is public
  b.bar(); // error since A.bar() is protected
}

/*
** Output:
(stdin):30:3: Calling function 'report':
(stdin):30:10:   Parameter 1:
(stdin):30:3: error: cannot access private member n
(stdin):31:3: Calling function 'report':
(stdin):31:10:   Parameter 1:
(stdin):31:3: error: cannot access protected member s
(stdin):33:3: error: cannot access protected function A.bar?()
(stdin):22:3: Calling function 'report':
(stdin):22:10:   Parameter 1:
(stdin):22:3: error: cannot access private member n


*/

permits キーワードの使用により、構造体もしくはオブジェクトがこのメカニズムを迂回させることができます。構造体やオブジェクトが、他の構造体もしくはオブジェクトの permits セクションにリストされていれば、private や protected メンバもしくはメソッドにアクセス可能です。

/*
** Example: Bypassing Acess Controls Using ``permits``
*/

object A;

object Base permits A
{
  private UInt32 n;
};

object A { Base b; };
A() { this.b = Base(); }
A.foo()
{
  report(this.b.n); // OK since Base permits A
}

object B;
object C;

object Derived : Base permits B, C
{
};

private Derived.bar() {}

object B { Derived d; };
B() { this.d = Derived(); }
B.baz()
{
  this.d.bar();  // OK since Derived permits B
}

operator entry()
{
  A a();
  a.foo();
  B b();
  b.baz();
}

/*
** Output:

0

*/

インタフェースと継承

バージョン 1.13.0 で追加.

base object type を継承し特殊化したオブジェクトでも追加のインタフェースを実装できます。

object MyObj : MyBaseObj, MyInt {
  ...
};

基底オブジェクトがインタフェースを実装していると、そのオブジェクトを継承し特殊化したオブジェクトからも、基底オブジェクトでそのインタフェースを実装したメソッドをさらに、 override した実装が使用可能です。このような場面では、特別なシンタックスが必要となります。同一のインタフェースを実装した基底クラスのメソッドを呼ぶには インターフェイスメソッドと継承 参照してください。

オブジェクト、インタフェースの前方宣言

バージョン 1.12.0 で追加: オブジェクト、インタフェースの前方宣言

オブジェクトやインタフェースの存在を、実際のメンバの定義抜きに宣言できます。オブジェクトやインタフェースが相互依存するような場合便利です。オブジェクトやインタフェースの前方定義するには、単にメンバやメソッドそしてインタフェースの実装を省略します。

/*
** Example: Forward Declaration of Objects and Interfaces
*/

// Forward declaration of interface IntTwo
interface IntTwo;

interface IntOne {
  add!(IntTwo int);
};

interface IntTwo {
  sub!(IntOne int);
};

// Forward declaration of object ObjTwo
object ObjTwo;

object ObjOne : IntOne, IntTwo {
  UInt32 a;
  ObjTwo objTwo;
};

object ObjTwo {
  ObjOne objOne;
};

function ObjOne()
{
  this.a = 42;
}

function ObjOne.add!(IntTwo intTwo) {
  ObjOne objOne = ObjOne(intTwo);
  this.a += objOne.a;
}

function ObjOne.sub!(IntOne intOne) {
  ObjOne objOne = ObjOne(intOne);
  this.a -= objOne.a;
}

operator entry() {
  ObjOne objOne();
  objOne.add( IntOne(objOne) );
  objOne.sub( IntTwo(objOne) );
  objOne.objTwo = ObjTwo();
  report(objOne);
}

/*
** Output:

{a:0,objTwo:{objOne:null}}

*/

未所有オブジェクト、インタフェースへの参照

オブジェクト、インタフェースへの参照全てを追跡するランタイムコストはとても高価となりえます。特定の状況、例えばオブジェクトへの参照が常に1つ以上あることが事前に判明している時などは、パフォーマンス上の理由からこの追跡を避けるのが望ましいです。KLでは Ref<ObjectType>, Ref<InterfaceType> 構文を使いこの状況を避けることができます。これにより、メモリリークを発生させず、オブジェクトやインタフェースの参照を繰り返し作成する機能を提供します。

未所有参照(Unowned reference)は通常のオブジェクトやインタフェースの参照と全く同様に動作します。ただひとつの違いは、参照を追跡しないことです。

警告

未所有参照の使用すると、細かいバグにより、クラッシュを非常に簡単に引き起こしてしまいます。使用はあなたの責任です。未所有参照が、他の何かが所有するオブジェクトを正しく確実に参照するようにしましょう。つまり、既に破棄されたオブジェクトを参照する未所有参照をそのまま放置することができてしまい、プログラム中からその参照を使用し何かさせようとするとクラッシュしてしまうのです。

未所有参照の使用例:

/*
** Example: Unowned References
*/

object Foo {
  SInt32 intMember;
  String stringMember;
};

operator entry() {
  Foo foo(); // Construct a new specific object
  foo.stringMember = "me!";
  report("foo = " + foo);
  Ref<Foo> fooRef = foo; // fooRef is an unowned reference to foo
  report("fooRef = " + fooRef);
}

/*
** Output:

foo = {intMember:0,stringMember:"me!"}
fooRef = {intMember:0,stringMember:"me!"}

*/

Type

KLには Type 型と呼ばれる特別な型があります。KLでのある値の型を表します。KLでの全ての値は {value}.type() メソッドをサポートします。これによりそのオブジェクトの型、 Type 型の値を得ます。

変数の Type のディフォルト値は、特別な値 None です。これはいかに説明する幾つかのメソッドの返り値でもあります。 None はどの {value} に対する {value}.type() とも等しくありません。

Type 型の値をつかい、いくつかできることがあります。おもに、インタフェースとオブジェクトの実行時型推論に用います。

  • まず値の型を参照し Type 型の値を参照し、そして比較に使用することができます:
    /*
    ** Example: Type Comparisons
    */
    
    operator entry()
    {
      Type booleanType = Boolean;
      UInt32 uint32;
      report("booleanType == uint32.type " + (booleanType == uint32.type()));
      Boolean boolean;
      report("booleanType == boolean.type " + (booleanType == boolean.type()));
    }
    
    /*
    ** Output:
    
    booleanType == uint32.type false
    booleanType == boolean.type true
    
    */
    
  • インタフェース(Interfaces) では {value}.type() メソッドで、生成されたオブジェクトが参照する特定オブジェクトの型を返します。これによりオブジェクトの型推論を行います。 インタフェース(Interfaces) 節に詳細があります。
  • Type 型の値は {value}.parentType() メソッドをサポートします。この場合 {value} は親を継承したオブジェクトもしくは構造体です。 parentType() は親の型を返します。未継承であれば parentType() None を返します。
    /*
    ** Example: parentType
    */
    
    object Obj { /*...*/ };
    object SubObj : Obj { /*...*/ };
    
    operator entry() {
      Type type;
      report("type = " + type );
      report("type.parentType() = " + type.parentType());
      type = Obj;
      report("type = " + type );
      report("type.parentType() = " + type.parentType());
      type = SubObj;
      report("type = " + type );
      report("type.parentType() = " + type.parentType());
    }
    
    /*
    ** Output:
    
    type = None
    type.parentType() = None
    type = Obj
    type.parentType() = None
    type = SubObj
    type.parentType() = Obj
    
    */
    
  • Type 型の値は {value}.isA({interfaceType}) メソッドをサポートします。 {value} がインタフェース {interfaceType} をサポートしている場合のみ true を返します。
    /*
    ** Example: isA
    */
    
    interface Int1 { /*...*/ };
    interface Int2 { /*...*/ };
    interface Int3 { /*...*/ };
    object Obj : Int1, Int2 { /*...*/ };
    object SubObj : Obj, Int3 { /*...*/ };
    
    operator entry() {
      Type nullType;
      Obj obj();
      SubObj subObj();
      report("nullType.isA(nullType) = " + nullType.isA(nullType));
      report("nullType.isA(Obj) = " + nullType.isA(Obj));
      report("nullType.isA(SubObj) = " + nullType.isA(SubObj));
      report("nullType.isA(Int1) = " + nullType.isA(Int1));
      report("nullType.isA(Int2) = " + nullType.isA(Int2));
      report("nullType.isA(Int3) = " + nullType.isA(Int3));
      report("obj.type().isA(nullType) = " + obj.type().isA(nullType));
      report("obj.type().isA(Obj) = " + obj.type().isA(Obj));
      report("obj.type().isA(SubObj) = " + obj.type().isA(SubObj));
      report("obj.type().isA(Int1) = " + obj.type().isA(Int1));
      report("obj.type().isA(Int2) = " + obj.type().isA(Int2));
      report("obj.type().isA(Int3) = " + obj.type().isA(Int3));
      report("subObj.type().isA(nullType) = " + subObj.type().isA(nullType));
      report("subObj.type().isA(Obj) = " + subObj.type().isA(Obj));
      report("subObj.type().isA(SubObj) = " + subObj.type().isA(SubObj));
      report("subObj.type().isA(Int1) = " + subObj.type().isA(Int1));
      report("subObj.type().isA(Int2) = " + subObj.type().isA(Int2));
      report("subObj.type().isA(Int3) = " + subObj.type().isA(Int3));
    }
    
    /*
    ** Output:
    
    nullType.isA(nullType) = false
    nullType.isA(Obj) = false
    nullType.isA(SubObj) = false
    nullType.isA(Int1) = false
    nullType.isA(Int2) = false
    nullType.isA(Int3) = false
    obj.type().isA(nullType) = false
    obj.type().isA(Obj) = true
    obj.type().isA(SubObj) = false
    obj.type().isA(Int1) = true
    obj.type().isA(Int2) = true
    obj.type().isA(Int3) = false
    subObj.type().isA(nullType) = false
    subObj.type().isA(Obj) = true
    subObj.type().isA(SubObj) = true
    subObj.type().isA(Int1) = true
    subObj.type().isA(Int2) = true
    subObj.type().isA(Int3) = true
    
    */
    
  • {typeValue}.jsonDesc() メソッドにより、型の説明を取得できます。実行時に構造体のメンバのようなものを探しだすことができます。
    /*
    ** Example: Type Description
    */
    
    struct S
    {
      String string;
      UInt32 uint32;
    };
    
    operator entry()
    {
      report(S.jsonDesc());
    }
    
    /*
    ** Output:
    
    {
      "name" : "S",
      "size" : 32,
      "owningExt" : null,
      "members" : [
        {
          "name" : "string",
          "type" : "String"
          },
        {
          "name" : "uint32",
          "type" : "UInt32"
          }
        ]
      }
    
    */
    

The RTVal Type

The RTVal type is once which contains a dynamically-typed value. A value of type RTVal can be created from a value of any other type in KL, except RTVal itself.

Using the RTVal type allows you to work with values whose types are only known at runtime. In order to determine the type of the value inside of an RTVal value, use its .type() method as shown below:

/*
** Example: RTVal Type
*/

unwrapValue(RTVal rtVal) {
  if (rtVal.type() == UInt32) {
    UInt32 val(rtVal);
    report("Value is UInt32: " + val);
  }
  else if (rtVal.type() == String) {
    String val(rtVal);
    report("Value is String: " + val);
  }
  else
    report("Unhandled type: " + rtVal.type());
}

operator entry() {
  unwrapValue(RTVal(4056u32));
  unwrapValue(RTVal("foo"));
  unwrapValue(RTVal(3.141));
}

/*
** Output:
Connection refused at server (-111)
Cannot connect to server (comm: -4)

Value is UInt32: 4056
Value is String: foo
Unhandled type: floating-point constant

*/

Notice above that, unlike most conversions, conversions to and from the RTVal type must be explicit; this is because it’s too easy for bugs to slip by if the conversions are done for you automatically.

The DFGBinding Type

The DFGBinding type represents a Canvas (historically called “the DFG”) binding.

Currently, the only supported use of the DFGBinding type is as follows:

  • A value of type DFGBinding can be constructed from a JSON-formatted binding description (ie. .canvas file)
  • The methods .getArgValue({index}) and setArgValue({index}, {rtVal}) are supported
  • The method .execute() is supported.

This allows you to load and execute a Canvas graph entirely from KL. There is also a CAPI entry point for converting DFGBindings to and from RTVals, which will allow you to pass them in from client applications.

This documentation will be expanded in the future to cover complex use cases.

オブジェクト vs 構造体

型の合成に、構造体をつかうかオブジェクトを使うかの決定は、実行時の動作や性能に対してだけではなく、プログラムの設計にも影響する極めて重要な選択です。

構造体は、通常、パフォーマンスクリティカルな小さな型に対して最善な選択です。与えられた型の値を一時的に大量に作成する、複雑な式の場合、オブジェクトではなく、構造体を使いましょう。1点致命的なパフォーマンスの観点は、構造体の型をもった変数は、プログラム「スタック」に確保されるということです。つまり、構造体ではメモリ確保、開放のオーバーヘッドが事実上存在しないことを意味します。例えば、ベクタやトランスフォーム型などの数学的な型は、通常構造体です。

オブジェクトは、通常、使用回数が作成/破棄よりも著しく多いような、大規模な型に対して最善の選択です。オブジェクトは、スタックよりも顕著に遅い「ヒープ」に確保されます。さらに、異なる沢山の変数が同じオブジェクトを指すことができ、オブジェクトは同じデータに対し沢山参照する場合、とても良い選択です。データ階層などは通常オブジェクトで表現します。

配列

array とは同型(配列の element type 呼ばれる)の値を集めた一連のシーケンスです。整数によって索引付け(インデックス)され、メモリ上に連続配置されます。KLでは3種類の配列をサポートします: 可変長配列(variable-size arrays), 固定長配列(fixed-length arrays),外部配列(external arrays)です。各配列の詳細については後述します。

3つのうちいずれであっても、KLにおける配列は幾つかの共通の振舞をもちます。

  • 配列は [..] 演算子をつかい索引付されます。JavaScriptやC言語と同一です。配列の索引は 0 基点です。これもJavaScriptやC言語と一緒です:
    Size values[];    // Declare a variable-size array
    values.push(42);  // Push some elements onto the end of the array
    values.push(21);
    values.push(3);
    report(values[1]); // outputs "21"
    
  • 配列のサイズの型は Size 型です。インデクシング演算子は、 Index 型( Size へのエイリアス)を取ります。
  • 配列の宣言はネスト(入れ子)可能です、さらに他の配列の型ともネストできます。
    Integer b[][];  // A variable-size array of variable-size arrays of integers
    Boolean a[2][]; // An array of 2 variable-size arrays of booleans
    String c<>[];   // An external array of variable-size arrays of strings
    
  • 配列は参照渡し( passed by reference )により関数やオペレータに渡ります。すなわち、コピーされません。このことは、100万要素を持つ1つの配列を関数に渡すのも、1つの要素の1つの配列を渡すのも、かかる時間は同じです。
  • Fabricクライアントを境界チェック(bounds-checks)を有効にし実行した場合、インデクシング演算子を使った配列へのインデクシングは、境界チェックされます。インデックスが配列の終端に達し、超えると、例外が投げられます。

可変長配列(Variable-Size Arrays)

variable-size array とは、実行時に長さを帰ることのできる配列です。可変長配列の宣言は変数(パラメータもしくは構造体メンバが宣言された)の名前の直後 [] を付与します。例: String strings[]

可変長配列は properties of arrays を全て持ち、さらに以下の追加の特徴があります:

  • 可変長配列の最大サイズは \(2^{31}-1\)
  • 可変長配列は、割り当て共有( share-on-assign )です。つまりある可変長配列を他の配列へ割り当てると、要素はコピーされるのでは「なく」、要素への「参照をコピー」します。片方の配列へのどのような変更も、もう片方の配列に影響します。これはシャローコピー(対義語はディープコピー)と呼ばれます。配列のディープコピーを行うには、後述する clone() メソッドを使用します。
  • 可変長配列は、以下のメソッド、関数をサポートします。
    • ディフォルトで可変長配列は空です。コンストラクト時に整数を与えた場合、その配列初期化時初めからは要素を複数持つことになります。
    • push(element) メソッドにより、末端に要素を追加します。結果配列サイズは1繰り上がります。
    • pop() メソッドにより、末端要素を取り除き、その要素を返します。結果配列サイズは1小さくなります。空の配列に対し pop() を呼ぶとエラーが返ります。
    • size() メソッドによりその可変長配列の要素数を返します。
    • resize(newSize) メソッドにより配列のサイズを変更します。末端新規要素を、その要素の型のディフォルト値で初期化します。
    • reserve(count) メソッドでは少なくとも count 分の要素の場が確保されていることを保証します。事前に要素の最終的な数がわかっているのであれば、push(...) を何度も呼ぶ前に reserve(...) すると速度面で優位になります。
    • clone() メソッドによりその可変長配列のディープコピーを作成します。結果のコピー物は初期状態で他の可変長配列から共有されていません。
    • swap(Size lhsIndex, Size rhsIndex) メソッドにより、2つのインデクスにより指定された配列の値を入れ替えます。
    • swap(Type lhs[], Type rhs[]) 関数により、2つの可変長配列の内容を入れ替えます。この操作はデータのコピーを伴わず、一定時間で完了します。
/*
** Example: Variable-Size Arrays
*/

operator entry() {
  Integer a[];
  report("The array a has size " + a.size() + " and value " + a);
  a.push(42);
  a.push(84);
  report("The array NOW has size " + a.size() + " and value " + a);
  a.resize(4);
  report("The array NOW has size " + a.size() + " and value " + a);
  String b[](4);
  report("b is initially " + b);
}

/*
** Output:

The array a has size 0 and value []
The array NOW has size 2 and value [42,84]
The array NOW has size 4 and value [42,84,0,0]
b is initially ["","","",""]

*/

固定長配列(Fixed-Size Arrays)

固定長配列( fixed-size array )とは、実行時サイズが固定されている配列です。固定長配列は可変長配列よりもパフォーマンス特性に優れ、コンパイル時に配列のサイズが判明している場合、可変長配列のかわりに使用します。固定長配列を宣言するには、変数、パラメータ、構造体メンバ名の後に [size] を付け足します。例: String strings[4]

固定長配列は properties of arrays をすべて持ち、さらに追加の特徴があります:

  • 配列の最大サイズは \(2^31-1\).

    警告

    固定長配列はスタック上に確保される(ヒープではありません)ため、巨大な固定長配列を使用するとスタックオーバーフローを引き起こします。固定長配列を使用する際は、適正な大きさである場合に限りましょう。
  • 固定長配列は、割当時にコピーされます。つまり copy-by-value です。
/*
** Example: Fixed-Size Arrays
*/

function Float32 det(Float32 mat[2][2]) {
  return mat[0][0] * mat[1][1] - mat[0][1] * mat[1][0];
}

operator entry() {
  Float32 mat[2][2];
  mat[0][0] = 3.5;
  mat[0][1] = -9.2;
  mat[1][0] = -2.1;
  mat[1][1] = 8.6;
  report("The determinant of " + mat + " is " + det(mat));
}

/*
** Output:

The determinant of [[+3.5,-9.2],[-2.1,+8.6]] is +10.78

*/

外部配列(External Arrays)

external array とは作成時にサイズが固定され、データ操作時にはそのデータを所有しません。外部配列はおもに、オペレータのパラメータ ― Fabric Engine dependency graph 内でのスライスデータと結合(bind)されたパラメータだけでなく、 Fabric Engine エクステンション内の外部のデータと結合されたパラメータにも使います。また、KL内で使用することも可能です。外部配列の宣言には、変数、パラメータ、構造体メンバの名前の後ろに、 <> を食わ会えます。例: String strings<>

外部配列は、 properties of arrays をすべて持ちさらに追加の特徴を持ちます:

  • 外部配列は、既存の可変長配列からコンストラクト可能です。これは単に、その可変長配列がコンストラクトされた時点の可変長配列に含まれるデータを指します。ただし、この使用方法にはたくさんの穴があります。たとえば可変長配列をリサイズした場合などです。これは主にテストのため使用してください。
    String va[];
    va.push("hello");
    String ea<>(va);
    report(ea); // prints ["hello"]
    
  • 外部配列は空コンストラクタ(empty constructor)をサポートします。(空の外部配列がコンストラクトされます)
    Size ea<>;
    report(ea); // prints []
    
  • 外部配列は、コピーコンストラクタ、代入演算子両方サポートし、他方の配列と同じデータを参照する外部配列を作成します。
    String va[];
    va.push("hello");
    String ea1<>(va);
    String ea2<>(ea1);
    report(ea2); // prints ["hello"]
    String ea3<>;
    ea3 = ea2;
    report(ea3); // prints ["hello"]
    
  • 外部配列は、 size() メソッドをサポートし、外部配列の要素数を返します。
  • 外部配列は、 data ポインタだけでなく size を指定し初期化できます。これにより任意のメモリを配列として割り当てできます。とくにC++からKLへと(もしくはその逆)データを渡す際に便利です。このコンストラクタを使い、どんなメモリも配列として再解釈することができます:
    Float32 floats[12];
    for(Size i=0; i<floats.size(); i++)
      floats[i] = Float32(i);
    
    Vec3 vectors<>(floats.data(), floats.size() / 3);
    report(vectors);
    
  • 可変長配列とは逆に、外部配列は参照カウントされないオブジェクトです。つまり受け渡し低価でありますが、メモリのライフタイムを管理することはできなくなります。(次項参照)
  • 外部配列は、それが操作されるデータの生存期間を管理できません。可変長配列は破棄時にメモリが開放されますが、外部配列は単に他のオブジェクトが所持するメモリをマップしたものに過ぎません。外部配列は真のデータ所持者より長生きしてはいけません、さもなくば不要なゴミデータにマップされてしまいます。
    String ea<>;
    {
      String va[];
      for(Integer i=0; i<2023; i++)
        va.push("hello:" + i);
      String ea1<>(va);
      ea = ea1;
      // At the end of this scope, the variable array is freed, along with its data.
    }
    // The memory of the variable array is now garbage because it has been destroyed.
    // Printing the data will return garbage data or crash KL.
    // External arrays must be used with care to avoid mapping to garbage data in this way.
    report(ea);
    

外部配列の使用例:

/*
** Example: External Arrays
*/

operator entry() {
  String va[];
  for (Size i=0; i<8; ++i)
    va.push("string " + (i+1));

  String strings<>(va);
  for (Size i=0; i<8; ++i)
    strings[i] += " appended";
  report("strings = " + strings);
  report("va = " + va);
}

/*
** Output:

strings = ["string 1 appended","string 2 appended","string 3 appended","string 4 appended","string 5 appended","string 6 appended","string 7 appended","string 8 appended"]
va = ["string 1 appended","string 2 appended","string 3 appended","string 4 appended","string 5 appended","string 6 appended","string 7 appended","string 8 appended"]

*/

辞書(Dictionaries)

KLは、キーと値のペア、辞書( dictionaries )をサポートします。辞書のキーにはKL基本型( Boolean, String, 整数型、浮動小数点型)だけでなく、 .hash メソッドが定義された独自定義した型 (see キーにカスタム型を使用した辞書)を、そして辞書の値にはどのような型も取れます。辞書の宣言には、変数(パラメータ、メンバ名)に [KeyType] を付けます。例:

String scalarToString[Float32];     // A Float32-to-String dictionary
Boolean integerToBoolean[Integer];  // An Integer-to-Boolean dictionary

KL での辞書には以下の特徴があります:

  • share-on-assign です。他の辞書に割当てる際、内容そのものをコピーするのではなく内容への参照をコピーします。つまり、一方の辞書への変更が、他方にも作用します。これはシャローコピーと呼ばれます。(対義語はディープコピー)辞書のディープコピーを得るには、 clone() メソッドを用います。以下に説明します。
  • ネスト可能です。配列型をネストさせることができます。例:
    Integer b[String][2]; // An String-to-Fixed-Length-Integer-Array dictionary
    Boolean a[][Integer]; // A variable array of Integer-to-Boolean dictionaries
    
  • 最大要素数は \(2^{32}-1\)
  • has(key) メソッドにより、与えられた key 要素が存在するかどうかを Boolean の値で返します。
  • get(key) メソッドにより、与えられた key に対応する値を返します。対応する値が存在しない場合例外を投げます。
  • set(key, value) メソッドにより、 key に対応した 値 を設定します。キーに対応する値が既に存在する場合、値を上書きします。
  • [key] インデクサ演算子によるインデクシングに対応しています。代入ターゲットとしての使用、あるいは関数の ioパラメータとしての使用は(例: dict[key] = value )、 set(key, value) メソッドと同等です。read-onlyな式としての使用は、(例: value = dict[key])は、 get(key) と同等です。
  • get(key, defaultValue) メソッドにより、key に対応する値が存在すればその値を、そうでなければ defaultValue` を返します。
  • delete(key) メソッドにより、与えられた key に対する値を削除します。与えられたkeyが存在しない場合は、なにも置きません。
  • clone() メソッドにより、辞書のディープコピーを作成します。作成されたコピーは初期状態で他の何物の辞書からも共有されません。
  • clear() メソッドにより、全てのキーと値を取り除きます。
  • JavaScriptのように in により、繰り返しが可能です。
    String dict[String];
    for (k in dict)
      report("dict[" + k + "] = " + dict[k]);
    

    パフォーマンス向上のため、キーと値の両方を in の反復中利用可能とななります。

    String dict[String];
    for (k, v in dict)
      report("dict[" + k + "] = " + v);
    

    辞書の反復処理中、値はその辞書自体が割り当て済であれば、代入可能です。一方キーは反復処理中での代入は不可能です。

  • 挿入順(ソート順ではなく)が、反復処理の順序となります。JavaScriptのオブジェクトと同じです。
    operator entry() {
      String numbers[Integer];
      numbers[3] = "three";
      numbers[2] = "two";
      report(numbers);
      numbers[1] = "one";
      report(numbers);
    }
    
    /*
    ** Output:
    
    {3:"three",2:"two"}
    {3:"three",2:"two",1:"one"}
    
    */
    

辞書の利用例:

/*
** Example: Dictionaries
*/

operator entry() {
  Float32 a[String];
  a['pi'] = 3.14;
  a['e'] = 2.71;
  report("a is:");
  for ( k, v in a ) {
    report("a['" + k + "'] = " + v);
  }
  a.delete('pi');
  report("a is now:");
  for ( k, v in a ) {
    report("a['" + k + "'] = " + v);
  }
}

/*
** Output:

a is:
a['pi'] = +3.14
a['e'] = +2.71
a is now:
a['e'] = +2.71

*/

キーにカスタム型を使用した辞書

独自定義した構造体 struct を辞書のキーの型として使用できます。型に .hash メソッド、 == 演算子 を実装します。

/*
** Example: Dictionary with Custom Key Type
*/

struct S {
  UInt32 n;
  Float32 x;
};


function S(UInt32 n, Float32 x) {
  this.n = n;
  this.x = x;
}

function Boolean ==(S lhs, S rhs ) {
  return lhs.n == rhs.n && lhs.x == rhs.x;
}

function UInt32 S.hash() {
  return this.n.hash() ^ this.x.hash();
}

operator entry() {
  String d[S];
  d[S(56,2.4)] = "one";
  d[S(78,-1.2)] = "two";
  report(d);
}

/*
** Output:

{{n:56,x:+2.4}:"one",{n:78,x:-1.2}:"two"}

*/

Map-Reduce 型

Fabric の map-reduce フレームワークには、排他的に使用可能な2つの派生型があります:

  • ValueProducer<Type>
  • ArrayProducer<Type>

Map-Reduce の詳細を参照してください。

KL での型の暗黙的キャスト

KLにおいて、引数の型がその関数(もしくはメソッド)のどの多態なパラメータとも一致させずに、関数やメソッドを呼んぶと、KLは、最善の型を探すため暗黙のキャストを行います。以下に従い最善なものが選択されます:

  • 引数の数は、パラメータの数と完全に一致させます。仮にミスマッチがあると、多態バージョンは無視されます。例を挙げると、関数呼び出し foo(14, 23) がなされ、対して foo(Integer) が利用可能な場合、関数で処理可能なパラメータは1つなのに、与えられた引数が2つなため適切に処理されません。
  • 引数(複数)の数と、パラメータの数が合っている場合、呼び出し『コスト』はそれぞれの引数の最大のコストと等しくなります。それぞれの引数のコストは以下:
    • 型が一致していればコストは 0
    • 継承関係であればコストは 極小 (例:受付パラメータの型が A, 呼び出し引数の型が B, BA を引き継ぎ)
    • 以上に当てはまらない場合、コストは型のよりけりです。一般的に期待される通りではあります。例えば、小さい整数型からより大きい整数型( UInt16 to UInt32 など)はとても低コストです。一方高価な操作(文字列への変換, 精度を失うような数値変換)は高コストになります。

型の別名(Type Aliases)

エイリアス( alias )文により、ある型に別名を振りコードの可読性を向上させます。変数宣言の文法と同一の文法です:

alias Integer Int32;        // Int32 is now an alias for Integer
alias Float32 float;        // float is now an alias for Float32
alias Float32 Mat22[2][2];  // Mat22 is now an alias for Float32[2][2], ie. a size-2-array-of-size-2-arrays-of-Float32

alias 文はKLプログラム中、グローバルスコープに置きます。

/*
** Example: Type Aliases
*/

alias Float32 Mat22[2][2];

operator entry() {
  Mat22 mat22;
  report(mat22);
}

/*
** Output:

[[+0.0,+0.0],[+0.0,+0.0]]

*/

Datadata, dataSize メソッド

OpenGLなどの外部ライブラリとの接続を行う際、値の基礎となるデータへ直接のアクセスを必要とすることがあります。例えば、データへのポインタを受け付けるライブラリです。KL自体にはポインタの概念がありません。ですが、KLには Data 型 ―この型の値は外部ライブラリ呼び出しの際に渡されるデータへのポインタを示す― のコンセプトがあります。

KLでの多くの値は、組み込みメソッド data を持ち、 Data 型の値を返します。さらに組み込みメソッド dataSize を持ち、 Size 型の値を返します。 data メソッドは値の基であるデータへのポインタを返し、 dataSize はメモリに占める値のバイト数を返します。ただひとつ、辞書(およびその派生型)はこの data and dataSize メソッドを持ちません。なぜなならこの型の値の要素(もしくはメンバ)は、メモリ上に連続して配置されるわけではないからです。

/*
** Example: Valid and Invalid Use of .data() and .dataSize()
*/

Integer integers[];
report(integers.data());  // OK: integers are contiguous in memory
String strings[];
report(strings.data());   // ERROR: string data is not contiguous in memory

CやC++言語のポインターと異なり、 data メソッドの返す値はインスペクト不可、いかなる式での使用も不可です。この値は、 Boolean へのキャストだけが行えます。 Data の値が 0以上の値を示していれば true になります。しかし Data の値はそのまま直接外部ライブラリの関数 ―Fabric自身もしくはFabricエクステンションでも、メモリ上のデータへのポインタを使用する箇所でこへでも与えることができます。

注釈

For values of type String, the value returned by dataSize includes a null terminator that is automatically appended to the string by Fabric; this is so that the string data can be directly used in C library calls as a regular C string. If you want to pass the number of characters in the string, pass string.length() instead.

Data の値 data, dataSize メソッドの使用例:

/*
** Example: .data() and .dataSize()
*/

operator entry() {
  String s;
  report("s = '" + s + "'");
  report("s.data() = " + s.data());
  report("Boolean(s.data()) = " + Boolean(s.data()));
  report("s.dataSize() = " + s.dataSize());
  s = "Hello";
  report("s = '" + s + "'");
  report("s.data() = " + s.data());
  report("Boolean(s.data()) = " + Boolean(s.data()));
  report("s.dataSize() = " + s.dataSize());
}

/*
** Output:

s = ''
s.data() = <Opaque>
Boolean(s.data()) = true
s.dataSize() = 1
s = 'Hello'
s.data() = <Opaque>
Boolean(s.data()) = true
s.dataSize() = 6

*/