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?

Interfaces

Last updated at Posted at 2024-10-26

インターフェースは、インターフェースを実装する型の振る舞いを指定する抽象型です。インターフェースには以下を宣言します。必要な関数及びフィールド、それらに対するアクセス制御、pre条件、post条件。これを実装した型が提供する必要があるもの(以降これを要件と示します)を示します。(補足: このインターフェース型を明示したCapabilityはこのインターフェースに宣言した関数しか呼び出せません。)

インターフェースには以下の3種類があります。

  • 構造体インターフェースstructに実装される
  • リソースインターフェースresourceに実装される
  • コントラクトインターフェースcontractに実装される

構造体、リソース、およびコントラクト型は、複数のインターフェースを実装する事ができます。

イベントやenum(列挙型)のインターフェースはサポートされていません。

名称型(Nominal typing)は、インターフェースを実装する複合型に適用されます。つまり、型がインターフェースを実装するのは、適合性を明示的に宣言した場合のみであり、複合型は、インターフェースのすべての要件を満たしていても、暗黙的にインターフェースに適合することはありません。

インターフェースは、それを実装する型が提供しなければならない関数およびフィールドから構成されます。その為インターフェース内とその実装は、常に少なくともpublicです。(publicでない関数をインターフェースに書く事は出来ません)

変数のフィールドには、パブリックにセット可能(settable)にさせるアノテーションを付けることもできます。

関数の要件には、関数名、パラメータ型、オプション(必須ではない)の戻り値型、オプションのpre条件およびpost条件から構成されます。

フィールドの要件は、フィールド名と型で構成されます。オプション(必須ではない)で、getterとsetter(それぞれにpre条件とpost条件が伴う)を宣言することができます。

インターフェースにpre条件とpost条件を伴う関数を呼び出すことで、具体的な実装に代わってプログラムのセキュリティを向上させることができます。実装が変更された場合でも、その一部は常に保持されることが保証するためです。(補足: セキュリティを向上させる目的は実装にも最低限引き継がれる為)

Interface Declaration

インターフェースは、structresource、またはcontractキーワードを使用して宣言し、その後にinterfaceキーワード、インターフェース名を続けます。その後に開始波括弧と終了波括弧で囲んだ実装要件を記述します。

フィールドの要件には、実装を変数フィールドとすることを要求するためにvarキーワードを使用したり、実装を定数フィールドとすることを要求するためにletキーワードを使用したりすることができます。フィールドの要件では何も指定しないことも出来、その場合は実装が変数フィールド
、定数フィールドのどちらであっても構いません。

フィールドの要件と関数の要件には、必要とされるアクセスレベルを指定しなければなりません。アクセスは少なくともパブリックでなければなりませんので、access(all)キーワードが必ずつけられます。

インターフェースは型の中で使用できます。これは「Interfaces in Types」のセクションで詳しく説明されています。現時点では、{I}シンタックスはインターフェースIを実装する値の型と解釈できます。

// Declare a resource interface for a fungible token.
// Only resources can implement this resource interface.
//
access(all)
resource interface FungibleToken {

    // Require the implementing type to provide a field for the balance
    // that is readable in all scopes (`access(all)`).
    //
    // Neither the `var` keyword, nor the `let` keyword is used,
    // so the field may be implemented as either a variable
    // or as a constant field.
    //
    access(all)
    balance: Int

    // Require the implementing type to provide an initializer that
    // given the initial balance, must initialize the balance field.
    //
    init(balance: Int) {
        pre {
            balance >= 0:
                "Balances are always non-negative"
        }
        post {
            self.balance == balance:
                "the balance must be initialized to the initial balance"
        }

        // NOTE: The declaration contains no implementation code.
    }

    // Require the implementing type to provide a function that is
    // callable in all scopes, which withdraws an amount from
    // this fungible token and returns the withdrawn amount as
    // a new fungible token.
    //
    // The given amount must be positive and the function implementation
    // must add the amount to the balance.
    //
    // The function must return a new fungible token.
    // The type `{FungibleToken}` is the type of any resource
    // that implements the resource interface `FungibleToken`.
    //
    access(all)
    fun withdraw(amount: Int): @{FungibleToken} {
        pre {
            amount > 0:
                "the amount must be positive"
            amount <= self.balance:
                "insufficient funds: the amount must be smaller or equal to the balance"
        }
        post {
            self.balance == before(self.balance) - amount:
                "the amount must be deducted from the balance"
        }

        // NOTE: The declaration contains no implementation code.
    }

    // Require the implementing type to provide a function that is
    // callable in all scopes, which deposits a fungible token
    // into this fungible token.
    //
    // No precondition is required to check the given token's balance
    // is positive, as this condition is already ensured by
    // the field requirement.
    //
    // The parameter type `{FungibleToken}` is the type of any resource
    // that implements the resource interface `FungibleToken`.
    //
    access(all)
    fun deposit(_ token: @{FungibleToken}) {
        post {
            self.balance == before(self.balance) + token.balance:
                "the amount must be added to the balance"
        }

        // NOTE: The declaration contains no implementation code.
    }
}

イニシャライザ(init関数)および関数の要件には実行可能なコードが一切含まれまない事に注目してください。

構造体とリソースのインターフェースは、スマートコントラクトの内部で直接宣言するのみであって、関数の内部では宣言できません。コントラクトインターフェースはグローバルでのみ宣言でき、コントラクトの内部では宣言できません。

Interface Implementation

型がインターフェースを準拠していることを宣言するには、複合型(構造体やリソースなど)の型宣言時に行います。複合型の種類(resourceかstruct)と名前の後にコロン(:)を置き、その後にそれが実装する1つまたは複数のインターフェース名を入力します。

これにより、スマートコントラクトのデプロイチェッカーは指定されたインターフェイスの要件を、宣言された型が実装することを強制します。

インターフェースの実装を宣言し、インターフェイスの要件の全てのフィールドの宣言と、インターフェイスの要件のすべての関数の実装すると、型がインターフェイスを実装(準拠)したことになります。(補足: リソースや構造体は宣言するとそれが一つの型ということになるらしい)

実装する型の中のフィールド宣言は、インターフェースのフィールド要件と一致していなければならず、それは名前、型、宣言の種類(定数、変数など)が要件として指定されている場合です。例えば、インターフェースが特定の名前と型を持つフィールドを要求する場合でも、そのフィールドの種類(変数か定数か)は実装に任せる場合があります。

関数の実装は、関数の要件と一致していなければならず、名前、引数のラベル、引数の型、戻り値の型はインターフェースの関数と一致していなければなりません。

// Declare a resource named `ExampleToken` that has to implement
// the `FungibleToken` interface.
//
// It has a variable field named `balance`, that can be written
// by functions of the type, but outer scopes can only read it.
//
access(all)
resource ExampleToken: FungibleToken {

    // Implement the required field `balance` for the `FungibleToken` interface.
    // The interface does not specify if the field must be variable, constant,
    // so in order for this type (`ExampleToken`) to be able to write to the field,
    // but limit outer scopes to only read from the field, it is declared variable,
    // and only has public access (non-settable).
    //
    access(all)
    var balance: Int

    // Implement the required initializer for the `FungibleToken` interface:
    // accept an initial balance and initialize the `balance` field.
    //
    // This implementation satisfies the required postcondition.
    //
    // NOTE: the postcondition declared in the interface
    // does not have to be repeated here in the implementation.
    //
    init(balance: Int) {
        self.balance = balance
    }

    // Implement the required function named `withdraw` of the interface
    // `FungibleToken`, that withdraws an amount from the token's balance.
    //
    // The function must be public.
    //
    // This implementation satisfies the required postcondition.
    //
    // NOTE: neither the precondition nor the postcondition declared
    // in the interface have to be repeated here in the implementation.
    //
    access(all)
    fun withdraw(amount: Int): @ExampleToken {
        self.balance = self.balance - amount
        return create ExampleToken(balance: amount)
    }

    // Implement the required function named `deposit` of the interface
    // `FungibleToken`, that deposits the amount from the given token
    // to this token.
    //
    // The function must be public.
    //
    // NOTE: the type of the parameter is `{FungibleToken}`,
    // i.e., any resource that implements the resource interface `FungibleToken`,
    // so any other token – however, we want to ensure that only tokens
    // of the same type can be deposited.
    //
    // This implementation satisfies the required postconditions.
    //
    // NOTE: neither the precondition nor the postcondition declared
    // in the interface have to be repeated here in the implementation.
    //
    access(all)
    fun deposit(_ token: @{FungibleToken}) {
        if let exampleToken <- token as? ExampleToken {
            self.balance = self.balance + exampleToken.balance
            destroy exampleToken
        } else {
            panic("cannot deposit token which is not an example token")
        }
    }
}

// Declare a constant which has type `ExampleToken`,
// and is initialized with such an example token.
//
let token <- create ExampleToken(balance: 100)

// Withdraw 10 units from the token.
//
// The amount satisfies the precondition of the `withdraw` function
// in the `FungibleToken` interface.
//
// Invoking a function of a resource does not destroy the resource,
// so the resource `token` is still valid after the call of `withdraw`.
//
let withdrawn <- token.withdraw(amount: 10)

// The postcondition of the `withdraw` function in the `FungibleToken`
// interface ensured the balance field of the token was updated properly.
//
// `token.balance` is `90`
// `withdrawn.balance` is `10`

// Deposit the withdrawn token into another one.
let receiver: @ExampleToken <- // ...
receiver.deposit(<-withdrawn)

// Run-time error: The precondition of function `withdraw` in interface
// `FungibleToken` fails, the program aborts: the parameter `amount`
// is larger than the field `balance` (100 > 90).
//
token.withdraw(amount: 100)

// Withdrawing tokens so that the balance is zero does not destroy the resource.
// The resource has to be destroyed explicitly.
//
token.withdraw(amount: 90)

実装の中でのフィールド変数はアクセスレベル(access level)がインターフェースの要件より制限が緩い場合があります。例えば、インターフェースではフィールドが少なくともコントラクトアクセスを要件(access(contract))にしていても、実装ではパブリックアクセス(access(all))のフィールド変数にすることができます。

access(all)
struct interface AnInterface {
    // Require the implementing type to provide a contract-readable
    // field named `a` that has type `Int`. It may be a variable
    // or a constant field.
    //
    access(contract)
    a: Int
}

access(all)
struct AnImplementation: AnInterface {
    // Declare a public variable field named `a` that has type `Int`.
    // This implementation satisfies the requirement for interface `AnInterface`:
    //
    access(all)
    var a: Int

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

Interfaces in Types

インターフェースは、次のようにして型として使用できます。{I}という型は、インターフェースIを実装する全オブジェクトの型になります。

これは、intersection typeと呼ばれます。この型はこのインターフェースの機能(メンバーおよび関数)にしかアクセスできなくなります。

// Declare an interface named `Shape`.
//
// Require implementing types to provide a field which returns the area,
// and a function which scales the shape by a given factor.
//
access(all)
struct interface Shape {

    access(all)
    fun getArea(): Int

    access(all)
    fun scale(factor: Int)
}

// Declare a structure named `Square` the implements the `Shape` interface.
//
access(all)
struct Square: Shape {
    // In addition to the required fields from the interface,
    // the type can also declare additional fields.
    //
    access(all)
    var length: Int

    // Provided the field `area`  which is required to conform
    // to the interface `Shape`.
    //
    // Since `area` was not declared as a constant, variable,
    // field in the interface, it can be declared.
    //
    access(all)
    fun getArea(): Int {
        return self.length * self.length
    }

    access(all)
    init(length: Int) {
        self.length = length
    }

    // Provided the implementation of the function `scale`
    // which is required to conform to the interface `Shape`.
    //
    access(all)
    fun scale(factor: Int) {
        self.length = self.length * factor
    }
}

// Declare a structure named `Rectangle` that also implements the `Shape` interface.
//
access(all)
struct Rectangle: Shape {

    access(all)
    var width: Int

    access(all)
    var height: Int

    // Provided the field `area  which is required to conform
    // to the interface `Shape`.
    //
    access(all)
    fun getArea(): Int {
        return self.width * self.height
    }

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

    // Provided the implementation of the function `scale`
    // which is required to conform to the interface `Shape`.
    //
    access(all)
    fun scale(factor: Int) {
        self.width = self.width * factor
        self.height = self.height * factor
    }
}

// Declare a constant that has type `Shape`, which has a value that has type `Rectangle`.
//
var shape: {Shape} = Rectangle(width: 10, height: 20)

インターフェースを実装した値は、インターフェースを型とする変数に代入することができます。

// Assign a value of type `Square` to the variable `shape` that has type `Shape`.
//
shape = Square(length: 30)

// Invalid: cannot initialize a constant that has type `Rectangle`.
// with a value that has type `Square`.
//
let rectangle: Rectangle = Square(length: 10)

インターフェースを実装した型の値からは、インターフェースで宣言されたフィールドにアクセスでき、インターフェースで宣言された関数を呼び出すことができます。

// Declare a constant which has the type `Shape`.
// and is initialized with a value that has type `Rectangle`.
//
let shape: {Shape} = Rectangle(width: 2, height: 3)

// Access the field `area` declared in the interface `Shape`.
//
shape.area  // is `6`

// Call the function `scale` declared in the interface `Shape`.
//
shape.scale(factor: 3)

shape.area  // is `54`

Interface Nesting

🚧 ステータス
現在、ネストされたインターフェースをサポートしているのはコントラクトおよびコントラクトインターフェースのみです。

インターフェースは任意にネストすることができます。別のインターフェース内にインターフェースを宣言しても、外側のインターフェースの型を実装する必要はありません。

// Declare a resource interface `OuterInterface`, which declares
// a nested structure interface named `InnerInterface`.
//
// Resources implementing `OuterInterface` do not need to provide
// an implementation of `InnerInterface`.
//
// Structures may just implement `InnerInterface`.
//
resource interface OuterInterface {

    struct interface InnerInterface {}
}

// Declare a resource named `SomeOuter` that implements the interface `OuterInterface`.
//
// The resource is not required to implement `OuterInterface.InnerInterface`.
//
resource SomeOuter: OuterInterface {}

// Declare a structure named `SomeInner` that implements `InnerInterface`,
// which is nested in interface `OuterInterface`.
//
struct SomeInner: OuterInterface.InnerInterface {}

コントラクトインターフェースは、イベントを宣言することもでき、その場合も、外部インターフェースの実装型がそのイベントを「実装」する必要はありません。イベントは、宣言しているインターフェース、条件、または関数の(デフォルト動作の)実装内で発行することができます。
例:

// Declare a contract interface
//
contract interface ContractInterface {
    // some event declaration
    //
    event SomeEvent()

    // some function that emits `SomeEvent` when called
    //
    fun eventEmittingFunction() {
        pre {
            emit SomeEvent()
        }
    }
}

// A contract implementing `ContractInterface`
// Note that no declaration of `SomeEvent` is required
//
contract ImplementingContract: ContractInterface {
    // implementation of `eventEmittingFunction`;
    // this will emit `SomeEvent` when called
    //
    fun eventEmittingFunction() {
        // ...
    }
}

Interface Default Functions

インターフェースはデフォルトの関数を提供できる:インターフェースを実装している具体的な型が、インターフェースが要求する関数の実装を提供しない場合、インターフェースのデフォルトの関数が実装で使用される。

// Declare a struct interface `Container`,
// which declares a default function `getCount`.
//
struct interface Container {

    let items: [AnyStruct]

    fun getCount(): Int {
        return self.items.length
    }
}

// Declare a concrete struct named `Numbers` that implements the interface `Container`.
//
// The struct does not implement the function `getCount` of the interface `Container`,
// so the default function for `getCount` is used.
//
struct Numbers: Container {
    let items: [AnyStruct]

    init() {
        self.items = []
    }
}

let numbers = Numbers()
numbers.getCount()  // is 0

インターフェースはデフォルトの初期化子(init関数)を提供することはできません。

1つの適合性だけがデフォルトの関数を提供できます。

Interface inheritance

インターフェースは、同じ種類の他のインターフェースから継承(準拠)することができます。例えば、リソースインターフェースは別のリソースインターフェースから継承することができますが、構造体インターフェースから継承することはできません。インターフェースが別のインターフェースから継承する場合、親インターフェースのすべてのフィールド、関数、および型は、継承したインターフェースで暗黙的に利用可能になります。

access(all)
resource interface Receiver {
    access(all)
    fun deposit(_ something: @AnyResource)
}

// `Vault` interface inherits from `Receiver` interface.
access(all)
resource interface Vault: Receiver {
    access(all)
    fun withdraw(_ amount: Int): @Vault
}

上記の例では、VaultReceiverを継承します。Vaultインターフェースを実装する場合は、Receiverインターフェースも実装する必要があります。

access(all)
resource MyVault: Vault {
    
    // Must implement all the methods coming from both `Vault` and `Receiver` interfaces.
    access(all)
    fun deposit(_ something: @AnyResource) {}

    access(all)
    fun withdraw(_ amount: Int): @Vault {}
}

Duplicate interface members

あるインターフェースが別のインターフェースを実装する場合、2つのインターフェースに同じ名前のメンバーが存在することが可能です。以下のセクションでは、さまざまなシナリオにおけるこれらの曖昧さの解決方法について説明します。

Fields
同一の名前を持つ2つのフィールドが同一の型である場合、それは有効です。

access(all)
resource interface Receiver {
    access(all)
    var id: UInt64
}

access(all)
resource interface Vault: Receiver {
    // `id` field has the same type as the `Receiver.id`. Hence this is valid.
    access(all)
    var id: UInt64
}

そうでなければ、インターフェース準拠が無効となります。

access(all)
resource interface Receiver {
    access(all)
    var id: Int
}

access(all)
resource interface Vault: Receiver {
    // `id` field has a different type than the `Receiver.id`. Hence this is invalid.
    access(all)
    var id: UInt64
}

Function
同じ名前の関数で(引数など)シグネチャも同じ場合は、有効です。

access(all)
resource interface Receiver {
    access(all)
    fun deposit(_ something: @AnyResource)
}

access(all)
resource interface Vault: Receiver {
    // `deposit` function has the same signature as the `Receiver.deposit`.
    // Also none of them have any default implementations.
    // Hence this is valid.
    access(all)
    fun deposit(_ something: @AnyResource)
}

2つの関数の(引数など)シグネチャが異なっている場合、インターフェース準拠が無効となります。

access(all)
resource interface Receiver {
    access(all)
    fun deposit(_ something: @AnyResource)
}

access(all)
resource interface Vault: Receiver {
    // Error: `deposit` function has a different signature compared to the `Receiver.deposit`.
    // So these two cannot co-exist.
    access(all)
    fun deposit()
}
Functions with conditions

同一の名前とシグネチャを持つ2つの関数に事前/事後条件がある場合、それは依然として有効です。ただし、事前/事後条件はリニア化(Linearizing Conditionsのセクションを参照)され、条件の実行順序が決定されます。与えられる事前/事後条件はviewのみであり、実行順序が条件に影響を与えることはありません。

access(all)
resource interface Receiver {
    access(all)
    fun deposit(_ something: @AnyResource) {
        pre{ self.balance > 100 }
    }
}

access(all)
resource interface Vault: Receiver {
    // `deposit` function has the same signature as the `Receiver.deposit`.
    // Having pre/post condition is valid.
    // Both conditions would be executed, in a pre-determined order.
    access(all)
    fun deposit(_ something: @AnyResource) {
        pre{ self.balance > 50 }
    }
}

Default functions
インターフェースは、継承された関数にデフォルトの実装を提供することができます。

access(all)
resource interface Receiver {
    access(all)
    fun log(_ message: String)
}

access(all)
resource interface Vault: Receiver {
    // Valid: Provides the implementation for `Receiver.log` method.
    access(all)
    fun log(_ message: String) {
        log(message.append("from Vault"))
    }
}

しかし、インターフェースは継承されたデフォルトの関数実装を上書きすることはできません。

access(all)
resource interface Receiver {
    access(all)
    fun log(_ message: String) {
        log(message.append("from Receiver"))
    }
}

access(all)
resource interface Vault: Receiver {
    // Invalid: Cannot override the `Receiver.log` method.
    access(all)
    fun log(_ message: String) {
        log(message.append("from Vault"))
    }
}

また、インターフェースに対して2つ以上のデフォルト実装を継承することも無効です。

access(all)
resource interface Receiver {
    access(all)
    fun log(_ message: String) {
        log(message.append("from Receiver"))
    }
}

access(all)
resource interface Provider {
    access(all)
    fun log(_ message: String) {
        log(message.append("from Provider"))
    }
}

// Invalid: Two default functions from two interfaces.
access(all)
resource interface Vault: Receiver, Provider {}

とはいえ、同じデフォルト機能が異なる継承パス経由で利用できる状況もあり得ます。

access(all)
resource interface Logger {
    access(all)
    fun log(_ message: String) {
        log(message.append("from Logger"))
    }
}

access(all)
resource interface Receiver: Logger {}

access(all)
resource interface Provider: Logger {}

// Valid: `Logger.log()` default function is visible to the `Vault` interface
// via both `Receiver` and `Provider`.
access(all)
resource interface Vault: Receiver, Provider {}

上記の例では、Logger.log()のデフォルト関数は、VaultインターフェースからReceiverおよびProviderの両方を通じて参照できます。2つの異なるインターフェースから利用可能であっても、両者は同じデフォルト実装を参照しています。したがって、上記のコードは有効です。
Conditions with Default functions
より複雑な状況は、デフォルト機能が1つの継承パスで利用可能であり、事前/事後条件が別の継承パスで利用可能な場合です。

access(all)
resource interface Receiver {
    access(all)
    fun log(_ message: String) {
        log(message.append("from Receiver"))
    }
}

access(all)
resource interface Provider {
    access(all)
    fun log(_ message: String) {
        pre{ message != "" }
    }
}

// Valid: Both the default function and the condition would be available.
access(all)
resource interface Vault: Receiver, Provider {}

そのような状況では、デフォルト関数の継承および条件継承に適用されるすべてのルールが適用されます。したがって、Receiverインターフェースからのデフォルト関数と、Providerインターフェースからの条件は、継承されたインターフェースで利用可能になります。
Types and event definitions
型およびイベントの定義も、デフォルトの関数と同様に動作します。継承されたインターフェースは、型定義およびイベント定義を上書き(オーバーライド)することができます。

access(all)
contract interface Token {
    access(all)
    struct Foo {}
}

access(all)
contract interface NonFungibleToken: Token {
    access(all)
    struct Foo {}
}

access(all)
contract MyToken: NonFungibleToken {
    access(all)
    fun test() {
        let foo: Foo  // This will refer to the `NonFungibleToken.Foo`
    }
}

ユーザーがスーパーインターフェースTokenからFoo構造体にアクセスする必要がある場合、完全修飾名を使用してアクセスすることができます。例:let foo: Token.Foo

ただし、インターフェースに対して、同一の名前を持つ2つ以上の継承された 型やイベント定義を持つことは許可されていません。

access(all)
contract interface Token {
    access(all)
    struct Foo {}
}

access(all)
contract interface Collectible {
    access(all)
    struct Foo {}
}

// Invalid: Two type definitions with the same name from two interfaces.
access(all)
contract NonFungibleToken: Token, Collectible {
}

デフォルト関数と同様に、異なる継承パスを経由して同じ型/イベント定義が利用できる状況が考えられます。

access(all)
contract interface Logger {
    access(all)
    struct Foo {}
}

access(all)
contract interface Token: Logger {}

access(all)
contract interface Collectible: Logger {}

// Valid: `Logger.Foo` struct is visible to the `NonFungibleToken` interface via both `Token` and `Collectible`.
access(all)
contract interface NonFungibleToken: Token, Collectible {}

上記の例では、Logger.Foo型の定義は、NonFungibleTokenインターフェースに対して、TokenおよびCollectibleの両方から参照可能です。2つの異なるインターフェースから参照可能であっても、両者は同じ型の定義を参照しています。したがって、上記のコードは有効です。

しかし、チェーンの途中にあるインターフェースの少なくとも1つが型の定義Fooもオーバーライドしている場合、複数の実装が存在することになり、あいまい性につながるため、コードは無効になります。

access(all)
contract interface Logger {
    access(all)
    struct Foo {}
}

access(all)
contract interface Token: Logger {
    access(all)
    struct Foo {}
}

access(all)
contract interface Collectible: Logger {}

// Invalid: The default implementation of the `Foo` struct by the `Logger`
// interface is visible to the `NonFungibleToken` via the `Collectible` interface.
// Another implementation of `Foo` struct is visible to the `NonFungibleToken` via the `Token` interface.
// This creates ambiguity.
access(all)
resource interface NonFungibleToken: Token, Provider {}

Linearizing Conditions

functions with conditionsのセクションで述べたように、事前条件と事後条件が実行される順序を決定するために、関数の条件をリニア化する必要があります。これは、インターフェースをリニア化することで行われ、したがって条件も、深さ優先で重複なしの事前順序でリニア化されます。

例えば、以下のようなインターフェース継承階層を考えてみましょう。

       A
      / \
     B   C
    / \ /
   D   E
where an edge from A (top) to B (bottom) means A inherits B.

これは、以下に似たCadenceの実装に変換されます。

struct interface A: B, C {
    access(all)
    fun test() {
        pre { print("A") }
    }
}

struct interface B: D, E {
    access(all)
    fun test() {
        pre { print("B") }
    }
}

struct interface C: E {
    access(all)
    fun test() {
        pre { print("C") }
    }
}

struct interface D {
    access(all)
    fun test() {
        pre { print("D") }
    }
}

struct interface E {
    access(all)
    fun test() {
        pre { print("E") }
    }
}

Aのインターフェースを実装する具体的なタイプは、AからEまでのリニア化されたすべてのインターフェースを実装することと同等です。

struct Foo: A {
    access(all)
    fun test() {
        pre { print("Foo") }
    }
}

線形化されたインターフェースの順序は次のようになります:[A, B, D, E, C]。

すなわち、次のようになるのと同じです。

A, B, D, C, E {
    access(all)
    fun test() {
        pre { print("Foo") }
    }
}

したがって、Footestメソッドを呼び出すと、まず[A, B, D, E, C]の事前条件がその順序で呼び出され、最終的に具体的な実装であるFooの事前条件が実行されます。

let foo = Foo()
foo.test()

上記の出力結果:

A
B
D
E
C
Foo

同様に、事後条件についても同じインターフェースのリニア化が使用され、事後条件は逆の順序で実行されます。例えば、上記の例のpre条件を、まったく同じ内容のpost条件に置き換えると、以下のような出力が得られます。

Foo
C
E
D
B
A

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

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

Previous << Capabilities

Next >> Enumerations

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?