0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Composite Types

Last updated at Posted at 2024-10-26

複合型(Composite type)は、より単純な型をより複雑な型に構成することを可能にします。すなわち、複数の値を1つの値に構成することを可能にします。複合型には名前があり、名前のついたフィールドを0個以上、およびデータ上で動作する関数を0個以上含みます。各フィールドは異なる型を持つことができます。

複合型は、スマートコントラクト内でのみ宣言でき、それ以外では宣言できません。

複合型には2種類あります。 これらは以下の点で使用法と動作が異なります。定数または変数の初期値として値が使用される時、値が変数に割り当てられる時、値が関数の引数として渡される時、および関数から返り値として返される時。

  • 構造体(Struct)はコピーされ、値型です。 構造体は、独立した状態のコピーが必要な場合に便利です。
  • リソース(Resource)は移動され、リニア型です。リソースは所有権のモデル化に役立ちます(価値は1つの場所に正確に存在し、失われるべきではありません)。ブロックチェーンの特定の構造は、家や車、銀行口座など、現実の有形価値のある資産を表します。文字通り、数百万ドル規模の損失や盗難を心配しなければなりません。構造体はコピーされるため、この所有権を表す理想的な方法ではありません。つまり、特定の資産の複数のコピーが存在する可能性があるということになり、その資産が真の価値を持つために必要な希少性の要件を満たさなくなります。構造体は、論理的にグループ化できる情報を表現するにははるかに有用であるが、所有されたり譲渡される必要性もしくは価値を持ちません。構造体は、例えば、企業の部門に関連する情報を格納するために使用できるが、支出のためにその組織に割り当てられた資産を表すにはリソースが使用される。

リソースのネストは、他のリソースタイプ内、または配列やディクショナリなどのデータ構造体内でのみ許可され、リソースのコピーを許可することになるため、構造体内では許可されない。

Composite Type Declaration and Creation

構造体はstructキーワードを使用して宣言され、リソースはresourceキーワードを使用して宣言されます。キーワードの後に名前が続きます。

access(all)
struct SomeStruct {
    // ...
}

access(all)
resource SomeResource {
    // ...
}

構造体とリソースは型です。
構造体は、型を関数のように呼び出すことで作成(インスタンス化)されます。

// instantiate a new struct object and assign it to a constant
let a = SomeStruct()

複合型のイニシャライザー(init関数)がパラメータを必要とする場合、コンストラクタ関数(init関数)にパラメータを渡します。

複合型は、スマートコントラクト内でのみ宣言でき、関数内でローカルに宣言することはできません。

リソース(Resource)は、create キーワードを使用して作成(インスタンス化)し、関数のようにその型を呼び出す必要があります。

リソースは、リソースの型が宣言されているのと同じスマートコントラクト内の関数でのみ作成できます。

// instantiate a new resource object and assign it to a constant
let b <- create SomeResource()

Composite Type Fields

フィールドは変数や定数と同様に宣言されます。ただし、フィールドの初期値はフィールド宣言ではなくイニシャライザー(init関数)で設定します。すべてのフィールドはinit関数で必ず一度だけ初期化する必要があります。

init関数で初期値を指定しなければならないのは制限的に見えるかもしれませんが、これにより、すべてのフィールドが常にイニシャライザー(init関数)という1つの場所で初期化され、初期化の順序が明確になります。

すべてのフィールドの初期化は静的にチェックされ、init関数ですべてのフィールドを初期化しないと無効になります。また、フィールドが使用される前に確実に初期化されていることも静的にチェックされます。

イニシャライザー(init関数)の主な目的はフィールドの初期化ですが、他のコードを含めることもできます。関数と同様に、パラメータを宣言したり、任意のコードを含めることができます。ただし、イニシャライザー(init関数)には戻り値の型がありません。つまり、常にVoidです。

イニシャライザーは、initキーワードを使用して宣言します。

フィールドには2種類あります。

  • Constant(定数)フィールドは複合値も格納されますが、値で初期化された後は、それ以降に新しい値を割り当てることはできません。定数フィールドは、正確に1回だけ初期化する必要があります。定数フィールドは、letキーワードを使用して宣言します。
  • Variable(変数)フィールドは複合値に格納され、新しい値を割り当てることができます。変数フィールドは、varキーワードを使用して宣言します。

初期化子では、特別な定数selfは初期化される複合値を参照します。

複合型を格納する場合は、そのフィールド型はすべて格納可能でなければなりません。格納不可能な型は以下のとおりです。
関数(Function)
アカウント(Account)
トランザクション
参照(Reference):参照は一時的なものです。代わりに、必要に応じてCapabilityを格納し、それを借用(borrow)することを検討してください。

フィールドは、アクセス構文(syntax)を使用して、(定数または変数なら)読み取りおよび(変数なら)セットが可能です。複合値の後にドット(.)を付け、フィールド名を続けます。

// Declare a structure named `Token`, which has a constant field
// named `id` and a variable field named `balance`.
//
// Both fields are initialized through the initializer.
//
// The public access modifier `access(all)` is used in this example to allow
// the fields to be read in outer scopes. Fields can also be declared
// private so they cannot be accessed in outer scopes.
// Access control will be explained in a later section.
//
access(all)
struct Token {

    access(all)
    let id: Int

    access(all)
    var balance: Int

    init(id: Int, balance: Int) {
        self.id = id
        self.balance = balance
    }
}

フィールド宣言でフィールドの初期値を指定することは出来ないことに注意してください。

access(all)
struct StructureWithConstantField {
    // Invalid: It is invalid to provide an initial value in the field declaration.
    // The field must be initialized by setting the initial value in the initializer.
    //
    access(all)
    let id: Int = 1
}

フィールドにアクセスするには、アクセス構文(syntax)を使用する必要があります。

access(all)
struct Token {

    access(all)
    let id: Int

    init(initialID: Int) {
        // Invalid: There is no variable with the name `id` available.
        // The field `id` must be initialized by setting `self.id`.
        //
        id = initialID
    }
}

イニシャライザー(init関数)は自動的に派生せず、明示的に宣言する必要があります。

access(all)
struct Token {

    access(all)
    let id: Int

    // Invalid: Missing initializer initializing field `id`.
}

コンストラクタを呼び出し、フィールド値を引数として指定することで、複合値を作成することができます。

作成後にオブジェクトの値のフィールドにアクセスすることができます。

let token = Token(id: 42, balance: 1_000_00)

token.id  // is `42`
token.balance  // is `1_000_000`

token.balance = 1
// `token.balance` is `1`

// Invalid: assignment to constant field
//
token.id = 23

init関数はオーバーロード(overload)をサポートしません。

init関数は、view キーワードを指定して宣言することもでき、これにより、変更操作(mutating operation)を行わないことを示し、他のview 関数から呼び出せるようにすることができます。(補足: 変更が発生しない、ということはトランザクションでなくても(スクリプトで)呼び出せることを意味します。但しinit関数をviewにする利点は分かりません。) init関数では、self への書き込みは(他の複合関数内とは異なり)ここで構成される値が、定義上、init関数を呼び出すコンテキストに対しローカルであるためview と見なされます。

access(all)
struct Token {

    access(all)
    let id: Int

    access(all)
    var balance: Int

    view init(id: Int, balance: Int) {
        self.id = id
        self.balance = balance
    }
}

Composite Type Functions

複合型には関数が含まれている場合があります。init関数の場合と同様に、特別な定数selfは、関数が呼び出される複合型(構造体, リソース)を指します。

// Declare a structure named "Rectangle", which represents a rectangle
// and has variable fields for the width and height.
//
access(all)
struct Rectangle {

    access(all)
    var width: Int

    access(all)
    var height: Int

    init(width: Int, height: Int) {
        self.width = width
        self.height = height
    }

    // Declare a function named "scale", which scales
    // the rectangle by the given factor.
    //
    access(all)
    fun scale(factor: Int) {
        self.width = self.width * factor
        self.height = self.height * factor
    }
}

let rectangle = Rectangle(width: 2, height: 3)
rectangle.scale(factor: 4)
// `rectangle.width` is `8`
// `rectangle.height` is `12`

関数はオーバーロードをサポートしていません。

Composite Type Subtyping

2つの複合型が互換性を持つのは、同じ宣言を名前で参照する場合のみです。

2つの複合型が同じフィールドと関数を宣言している場合でも、それらの名前が一致する場合のみ、型は互換性を持つ。

すなわち、構造的型付けではなく名義的型付けが適用されています。

// Declare a structure named `A` which has a function `test`
// which has type `fun(): Void`.
//
struct A {
    fun test() {}
}

// Declare a structure named `B` which has a function `test`
// which has type `fun(): Void`.
//
struct B {
    fun test() {}
}

// Declare a variable named which accepts values of type `A`.
//
var something: A = A()

// Invalid: Assign a value of type `B` to the variable.
// Even though types `A` and `B` have the same declarations,
// a function with the same name and type, the types' names differ,
// so they are not compatible.
//
something = B()

// Valid: Reassign a new value of type `A`.
//
something = A()

Composite Type Behaviour

Structures

構造体は、定数または変数の初期値として使用された時、異なる変数に割り当てられた時、関数の引数として渡された時、関数から返された時にその値がコピーされます。

構造体のフィールドにアクセスしたり、関数を呼び出したりしても、構造体がコピーされることはありません。

// Declare a structure named `SomeStruct`, with a variable integer field.
//
access(all)
struct SomeStruct {
    
    access(all)
    var value: Int

    init(value: Int) {
        self.value = value
    }

    fun increment() {
        self.value = self.value + 1
    }
}

// Declare a constant with value of structure type `SomeStruct`.
//
let a = SomeStruct(value: 0)

// *Copy* the structure value into a new constant.
//
let b = a

b.value = 1
// NOTE: `b.value` is 1, `a.value` is *`0`*

b.increment()
// `b.value` is 2, `a.value` is `0`

Accessing Fields and Functions of Composite Types Using Optional Chaining

フィールドと関数(function)を持つ複合型がオプショナルでラップされている場合、オプショナルチェイニングを使用して、フィールド値を取得したり関数を呼び出したりすることができます。

オプショナルチェイニングは、オプショナル複合型のフィールドまたは関数に対して付けるアクセス演算子.の前に?を追加することで使用します。

フィールド値を取得したり、戻り値のある関数を呼び出す場合、値をオプショナルとして返します。オブジェクトが存在しない場合、値は常にnilとなります。

このようにオプショナルに対して関数を呼び出す場合、オブジェクトが存在しない場合には何も起こらず、実行が継続されます。

// Declare a struct with a field and method.
access(all)
struct Value {

    access(all)
    var number: Int

    init() {
        self.number = 2
    }

    access(all)
    fun set(new: Int) {
        self.number = new
    }

    access(all)
    fun setAndReturn(new: Int): Int {
        self.number = new
        return new
    }
}

// Create a new instance of the struct as an optional
let value: Value? = Value()
// Create another optional with the same type, but nil
let noValue: Value? = nil

// Access the `number` field using optional chaining
let twoOpt = value?.number
// Because `value` is an optional, `twoOpt` has type `Int?`
let two = twoOpt ?? 0
// `two` is `2`

// Try to access the `number` field of `noValue`, which has type `Value?`.
// This still returns an `Int?`
let nilValue = noValue?.number
// This time, since `noValue` is `nil`, `nilValue` will also be `nil`

// Try to call the `set` function of `value`.
// The function call is performed, as `value` is not nil
value?.set(new: 4)

// Try to call the `set` function of `noValue`.
// The function call is *not* performed, as `noValue` is nil
noValue?.set(new: 4)

// Call the `setAndReturn` function, which returns an `Int`.
// Because `value` is an optional, the return value is type `Int?`
let sixOpt = value?.setAndReturn(new: 6)
let six = sixOpt ?? 0
// `six` is `6`

オプショナルは、強制アンラップ演算子(!)を使用することも出来ます。

強制オプショナルチェイニングは、オプショナルの複合型のフィールドまたは関数に対してつけるアクセス演算子.の前に!を追加することで使用します。

フィールド値を取得したり、戻り値を持つ関数を呼び出す時、オブジェクトが存在しない場合、実行はパニックを起こしてリバートされます。(補足: 関数の実行がなかったことになります)

// Declare a struct with a field and method.
access(all)
struct Value {

    access(all)
    var number: Int

    init() {
        self.number = 2
    }

    access(all)
    fun set(new: Int) {
        self.number = new
    }

    access(all)
    fun setAndReturn(new: Int): Int {
        self.number = new
        return new
    }
}

// Create a new instance of the struct as an optional
let value: Value? = Value()
// Create another optional with the same type, but nil
let noValue: Value? = nil

// Access the `number` field using force-optional chaining
let two = value!.number
// `two` is `2`

// Try to access the `number` field of `noValue`, which has type `Value?`
// Run-time error: This time, since `noValue` is `nil`,
// the program execution will revert
let number = noValue!.number

// Call the `set` function of the struct

// This succeeds and sets the value to 4
value!.set(new: 4)

// Run-time error: Since `noValue` is nil, the value is not set
// and the program execution reverts.
noValue!.set(new: 4)

// Call the `setAndReturn` function, which returns an `Int`
// Because we use force-unwrap before calling the function,
// the return value is type `Int`
let six = value!.setAndReturn(new: 6)
// `six` is `6`

Resources

リソースについては、次のページで詳しく説明されています。

Unbound References / Nulls

nullに対するサポートはありません。

Inheritance and Abstract Types

継承のサポートはありません。継承は、ある型のフィールドや関数を別の型に含めることを可能にする、他のプログラミング言語では一般的な機能です。

その代わりに、「継承よりも合成」の原則に従い、継承ツリーを構築するのではなく、複数の個々の部分から機能性を合成するという考え方です。

さらに、抽象型(abstract type)もサポートされていません。抽象型は、他のプログラミング言語では一般的な機能であり、その型の値を作成することを防ぎ、サブタイプの値の作成のみを許可します。また、抽象型は関数を宣言することはできますが、その実装は省略し、サブタイプに実装を要求します。

代わりに、インターフェースの利用を検討してください。

翻訳元->https://cadence-lang.org/docs/language/composite-types

Flow BlockchainのCadence version1.0ドキュメント (Composite Types)

Previous << Type Inference(型推論)

Next >> Resources

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?