データ型
プロトタイプベースであるため、厳密にどのオブジェクトが型なのかという分別は内部的にはありません。とはいえ、型として扱われるために定義されているデフォルトのオブジェクトというものは存在し、リテラルオブジェクト等はそれらの子供として生成されます。
型の参照
オブジェクトの型が何であるかを知るための手段を示します。
@nameで型名が得られます。
0.@name.flush //Intが出力 ''.@name.flush //Stringが出力 :str.@name.flush //Symbol_idが出力 0.@name.@name.flush //Symbol_idが出力 @name.flush //Contextが出力
type?(:id)で、そのオブジェクトまたはその先祖が、引数に指定された名前の@nameを持つかどうかの真偽値を得られます。
0.type?(:Int).flush //1が出力 0.type?(:Double).flush //0が出力 0.type?(:Number).flush //1が出力 //Int等数値リテラルの親はNumber 0.type?(:Object).flush //1が出力 //全てのオブジェクトの始祖はObject ''.type?(:String).flush //1が出力 ''.type?(:Container).flush //1が出力 //所謂レンジは全てContainerの子供 ''.type?(:Number).flush //0が出力
@nameは、型として扱われる先祖が自主的に@nameというメンバを自分の名前として保持しているだけですので、実際の内部的な型とは別の次元のものです。内部的な型は@instance_typeで参照できます。
0.@instance_type.flush //int constが出力 0.@instance_constness.flush //1が出力
参照用のものなので、これから型やconstnessを書き換えることはできません。それらには別の手段が提供されます。
型の変換
特別な構文等は無く、普通のメソッドをそれと扱っているだけです。
self.to_Typeで新しくselfを元にしたType型の値を生成します。内部的にも明示的な型変換*1を実行したときに呼ばれます。
0.to_String.flush 0.to_Symbol_id.flush //$undefined$が出力 :Int.to_Uint.flush //Intを表す数値が出力 :Int.to_Uint.to_Symbol_id.flush //Intが出力 100.to_String(0x10).flush //64が出力 //Int等のto_Stringは基数を取れる
self.as_Typeは暗黙の型変換に使用されます。主にC++の関数の呼び出し時に自動的に呼ばれます。
0.type?('Int').flush //1が出力 //type?の引数はSymbol_idだが、Stringがas_Symbol_idを持っているため可能 0.type?(0).flush //エラー //Intにto_Symbol_idはあるがas_Symbol_idは無いため。
to_Type以外の変換方法として、newがあります。to_Typeは基本的な型に対してのみ定義されていますから、例えばInt型のnという名のオブジェクトからWindows_HWNDへの変換には、Int.to_Windows_HWNDは定義されていませんので、Windows_HWND.new(n)となります。当然、このnewも予約語の類ではなく、新しくオブジェクトを作るメソッドの名前の慣例のようなものでしかありません。
constnessの変更にはObject.constizeとObject.unconstizeを使います。新しくオブジェクトを生成せず、自身を書き換えるものは最後に!が付きます。
n:: 0 n.@instance_constness.not.assert //Object.assertはselfがfalseな場合に例外を飛ばす関数*2 n2: n.constize n.@instance_constness.not.assert n2.@instance_constness.assert n.constize! n.@instance_constness.assert n3: n.unconstize n.@instance_constness.assert n3.@instance_constness.not.assert n.unconstize! n.@instance_constness.not.assert