We discussed briefly in the Language Design Meeting the benefits/drawbacks of having dedicated syntax for attachment addition, removal and access. I’d like to summarize the reasons why I prefer the dedicated syntax:
-
It makes clear that attachments are not a first class-value. We want to only allow attachments to be constructed inside of the same expression that attaches them, which is much harder to enforce and more confusing to users if that expression is just a function call. With the
attach
syntax, we can simply enforce inattach e1 to e2
thate1
is always a function call to an attachment constructor. However, if we had anattach
method on resources/structs, we would need to enforce ine1.attach(e2)
thate2
is always an attachment constructor call. It is less immediately obvious, I would argue, in the latter case whye2
cannot be an arbitrary expression, since this looks to a user just like a regular function call.To make matters worse, functions defined on structs/resources can be stored in variables:
let x = s.foo
, for example, whenfoo
is a function ons
. It becomes dramatically more complex to enforce thatattach
's argument must be an attachment constructor call whenattach
can be aliased like this:let x = r.attach
, followed by anx(e)
call. We would need to require thate
's argument be an attachment constructor call, but to raise an error here is much more opaque to the user than if this were simply a dedicated syntax form. -
Removal and access of attachments on resource/structs is done by type rather than value:
remove A from v
andv[A]
. These semantics would be fairly unusual to implement with functions, as the argument is a type rather than a value. It might be possible to support this with a type argument likev.remove<A>()
orv.getAttachment<A>()
, but this IMO is confusing because it conflates the behavior of type arguments and proper arguments. Compare to a function likeaccount.borrow
, which takes both a path argument as well a type argument. The path argument determines the actual runtime value that is returned, whereas the type argument simply restricts the type with which the returned value has statically. Accessing and removing attachments however has its runtime behavior depend on the type provided;v[A]
andv[I]
do different things at runtime, for example, even ifA
conforms to interfaceI
. This is fundamentally different to what a type parameter is intended to do, so IMO it does not make sense to provide the attachment type as a function type argument this way.Relatedly, accessing and removing an attachment requires a nominal type expressing an attachment; which we are able to enforce in the
remove A from v
and thev[A]
expressions. These simply require a type name rather than a type itself. However, if we were to use a type parameter style as discussed above, we would need to arbitrarily restrict these type parameters to only be attachment names, rather than arbitrary types as are normally valid in type arguments. This would be unusual behavior for imo little to no benefit.