These FLIPs propose two potential new language features designed to solve the extensibility problem in Cadence, namely: how can a developer add new functionality to an existing type that the original writer of the type did not account for? Prior discussion on this topic has occurred on https://github.com/onflow/flow/pull/1101, Extensibility and https://github.com/onflow/cadence/issues/357.
Now that there are two competing proposals here, I wanted to write up a short analysis of some uses cases we expect extensions/attachments to handle, and to compare and contrast the two proposals based on how well they handle these use cases.
Metadata: Given some NFT that conforms to the Metadata proposal, weād like to be able to show not only the metadata associated with this NFT, but also any additional metadata associated with any extensions/attachments on that NFT. In general, this use case is any situation in which we would like to make the behavior of a resource dependent on the attachments or extensions present upon it.
Extensions:
The original extensions proposal deliberately did not allow the base type to reference its extensions at any point; so it is not possible to have a function on the base type that queries its extensions, nor to iterate over those extensions dynamically. Instead, if we have some resource R that conforms to the Metadata spec, an extension for R called E could in theory choose to implement an getExtendedNFTView (or similarly named) function that calls R's getNFTView function and then adds additional data onto it. Then any context in which an R with E value exists could call getExtendedNFTView to get an NFTView value that contains metadata for both the resource and its extension.
This is limited, however, in that a calling context must statically know that an extension is present on a resource in order to call its getExtendedNFTView function; there is no way to dynamically iterate over or inspect the extensions on a resource. In general, this means that while an extension can add new functionality to a resource, there is no way to alter the original behavior of the resource arbitrarily depending on which extensions it has on it.
Attachments:
This proposal was designed with precisely this use case in mind. If you have some NFT that conforms to the Metadata proposal, as long as the attachments you add to that NFT have some consistent, known set of fields or methods that you can use to get their Metadata. The implementation of getNFTView in R can iterate over all of R's attachments, and check whether those attachments have a getNFTView method themselves. If they do, and that method has the appropriate type, R can call this method on each of its attachments and combine all of the views together into one view that captures the metadata of R and all of the attachments with metadata.
The Strategy Pattern: Described in detail here (Strategy pattern - Wikipedia), the strategy pattern is a design pattern in which the specific details of an algorithmās implementation are abstracted away from the object. Specifically, we can imagine an Avatar NFT which is intended to function as a player character in a variety of Dapps, but only features very basic functionality, and is intended to be easily extendable via attachments or extensions in order to participate in these Dapps. One such Dapp might be a fighting game, in which usersā customized Avatars can fight each other using a variety of weapons or strategies.
Extensions:
Using extensions, this use case is quite simple. The creator of the fighting game Dapp can specify a Fighter interface that all participating Avatars must implement, containing in particular an attack and a defend method that all participating Avatars must know how to do in order to be valid characters in the game. While these methods would not be present on the base Avatar type (and thus Avatar itself would not implement Fighter), any user could supply an extension to their Avatar that would cause them to implement Fighter and thus be valid characters. For example, a SwordFighter extension might extend Avatar with a sword and shield, while a BowFighter might instead supply Avatars with a bow and arrows.
This would allow the creator of the fighting game to write their game logic purely in terms of the Fighter interface, agnostic to the specific details of how the participating fighters may be implemented, and allows players to supply their own customized fighters using a potentially unlimited number of different strategies.
Attachments:
Because the Avatar resource itself is intended to be general and thus cannot know any details about what the fighting game (or any other Dapp) wants to use it for, and because calling a method on an attachment must specify exactly which attachment the method is coming from (no static dispatch), the burden of handling the fighting strategy for each participating Avatar falls on the Dapp developer. While users can attach an attack and defend method to their Avatar, the Dapp developer must manually search through each participating Avatar for these methods in order to invoke them.
Additionally, because attachments do not alter the type of their base resource, there is no way for the developer of the fighting game Dapp to enforce statically that participating Avatars have the necessary functionality to participate in the game; they will need to write defensive code that will abort game transactions early if one of the users supplies an Avatar that doesnāt have an attachment with attack, for example.
A Vault with an exchange rate: This case involves a Vault object that wants to add additional functionality beyond what a basic Vault can support; e.g. supporting depositing in one currency but withdrawing in another, with a scalar applied to the amount to represent the exchange rate between the two currencies. In neither of these two cases would a user just be able to call the standard withdraw or deposit functions on the Vault and expect the added functionality to occur, as neither proposal has support for dynamic dispatch.
Extensions: The creator of the Vault can add a extension to their Vault that causes it to implement a ConvertingVault interface, and then expose a restricted type to anybody who wishes to use that Vault only giving them access to the convertingDeposit and convertingWithdraw functions defined on the extension, which would perform the conversion and then call the basic deposit and withdraw functions on the Vault itself.
Attachments: Users can add an attachment to their Vault that implements the converting functionality similarly to the extensions example, but there is no way for them to enforce that users of the Vault only call the attachmentās withdraw and deposit instead of using the Vault typeās versions of those methods, as the type of the attachment cannot be expressed.
Adding a Hat to a Kitty: Given an existing Kitty NFT, a user wishes to add a Hat to it. This is the most basic case, and is handled well by both proposals; the user can simply create the extension/attachment and add them to the Kitty.
Writing a Dapp explicitly for extended resources (e.g. Kitties with Hats)
Extensions:
This is quite simple, the developer of the Dapp can specify that any inputs to the Dapp contractās functions must be a Kitty with Hat type, and any attempts to provide a regular Kitty without a Hat to the Dapp will fail statically.
Attachments:
The Dapp developer must write defensive checks at the beginning of their functions that will abort execution or return nil if the input to the function does not have the appropriate Hat extension.
Please let me know if there are other use cases that weād like to consider when evaluating these two proposals.
Are use cases which decrease rather than increase the value of the original in scope? One example of a decrease in value might be accepting a valuable NFT as collateral for a loan, and instead of putting the NFT in escrow, attaching a virtual ālienā to it and allowing the NFT to be used (you can breed your CryptoKitty) as usual. In that case, I would expect that you wouldnāt want the owner of the NFT to be able to remove the extension/attachment on their own.
Based on the discussion about the value of extensions/attachments that occurred during the last meeting on this topic, it seems like extensions/attachments themselves are not intended to have value (or negative value, in this case) in and of themselves; in particular they should not be resources. If you wish to āattachā a resource-kinded value to another resource, like the lien to the NFT in your example, the pattern weād probably use would be to attach a lien-manager value to the NFT that references the lien, rather than attaching the lien itself directly to the NFT. This way even if the owner of the resource removes the attachment/extension pointing to the lien from the NFT, the lien still exists, and the owner of the lien (presumably the person loaning the money) still knows that the lien exists on that NFT.
During the Cadence Language Design Meeting today, @dete had a valuable insight about the Strategy pattern use case; namely, that the attachment proposal is actually a better fit for the patterns that actually exist on the blockchain. In order to guarantee conditions like fairness, the creator of the fighting game (or the organizer of the tournament) must be able to restrict which extensions/attachments are actually allowed to be used in any instance of gameplay; otherwise someone could simply write a strategy that circumvents the rules or is otherwise unfair.
The extensions pattern would require the Fighter interface creator to limit who can create values of this interface type, in order to prevent people from creating Fighters with unfair strategies. However, once a Fighter has been created, it is always a valid game participant. On the other hand, the attachments pattern would put the onus on the tournament organizer or game developer to validate that the attached strategy is a valid one. This latter pattern is actually more in line with the existing blockchain paradigm where instantiation of values is not restricted or limited, but Dapp implementers choose which values to accept and which to ignore.
Other feedback from this meeting is that the extensions proposal is more powerful, but consequently more complex, while the attachments proposal is simpler but also more directly addresses the common uses cases.
This adds some updates to the FLIP for attachments.
In response to the two questions on that PR:
I think there is a benefit to allowing extensions to implement interfaces; namely that it makes iteration easier. If it were possible for an extension to implement an interface, when iterating over the extensions on a resource we could take this implementation into account and be able to access methods/values on that extension without needing to use reflection.
The attachments proposal already gives up on so much of the static guarantees the extensions proposal would have provided that there seems to be little point in even providing a static type for a resource with attachments.
I am curious to hear other perspectives on these points.
While i like the power that extensions would give, I also see it as very complex and I am not sure it is worth the added mental strain it will put on developers. Just look at scala vs kotlin in the JVM ecosystem. Scala has lots of merrits but its type system is very complex while kotlin is easier and puts less strain on the developer. Scala is more powerfull but at what cost?
(For those not familiar with JVM Kotlin is more widely adopted then Scala at this point, namely on android and atelast in Norway in most gov/big-enterprise IT that are on JVM)
One of the main tenants of Cadence that @dete has a very clear vision on is that it should be easy to read, and some of the examples in the extensions document is not that easy to understand unless you know some type theory.
They can freely implement any interface they want.
They can be used on interfaces ( attachment designed for FungibleToken }
These two are pretty important points, and make me feel like attachments should be able to implement interfaces, and also it should be possible to declare an attachment for an interface and then attach it to any concrete type that implements that interface.
The PR above has been updated to include information about attachments implementing interfaces and being declared for interfaces, as well as a change to the iteration function that allows users to leverage this information.
Awesome work on both the extensions and attachments proposal @sainati
I quite like the attachments proposal, as its slightly simpler, yet still covers most use-cases, and would prefer it to the extensions proposal.
There are still a few things to iron out in the proposal, but Iām confident we can get it to a state where it can get accepted and implemented.
Hereās my concrete feedback for the attachments proposal:
Maybe state explicitly that attachments are not first-class values
Access modifier:
Side note: All other type declarations are currently required to be pub. This was not necessarily the end-goal, but rather it was a temporary restriction until we would determine how private declarations could be used. For example, can they be returned from a public function?
There are really two separate access concerns: Access to/visibility of
The type
The constructor
So maybe simplify the proposal, and require pub / disallow other access modifiers for attachments, too, at least as a start ā we can extend the feature later
this access modifier only applies to the āattachingā of the attachment; an attachment can be removed from a resource by the owner of that resource in any context.
If the user does not have access to the type, for example because it is private, how will they be able to remove the attachment from their value
Some of the phrasing makes it seem like the proposal is resource-specific, maybe generalize, for example:
an attachment can be removed from a resource by the owner of that resource in any context
Maybe replace with:
an attachment can be removed from a composite value by the owner of that value in any context"
āOnce an attachment has been added to a resource [ā¦] which is implicitly present on all resourcesā
Maybe replace with:
Once an attachment has been added to a composite value [ā¦] which is implicitly present on all composite values
Attaching
attach A(y: 3) to S(x: "foo")
Initialization order:
Aās initializer may refer to super ā but it is not available yet?
The evaluation order is usually left-to-right, but for attach seems to be right-to-left
Maybe swap the order? For example, instead of having a prefix operator, make attach a postfix operator (or a function, see below)
It looks like only one attachment per type is supported. What happens if a second attachment instance is added for an attachment type?
Removing
The t value here is a type name, rather than a value"
āt valueā is confusing
Maybe replace with:
Here, t refers to an attachment type name
Before the expression executes
Expression or statement. For example:
Attachments can be removed with a new statement"
If a statement, maybe replace with:
āBefore the statement executesā
Related: destroy is an expression
Ideally we want to keep it consistent
Maybe change destroy to a statement?
Access
Attaching and removing are keywords, but access is a predeclared function
We may want to keep it consistent
Could make access have dedicated syntax, e.g. r[A]
Could make attach and remove functions, e.g r.attach(A()), r.remove<A>()
<T: Any>
: Any is implicit, can be removed
forEachAttachment(f: fun ((attachment: &{Description}): Void {
Remove incorrect argument label (parameter has no argument label)
Thoughts on the dedicated syntax for access: I like the r[A] syntax for access, but I think this would require a breaking change to Cadence to make possible. Cadence currently has separate namespaces for types and values, but if we allowed people to use indexing syntax with types, we might run into ambiguities if a type and a value share a name. What about something like r<A>? The angle brackets are already associated with types, since we use them with generic parameters.
Regarding the order of evaluation for attach, I like the prefix operator because it approximates natural language: attach <ATTACHMENT> to <VALUE> reads more naturally than <VALUE> attach <ATTACHMENT> or similar. However, I agree that as described it needs to be right-to-left evaluated; this was not an issue with the old syntax of extend <VALUE> with <ATTACHMENT>.
After discussion with @bastian we determined it would not be a breaking change to allow attachment access with r[A] syntax, so https://github.com/onflow/flips/pull/42 updates the proposal to use this new method.
Have we thought about using an instance method for removing attachments? e.g: v.removeAttachment(type) going similar to the rest of the methods like forEachAttachment etcā¦ It would reduce the complexity of adding (e.g: for language impl team) and learning (for users) a new syntax for the language. It would also mean less keywords/preserved words in the language.
I can understand having a new syntax allows additional type checking, like checking whether the removing attachment T2 is actually attachable to T1 base type. An alternative would be to make removeAttachment a compiler-known function: That is, the type checker knows this as a special function and can have special type checking for those (that would be an implementation detail)
Some realistic example is lacking.
I think if we add a realistic example with transaction snippets/attachment code etc., it can show us better how it will work. ( good example can be NFT signatures for example )
I should be able to attach an attachment on a resource reference. (also I should be able to limit access to addAttachment on resource reference somehow )
Not having the same type of attachment on the resource many times.
I think the direction here is, the attachment is kind of a manager, ( SignatureAttachment ā Attachment that manages signatures ) I think this is very similar to NFT collection, and putting NFT to collection design pattern, that worries me a bit little. If those resources ( signatures ) will be tradeable, then user should know how to set up an attachment to the object, rather than attaching the signature directly.
Attachments are not resources. ( I am not sure complexity of new composite type is necessary, i think creating attachment from a built-in interface would be really more powerful )
Attach / Detach syntax
I agree with @supun, instead of new keyword , I think using instance methods are better.
Also allows attaching/detaching on resource references.
I feel pretty strongly that dedicated syntax is the right approach here. While you are correct that this increases the implementation complexity, as well as requires more effort from users to learn, crucially IMO both of these costs are one-time; we only need to implement the feature once, and a new user only needs to learn the new syntax a single time. In exchange for these two one-time costs, the syntax will make the behavior easier to read and understand, since it is similar to natural language, and so will quickly pay for the upfront complexity by simplifying actually using the language. You can see this tradeoff in many places in other languages, but the classic example is async/await.
Realistic Example
I absolutely agree we should add a realistic example to the FLIP. I am by no means an expert on writing smart contracts though; @bluesign would you be willing to help me out by writing such an example and we can add it to the FLIP?
Attaching to resource references
As the language is right now, I donāt think it makes sense to allow this; we want the owner of the resource to have control over who can and cannot add or remove attachments, so allowing anybody with a reference to that resource to be able to modify its attachments seems unwise. If we had a feature like you suggested to limit access to attach and remove, Iād agree then we could extend those operations to work on references. This IMO is out of scope of the FLIP though. Letās get the core of the feature out and then we can work on a proposal to add new functionality to references/attachments to make this work.
would you be willing to help me out by writing such an example and we can add it to the FLIP?
Sure I will prepare one in the weekend.
Attaching to resource references
I have this problem here: creation and attachment is single-op, so this means either I will allow everyone to be able to use this attachment, or they need to give me their resource temporarily to attach something and give it back. Though I may be little confused about attachments/resources, need to think this a bit more for sure.
this means either I will allow everyone to be able to use this attachment, or they need to give me their resource temporarily to attach something and give it back
Currently type definitions in Cadence always have pub access; we disallow priv and access(contract) type declarations because itās unclear as of yet how to handle these. E.g., what is the behavior when a private type is returned from a public function? Should this be allowed?
In any case, what this means is that there shouldnāt ever be a case where a resource owner needs to give their resource away to add an attachment; they can just add the attachment themselves because it will always be pub. If/when we later add support for priv types maybe then it makes sense to consider also adding support for attaching to references?
This independence was a part of the previous extensions proposal, but we scrapped it when moving to attachments because it dramatically complicates the design and the user experience. In particular, if an attachment can be created independently of its base type, that means we cannot guarantee that itās super will have a value at any point when its functions are executed. Similarly, if I can remove an attachment and re-add it to another resource, this has the potential to break many of the invariants an implementor might assume about the relationship between an attachment and its base, and requires the implementor of the attachment to handle all the cases where the base either does not exist or is changed in between calls. By tightly coupling the attachment and its base we simplify these cases a lot.
This kind of limited instantiation already exists in Cadence by the way; events can only be created inside of an emit statement; you cannot create them independently and pass them around.