-
Notifications
You must be signed in to change notification settings - Fork 140
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improve interface default functions #2471
Comments
This is restrictive I agree, but also isn't it preventitive ? Chances are my contract ( in this case C ) will break is obviously higher in this case vs B not having hello method. |
Yeah, I think it comes down to what Cadence developers would want between the two options. This definitely going to need a FLIP, I only created this issue for now to make it easier to track. |
Is this related to discussion we had in the PR that added interface default functions? #1076 (comment) |
Yeah, it is the same. The concern now is, with interface inheritance, that particular case can become a useful pattern, where interfaces in the chain might define just conditions (which is part of the restrictions that you would get by conforming to that interface). But with the current restrictions, it makes it impossible to add such conditions if some other interface provides a default implementation already or vise-versa. pub resource interface Provider {
pub fun withdraw(_ amount: Int): @NFT
}
pub resource interface Vault: Provider {
pub fun withdraw(_ amount: Int): @NFT {
// some default implementation
}
}
pub resource interface ThrottledVault: Provider {
pub fun withdraw(_ amount: Int): @NFT {
pre { amount < 50 }
}
}
pub resource MyVault: Vault, ThrottledVault { // Will report an error
}
^ might not be the best example, but just want to show the idea. Also, with stable-cadence, since conditions can now be view-only, I think some of the original concerns had at that time are no longer applicable? e.g: order of conditions isn't going to be a problem |
TLDR:
Current behaviour of conflicting default functionsI re-familiarized myself with the current behaviour for conflicting default functions and the reasoning behind it (original discussion: #1076 (comment)): If conformance to multiple interfaces results in multiple default functions for a particular interface function declaration, then this conflict results in an error. I think we all agreed so far that this behaviour is better than resolving such a conflict automatically, e.g. by picking a particular default function based on order of the interfaces in the conformance list. Let's assume there is currently no conflict, e.g. struct interface I1 {
fun test(): Int {
return 1
}
}
struct interface I2 {
fun test(): Int
}
struct S: I1, I2 {} If Therefore, Deniz' point is that already the original definition/case before the update should be forbidden. I still think this reasoning makes sense. Default functions and conditionsAn interface function may be declared in three ways:
Let's consider the example from the previous section again: struct interface I1 {
fun test(): Int {
return 1
}
}
struct interface I2 {
fun test(): Int {
pre { true }
}
}
struct S: I1, I2 {} This seems OK to me: As a result, I would recommend
|
Thanks, Bastian for the great writeup! I find it hard to reason why (1 + 3) is not OK, whereas (2 + 3) is OK.
Wouldn't any local change to I believe any combination other than (3+3) should be OK. |
Some local changes, like e.g. adding an additional function requirement (1), do break implementations of the interface, but already do so on their own, independent of the implementation conforming to other interfaces. For example, assume there is an empty interface struct interface I {
fun test(): Int
// ^^^^^^^^^^^^^^^ added in update
}
struct S: I {}
// ^ only one interface
// ^ implementation is broken However, the local change of adding a function with a default implementation (3), or turning a function without without a body (1) into a default function, does not break implementations of the interface – that what the original use-case, adding additional functions to interface in a non-breaking way for implementations. For example, assume there is an empty interface struct interface I {
fun test(): Int {
return 0
}
// ^^^^^^^^^^^^^^^ added or changed from function without body in update
}
struct S: I {}
// ^ only one interface
// ^ implementation is NOT broken The "(2 + 3) behaviour" basically just ensures that such a local change keeps on being a non-breaking change for implementations of the interface in the case where the implementing type conforms to multiple interfaces. To be honest, it is just a "nice to have" when this particular situation is encountered, is not critical, and could be relaxed. |
Opened FLIP onflow/flips#83 for relaxing the current restriction, and to allow pre/post conditions to coexist with default implementations coming from different interfaces. |
Added in #2697 |
I'd like to re-open this discussion about allowing an empty function declaration in one interface and a default implementation for the function in an inheriting interface. In the new fungible token and non-fungible token standards, there are functions that we want to declare in the access(all) resource interface Receiver {
/// getSupportedVaultTypes optionally returns a list of vault types that this receiver accepts
access(all) view fun getSupportedVaultTypes(): {Type: Bool}
}
access(all) resource interface Vault: Receiver, Balance, Transferor, Provider, ViewResolver.Resolver {
/// getSupportedVaultTypes optionally returns a list of vault types that this receiver accepts
access(all) view fun getSupportedVaultTypes(): {Type: Bool} {
// Below check is implemented to make sure that run-time type would
// only get returned when the parent resource conforms with `FungibleToken.Vault`.
if self.getType().isSubtype(of: Type<@{FungibleToken.Vault}>()) {
return {self.getType(): true}
} else {
// Return an empty dictionary as the default value for resource who don't
// implement `FungibleToken.Vault`, such as `FungibleTokenSwitchboard`, `TokenForwarder` etc.
return {}
}
} This allows us to have the benefits of defining custom resources that implement the EDIT: This is also a blocker for finishing the updated token standards for stable cadence |
Thanks @joshuahannan for bringing this up. I opened a FLIP onflow/flips#134 proposing the suggested improvement. |
Added in #2725 |
Issue to be solved
Originated from discussion: #2112 (comment)
Currently, it is invalid for one interface to provide a default implementation, and another to declare the function definition only.
This is too restrictive, and also avoids use-cases where certain interfaces only want to define pre/post conditions, while some other interface provides the default implementation.
Suggested Solution
The above example should not give errors.
Furthermore, interface
B
should be able to define a pre/post condition. e.g:The text was updated successfully, but these errors were encountered: