Schemeは動的な強い型付けの言語です。つまり、全ての値は 実行時に自分の型を知っていて、その型が値に適用できる操作を決定します。
Scheme標準は、型についてはとても単純です。基本的に、オブジェクトがある型であるというのは、 そのオブジェクトに対して型述語が真を返す、それだけです。 Gaucheはもう少し込み入ったシステムを採用していて、型も第一級のオブジェクトになり 様々な情報を問い合わせることができます。
Gaucheでは慣習的に、型は<string>
のように
<
と>
で囲まれた名前を持ちます。
この<
や>
は文法的には何ら特別な意味を持ちません。
普通に変数名として使える文字です。
• 規範的な型と記述的な型 | ||
• 汎用型述語 | ||
• 定義済みクラス | ||
• 型の式と型コンストラクタ | ||
• ネイティブタイプ |
型には2つの使われ方があります。まず、型は実際の値(インスタンス)のテンプレートと
見ることができます。つまり、型がインスタンスの構造と、それに適用できる操作を
あらかじめ決めている(prescribe)ということです。
Gaucheでは、このような規範的(prescriptive)な型は、クラスで表現されます。
Gauche内での値は全て何かのクラスに属していて、
class-of
手続きでそのクラスを取れます。また、ユーザは独自のクラスを
define-class
やdefine-record-type
で定義することもできます
(オブジェクトシステムおよびレコード型参照)。
型はまた、与えられた式についての制約と見ることもできます。 つまり、型がその式が持つべき特定の性質を記述する(describe)ということです。 Gaucheでは、クラスとは別に、そういった記述的(descriptive)な型を表現するエンティティを 用意しています。記述的な型は型コンストラクタで作ることができます (型の式と型コンストラクタ参照)。 また、Cライブラリとやりとりするための記述的な型も用意されています (ネイティブタイプ参照)。
値は、特定のクラスのインスタンスです。
例えば1
は<integer>
のインスタンスであり、
"xyz"
は<string>
のインスタンスです。
このprescriptiveな型の関係は、手続きis-a?
で検査できます。
(is-a? 1 <integer>)
、(is-a? "xyz" <string>)
は
どちらも#t
を返します。
一方、例えば手続きが数値か文字列を引数に取る、といったケースもあります。
「数値か文字列」というのは型に関する制約であり、descriptiveな型
(</> <number> <string>)
として表現できます。
descriptiveな型の関係は手続きof-type?
で検査できます。
(of-type? 1 (</> <number> <string>))
、
(of-type? "xyz" (</> <number> <string>))
はどちらも#t
を返します。
また、(assume-type arg (</> <number> <string>))
は
argが与えられた型の制約を満たさなければエラーを投げます。
Next: 定義済みクラス, Previous: 規範的な型と記述的な型, Up: 型とクラス [Contents][Index]
「型述語」は、与えられたオブジェクトが特定の型を持つかどうかを判定する述語です。
例えばnumber?
は引数が数値かどうかを返します。
Gaucheでは型がファーストクラスなので、もう一歩進めて、
オブジェクトが与えられた型を持つかどうかを判定する述語や、
型同士の関係を調べる述語が提供されています。
これは規範的な型に関する述語です。 objがclassのインスタンスであるか、classのサブクラスの インスタンスである場合に、真を返します。
(is-a? 3 <integer>) ⇒ #t (is-a? 3 <real>) ⇒ #t (is-a? 5+3i <real>) ⇒ #f (is-a? :foo <symbol>) ⇒ #f
註:objのクラスが再定義されていた場合、is-a?
はインスタンスアップデートを
トリガします。クラスの再定義を参照してください。
これは記述的な型に関する述語です。
objがtypeで記述される制約を満たすなら#t
を、そうでなければ#f
を
返します。typeはクラスか(その場合はis-a?
と同じです)、
記述型 (see 型の式と型コンストラクタ参照) です。
(of-type? 1 (</> <number> <string>)) ⇒ #t (of-type? "a" (</> <number> <string>)) ⇒ #t (of-type? 'a (</> <number> <string>)) ⇒ #f
型subが型superのサブタイプであれば(subとsuperが
同じである場合も含め)#t
を、そうでなければ#f
を返します。
一般的に、subがsuperのサブタイプであれば、 subの制約を満たすオブジェクトはsuperが期待されている箇所で使うことが できます。言い換えれば、subの制約の方がsuperよりきついということです。 subとsuperのどちらもがクラスの場合、subがsuperの サブタイプであるということは、subがsuperのサブクラスであるということです。
(subtype? <integer> <real>) ⇒ #t (subtype? <char> (</> <char> <string>)) ⇒ #t (subtype? (</> <integer> <string>) (</> <number> <string>)) ⇒ #t
この「置換原則」は常に厳密に成り立つわけではないことに注意してください。
例えば「整数のリスト」は大抵の場合、ジェネリックなリストが期待されているところに
使えて、従って(subtype? (<List> <integer>) <list>)
も#t
を
返します。しかし、渡されたリストが変更される場合は、ジェネリックなリストを
整数のリストで置き換え可能とは限りません。コードはリストに整数でないものをセットしようとする
かもしれないからです。こういったケースはsubtype?
に頼るのではなく
個別に対応する必要があります。
引数はどちらもクラスでなければなりません。
subがsuperのサブクラスであれば#t
を、そうでなければ#f
を
返します。クラスはそれ自身のサブクラスとみなされます。
Next: 型の式と型コンストラクタ, Previous: 汎用型述語, Up: 型とクラス [Contents][Index]
組み込みの型に対応するクラスについては、この章の中で順に紹介してゆきます。 まずは次のいくつかのクラスから始めましょう。
全ての型のスーパータイプを表現するクラスです。
つまり、どんなクラスX
に対しても(subtype? X <top>)
は#t
であり、
どんなオブジェクトx
に対しても(is-a? x <top>)
は#t
です。
全ての型のサブタイプを表現するクラスです。
どんなクラスX
に対しても(subtype? <bottom> X)
は#t
であり、
どんなオブジェクトx
に対しても(is-a? x <bottom>)
は#f
です。
<bottom>
型のインスタンスは存在しません。
註: <bottom>
は全ての型のサブタイプですが、そのクラス順位リスト
(class precedence list, CPL)には<bottom>
と<top>
以外のクラスは含まれていません。全ての型を線形に並べることは常に可能である
とは限らず、まだそうであったとしても新たなクラスの定義や既存のクラスの
再定義のたびに<bottom>
のCPLを検査してアップデートすることは
高くつくでしょう。subtype?
やis-a?
といった手続きは
<bottom>
を特別扱いしています。
<bottom>
の使いどころのひとつは、applicable?
手続きです。
Procedure class and applicabilityを参照のこと。
このクラスは、ユーザ定義されたクラスのスーパータイプを表現するクラスです。
objのクラスを返します。
(class-of 3) ⇒ #<class <integer>> (class-of "foo") ⇒ #<class <string>> (class-of <integer>) ⇒ #<class <class>>
註: Gaucheでは、ユーザ定義クラスを再定義することができます。
新たな定義でインスタンスの構造が変更された場合、以前のクラスから作られた
インスタンスにclass-of
を適用すると、インスタンスが新しいクラスに
適合するようにアップデートされます。詳しくはクラスの再定義を参照して
ください。インスタンスアップデートを避けるにはcurrent-class-of
を
使います(インスタンスへのアクセス参照)。
型はGaucheでは実行時に扱える第一級のオブジェクトですが、 静的解析のためにはコンパイル時に型がわかる必要があります。 なので、いくつかの場所では、型を生成する式の値が静的に計算可能であることが要求されます。 そのような式を「型の式 (type expression)」と呼びます。
型の式は、型に静的に束縛されたグローバル変数か、型コンストラクタの呼び出しです:
<type-expression> : <global-variable-constantly-bound-to-a-type> | <type-constructor-call> <type-constructor-call> : (<type-constructor> <type-constructor-argument> ...) <type-constructor-argument> : <type-expression> | <integer-constant> | -> | *
<integer>
のような全ての組み込みクラスは静的に束縛されています
(正確には、「インライン可能な」束縛になっています)。
そのような束縛を変更しようとすると警告が出され、それ以降の動作は保証されません。
将来的には、型に静的に束縛された変数を変更するのはエラーにするつもりです。
define-class
やdefine-record-type
で定義されるクラスも、
インライン可能な束縛になります。
型コンストラクタはそれ自身がクラスで、 そのインスタンスが記述的な型オブジェクトになるものです。 型コンストラクタは構文的には手続き呼び出しのように使えますが、 上の<type-constructor-call>はコンパイラによって認識され、 コンパイル時に計算されて型オブジェクトになります。 なので、ランタイムには結果の型オブジェクトしか見えません。
例えば、<?>
は”maybe”のようなオプショナルな型を作る型コンストラクタです。
(<?> <integer>)
は「<integer>
か#f
」を意味します
(srfi-189 (MaybeとEither、オプショナルなコンテナ型参照) の
Maybe型と混同しないようにしてください)。
この型オブジェクトは #<? <integer>>
と表示されます。
(<?> <integer>) ⇒ #<? <integer>>
型コンストラクタの呼び出しは手続き呼び出しのように見えますが、コンパイル時に計算されます。
gosh> (disasm (^[] (<?> <integer>))) CLOSURE #<closure (#f)> === main_code (name=#f, cc=0x7f5cd76ab540, codevec=...): signatureInfo: ((#f)) 0 CONST-RET #<? <integer>>
Type must be a type expression.
This creates a maybe-like type, that is, the object is either
of type or #f
. Usually, #f
indicates
that the value is invalid (e.g. the return value of assoc
).
This type has ambiguity when #f
can be a meaningful value, but
traditionally been used a lot in Scheme, for it is lightweight.
We also have a “proper” Maybe type in srfi-189
(see MaybeとEither、オプショナルなコンテナ型) but that involves
extra allocation to wrap the value.
Type … must be type expressions.
This creates a sum type of type ….
For example, (</> <string> <symbol>)
is a type that is
either a string or a symbol.
Type … must be type expressions, except that the
last argument that may be an identifier *
.
This creates a product type of type ….
Actually, this type is looser than the product types in typical
statically-typed languages. Our tuple is a subtype of list, with
types of each positional element are restricted with type ….
For example, (<Tuple> <integer> <string>)
is a list of two
elements, the first one being an integer and the second being a string.
(of-type? '(3 "abc") (<Tuple> <integer> <string>)) ⇒ #t
If you need a more strict and disjoint product type, you can just create a class or a record.
If the last argument is *
, the resulting type allows
extra elements after the typed elements.
(of-type? '(3 "abc" 1 2) (<Tuple> <integer> <string> *)) ⇒ #t
->
type …Type … must be type expressions, except that the
one right before ->
and the last one may be an identifier *
.
This creates a procedure type, with the constraints in
arguments and return types.
The type … before ->
are the argument types,
and the ones after ->
are the result types.
The *
in the last of argument type list and/or result type list
indicates extra elements are allowed.
In vanilla Scheme, all procedures belong to just one type, that responds
true to procedure?
. It is simple and flexible, but sometimes the
resolution is too coarse to do reasoning on the program.
In Gauche, we can attach more detailed type information in procedures.
In the current version, some built-in procedures already have such
type information, that can be retrieved with procedure-type
:
(procedure-type cons) ⇒ #<^ <top> <top> -> <pair>>
Not all procedures have such information, though. Do not expect the rigorousness of statically typed languages.
At this moment, Scheme-defined procedures treats all argument types
as <top>
. We’ll provide a way to attach type info in future.
Type must be a type expression.
These create a list or a vector type whose elements are of type,
respectively.
For example, (<List> <string>)
is a list of strings.
The optional arguments must be a literal real numbers that limit
the minimum and maximum length of the list or the vector.
When omitted, min-length
is zero and max-length is inf.0
.
Previous: 型の式と型コンストラクタ, Up: 型とクラス [Contents][Index]
Native types are a set of predefined descriptive types that bridge Scheme types and C types. They can be used to access binary blobs passed from a foreign functions, for example. They’ll also be used for FFI.
An example of native types is <int16>
, which corresponds to
C’s int16_t
. You can use <int16>
to check if a Scheme
object can be treated as that type:
(of-type? 357 <int16>) ⇒ #t (of-type? 50000 <int16>) ⇒ #f
Or obtain size and alignemnt info:
gosh> (describe <int16>) #<native-type <int16>> is an instance of class <native-type> slots: name : <int16> super : #<class <integer>> c-type-name: "int16_t" size : 2 alignment : 2
Predefined native types. The correspondence of Scheme and C types are shown below:
Native type Scheme C Notes ----------------------------------------------------------------- <fixnum> <integer> ScmSmallInt Integers within fixnum range <int> <integer> int Integers representable in C <int8> <integer> int8_t <int16> <integer> int16_t <int32> <integer> int32_t <int64> <integer> int64_t <short> <integer> short <long> <integer> long <uint> <integer> uint Integers representable in C <uint8> <integer> uint8_t <uint16> <integer> uint16_t <uint32> <integer> uint32_t <uint6r> <integer> uint64_t <ushort> <integer> ushort <ulong> <integer> ulong <float> <real> float Unboxed value casted to float <double> <real> double <size_t> <integer> size_t System-dependent types <ssize_t> <integer> ssize_t <ptrdiff_t> <integer> ptrdiff_t <off_t> <integer> off_t <void> - void (Used only as a return type. Scheme function returns #<undef>)
Previous: 型の式と型コンストラクタ, Up: 型とクラス [Contents][Index]