For Gauche 0.9.12Search (procedure/syntax/module):

Next: , Previous: , Up: 組み込みライブラリ   [Contents][Index]

6.1 型とクラス

Schemeは動的な強い型付けの言語です。つまり、全ての値は 実行時に自分の型を知っていて、その型が値に適用できる操作を決定します。

Scheme標準は、型についてはとても単純です。基本的に、オブジェクトがある型であるというのは、 そのオブジェクトに対して型述語が真を返す、それだけです。 Gaucheはもう少し込み入ったシステムを採用していて、型も第一級のオブジェクトになり 様々な情報を問い合わせることができます。

Gaucheでは慣習的に、型は<string>のように <>で囲まれた名前を持ちます。 この<>は文法的には何ら特別な意味を持ちません。 普通に変数名として使える文字です。


Next: , Previous: , Up: 型とクラス   [Contents][Index]

6.1.1 規範的な型と記述的な型

型には2つの使われ方があります。まず、型は実際の値(インスタンス)のテンプレートと 見ることができます。つまり、型がインスタンスの構造と、それに適用できる操作を あらかじめ決めている(prescribe)ということです。 Gaucheでは、このような規範的(prescriptive)な型は、クラスで表現されます。 Gauche内での値は全て何かのクラスに属していて、 class-of手続きでそのクラスを取れます。また、ユーザは独自のクラスを define-classdefine-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]

6.1.2 汎用型述語

「型述語」は、与えられたオブジェクトが特定の型を持つかどうかを判定する述語です。 例えばnumber?は引数が数値かどうかを返します。 Gaucheでは型がファーストクラスなので、もう一歩進めて、 オブジェクトが与えられた型を持つかどうかを判定する述語や、 型同士の関係を調べる述語が提供されています。

Function: is-a? obj class

これは規範的な型に関する述語です。 objclassのインスタンスであるか、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?はインスタンスアップデートを トリガします。クラスの再定義を参照してください。

Function: of-type? obj type

これは記述的な型に関する述語です。 objtypeで記述される制約を満たすなら#tを、そうでなければ#fを 返します。typeはクラスか(その場合はis-a?と同じです)、 記述型 (see 型の式と型コンストラクタ参照) です。

(of-type? 1   (</> <number> <string>)) ⇒ #t
(of-type? "a" (</> <number> <string>)) ⇒ #t
(of-type? 'a  (</> <number> <string>)) ⇒ #f
Function: subtype? sub super

subが型superのサブタイプであれば(subsuperが 同じである場合も含め)#tを、そうでなければ#fを返します。

一般的に、subsuperのサブタイプであれば、 subの制約を満たすオブジェクトはsuperが期待されている箇所で使うことが できます。言い換えれば、subの制約の方がsuperよりきついということです。 subsuperのどちらもがクラスの場合、subsuperの サブタイプであるということは、subsuperのサブクラスであるということです。

(subtype? <integer> <real>) ⇒ #t
(subtype? <char> (</> <char> <string>)) ⇒ #t
(subtype? (</> <integer> <string>) (</> <number> <string>)) ⇒ #t

この「置換原則」は常に厳密に成り立つわけではないことに注意してください。 例えば「整数のリスト」は大抵の場合、ジェネリックなリストが期待されているところに 使えて、従って(subtype? (<List> <integer>) <list>)#tを 返します。しかし、渡されたリストが変更される場合は、ジェネリックなリストを 整数のリストで置き換え可能とは限りません。コードはリストに整数でないものをセットしようとする かもしれないからです。こういったケースはsubtype?に頼るのではなく 個別に対応する必要があります。

Function: subclass? sub super

引数はどちらもクラスでなければなりません。 subsuperのサブクラスであれば#tを、そうでなければ#fを 返します。クラスはそれ自身のサブクラスとみなされます。


Next: , Previous: , Up: 型とクラス   [Contents][Index]

6.1.3 定義済みクラス

組み込みの型に対応するクラスについては、この章の中で順に紹介してゆきます。 まずは次のいくつかのクラスから始めましょう。

Builtin Class: <top>

全ての型のスーパータイプを表現するクラスです。 つまり、どんなクラスXに対しても(subtype? X <top>)#tであり、 どんなオブジェクトxに対しても(is-a? x <top>)#tです。

Builtin Class: <bottom>

全ての型のサブタイプを表現するクラスです。 どんなクラス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を参照のこと。

Builtin Class: <object>

このクラスは、ユーザ定義されたクラスのスーパータイプを表現するクラスです。

Function: class-of obj

objのクラスを返します。

(class-of 3)         ⇒ #<class <integer>>
(class-of "foo")     ⇒ #<class <string>>
(class-of <integer>) ⇒ #<class <class>>

註: Gaucheでは、ユーザ定義クラスを再定義することができます。 新たな定義でインスタンスの構造が変更された場合、以前のクラスから作られた インスタンスにclass-ofを適用すると、インスタンスが新しいクラスに 適合するようにアップデートされます。詳しくはクラスの再定義を参照して ください。インスタンスアップデートを避けるにはcurrent-class-ofを 使います(インスタンスへのアクセス参照)。


Next: , Previous: , Up: 型とクラス   [Contents][Index]

6.1.4 型の式と型コンストラクタ

型は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-classdefine-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 Constructor: <?> type

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 Constructor: </> type …

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 Constructor: <Tuple> type …

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 Constructor: <^> type … -> 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 Constructor: <List> type :optional min-length max-length
Type Constructor: <Vector> type :optional min-length max-length

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]

6.1.5 ネイティブタイプ

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
Native type: <fixnum>
Native type: <int>
Native type: <int8>
Native type: <int16>
Native type: <int32>
Native type: <int64>
Native type: <short>
Native type: <long>
Native type: <uint>
Native type: <uint8>
Native type: <uint16>
Native type: <uint32>
Native type: <uint64>
Native type: <ushort>
Native type: <ulong>
Native type: <float>
Native type: <double>
Native type: <size_t>
Native type: <ssize_t>
Native type: <ptrdiff_t>
Native type: <off_t>
Native type: <void>

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]


For Gauche 0.9.12Search (procedure/syntax/module):