WebAssembly / component-model

Repository for design and specification of the Component Model
Other
947 stars 79 forks source link

Resource properties #235

Open badeend opened 1 year ago

badeend commented 1 year ago

Most languages have either:

If properties are not encoded natively in WIT, we'll probably end up with everybody choosing their own convention (based on their own language). Which is bound to end up looking foreign to the other languages. Or we can prescribe a convention for tooling to convert from&to, but at that point we might as well just encode it into Wit itself.

My suggestion:

interface abc {
    resource blob {
        content-type1: string { get } // Read-only property
        content-type2: string { set } // Write-only property
        content-type3: string { get, set } // Read/write property

        content-type4: string // TODO: Without annotation defaults to { get } ? Or { get, set } ?

        content-type5: string { // Customized signatures
            get -> result<string, my-get-error>,
            set -> result<_, my-set-error>,
        }
        content-type6: string { // Infallible getter, fallible setter
            get, // If no custom signature is provided, the getter/setter is assumed to be infallible.
            set -> result<_, my-set-error>,
        }

        /// General doc-comment applicable to both the getter & setter.
        content-type7: string {
            /// Documentation specific to the getter.
            get,

            /// Documentation specific to the setter.
            set,
        }
    }

    // Is equivalent to:

    resource blob
    %[get]blob.content-type1: func(self: borrow<blob>) -> string
    %[set]blob.content-type2: func(self: borrow<blob>, value: string)
    %[get]blob.content-type3: func(self: borrow<blob>) -> string
    %[set]blob.content-type3: func(self: borrow<blob>, value: string)

    // %[???]blob.content-type4

    %[get]blob.content-type5: func(self: borrow<blob>) -> result<string, my-get-error>
    %[set]blob.content-type5: func(self: borrow<blob>, value: string) -> result<_, my-set-error>
    %[get]blob.content-type6: func(self: borrow<blob>) -> string
    %[set]blob.content-type6: func(self: borrow<blob>, value: string) -> result<_, my-set-error>

}

Or more Javascript-esque:

interface abc {
    resource blob {
        // Together these two declarations represent one logical property.
        content-type: get func() -> string
        content-type: set func(value: string)
    }
}

Either way, in both variants getters and setters:

lukewagner commented 1 year ago

Good points and thanks for the great write up! Yes, this has been in the idea queue for a while (I think maybe @guybedford brought this up somewhere too?) and hasn't been added yet mostly due to not having yet had the time to analyze the option space. Thanks for the detailed examples and ideas, which look reasonable. I can't say I have any strong opinions yet on whether to be more like JS or C#; I'd be interested to see more examples of what other languages do beyond C# and JS.

badeend commented 1 year ago

Here ya go:

Python

@property
def the_property_name(self):
   return # value

@the_property_name.setter
def the_property_name(self, value):
    # update value

Visual Basic

Public Property ThePropertyName As String
    Get
        Return name
    End Get

    Set(ByVal value As String)
        name = value
    End Set

End Property

Ruby

def name # get
    @name
end

def name=(name) # set
    @name = name
end

Swift

class Point {
    var x: Int {
        get { return /* value */ }
        set { /* update value */ }
    }
}

Kotlin

var thePropertyName: String
    get() {
        return /* value */
    }
    set(value) {
        /* update value */
    }

Objective-C

@property int someNumber;

- (int)someNumber {
    return // value
}
- (void)setSomeNumber: (int)newValue {
    // update value
}

Scala

def age = // value
def age_=(newValue: Int) {
    // update value
}

D

@property bar() { return 10; }
@property bar(int x) { writeln(x); }

Dart

int get thePropertyName {
    return /* value */
}

void set thePropertyName(int newValue) {
    /* update value */
}

F

member MyProperty
    with get() = // value
    and set(value) = // update value

C

public int MyProperty
{
    get
    {
        return /* value */;
    }
    set
    {
        /* update value */
    }
}

JavaScript

get thePropertyName() {
    return /* value */;
}

set thePropertyName(newValue) {
    /* update value */
}
lukewagner commented 1 year ago

Thanks a bunch, that's super-helpful to see the spectrum. I guess the take-away for me is that it doesn't seem like there's a clear "everyone does it this way" precedent. Also, we're in a slightly different boat than all these languages since we only care about defining the signature, not implementations, which makes our constraints slightly different.

My impression is that the "JavaScript-esque" option you presented in your original comment is the better option:

landonxjames commented 6 months ago

I have recently encountered the need for this as well. For syntax I would prefer something a little less verbose than the "Javascript-esque" option since having to define two functions each time seems like overkill. Some kind of default for the non-result getter/setter option would be nice, only requiring explicitly typing the functions if they differ from the simple case.

//This has the default getter/setter signatures
foo: string

//This type can fail on get or set, so must define the signature for both
bar: get func() -> result<string, error-type>
bar: set func(value: string) -> result<_, error-type>

A readonly keyword would be nice as well to indicate that a value would only have a getter (assuming that the default is gettable and settable).