[Pitch] Introduce User-defined "Dynamic Member Lookup" Types

* Ruby and Perl don't have the "call a method by fetching a closure property and invoking it" behavior you're relying on here. Instead, Ruby has a syntax for settable "overloads" of methods (i.e. you can write `def someMember` and `def someMember= (newValue)`), while Perl supports lvalue methods (but sometimes uses getter and setter method pairs instead). How do you envision these behaviors being bridged to Swift? I worry that this protocol may not be sufficient, and that we may need a design which can distinguish between looking up methods and looking up properties.

I’ve never pried the lid of Ruby’s implementation of method dispatch, but I’m pretty sure that if foo defines a bar method, then

    foo.bar(…args…)

is fully equivalent to:

    foo.method(:bar).call(…args…)

IOW, there is an intermediate Method object that would fit the shape of the proposed callable protocol.

If foo instead doesn’t actually declare the bar method, but instead handles it via method_missing or __send__, then foo.method(:bar) raises an exception. However, it would be trivial to write a deferred invocation wrapper that also fits the shape of the proposed protocols and calls foo.send(“bar”, …args…) at the appropriate time.

In short, I don’t think there’s a problem here.

True. Ruby doesn't dispatch everything through a Method object…but I guess Swift sort of does, and we're bridging semantics into Swift here.

In the example you bring up:

you can write `def someMember` and `def someMember= (newValue)`)

…there is no overloading. The = is _part of the method name_, i.e. there is a `someMember` method and a `someMember=` method.

You're right—I was speaking imprecisely when I used the word "overloading". Nevertheless, Ruby doesn't quite directly interpret `x.someMember = y` as `x.someMember= (y)`—it supports operators like `+=`, which do a getter-operation-setter dance.

The following are equivalent:

    foo.bar = 3 # just sugar
    foo.bar=(3)
    foo.send("bar=", 3)

Ruby allows ?, !, and = as the last char of method names, and AFAIK other than the special sugar around setters, they are just parts of the method name with no further semantic significance.

You're correct that, with this design, you could access Ruby accessors from Swift with syntax like:

  myObj.name()
  myObj.`name=`("Chris") // If we loosened the characters allowed in backticks

My point is simply that this is a poor mapping, for much the same reason `dog["add_trick"].call(…)` is a poor mapping. It's technically correct and exposes the functionality, but it's awkward and doesn't match the user's mental model.

If we had separate subscripts for methods and properties, then the property subscript could immediately call the appropriate getters and setters, while the method subscript could return a ready-to-call `Method` object. This would prevent you from fetching uncalled methods using the `x.method` syntax (as opposed to `x.method(_:)`, which could be made to work), but that seems a lot better than a mapping that's technically correct but breaks the mental model.

* Let's step away from bridging entirely and just think about Swift for a moment. There are cases where we'd like to make *semi*-dynamic proxies which wrap another type and allow operations based on what's statically known about that type. Think, for example, of the appearance proxy in UIKit: This is an object attached to UIView subclasses which lets you (in essence) set default values for all instances. We currently just pretend it's an instance of `Self`, which mostly works because of Objective-C, but a Swift-native version would probably prefer to return a `UIAppearance<Self>` object which used its knowledge of `Self` to expose `Self`'s properties on itself. Is there a way we could design this feature, or a related feature, to cover that kind of use case? That is, to allow a limited set of keys—perhaps even key-path-based when you want static control—with a different type for each key, *or* to allow any key with some common type, depending on your type's needs?

Per my question about whether native methods shadow dynamic ones, one might be able to achieve some of this using a mix of statically typed, statically declared methods + dynamic members.

So, let me sketch a vague idea of how this might work. This is definitely not fully baked, but it might give you an idea.

Imagine you want to write an ORM. Its root class, `Record`, should expose a property for each field in a given record. You could make the ORM generate an enum like this:

  enum PersonProperty: String {
    case id, name, birthDate, address
  }

And then make `Record` dynamically gain a property for each enum case by defining the `subscript(additionalProperty:)` subscript:

  class Record<Property> where Property: RawRepresentable, Property.RawValue == String {
    …
    subscript(additionalProperty property: Property) -> Any {
      get { … }
      set { … }
    }
  }

Swift will notice this overload and essentially augment `Record<T>` with a property for each case of `T`, giving it type `Any`. Attempting to use any of these properties will pass through this subscript. (Presumably, if you generated the key path `\Record<PersonProperty>.name`, you'd actually end up with the key path for `\Record<PersonProperty>.[additionalProperty: PersonProperty.name]`.)

With a more sophisticated (and convoluted) design, our ORM could give the fields more specific types than `Any`:

  struct Property<RecordType: Record, Value> {
    let name: String
  }
  extension Property where RecordType == Person, Value == Int {
    static let id = Property(name: "id")
  }
  extension Property where RecordType == Person, Value == String {
    static let name = Property(name: "name")
    static let address = Property(name: "address")
  }
  extension Property where RecordType == Person, Value == Date {
    static let birthDate = Property(name: "birthDate")
  }

And we could then expose typed, automatically created properties:

  protocol Record {
    …
  }
  extension Record {
    subscript<T>(additionalProperty property: Property<Self, T>) -> T {
      get { … }
      set { … }
    }
  }
  
  struct Person: Record {}

That would work for arbitrary fixed sets of properties, but we can extend this to wrapper types. Imagine you want to write the `UIAppearance` class I mentioned previously. (Actually, we'll call it `UIAppearanceProxy` to avoid names already used in UIKit.) Your basic structure looks like this:

  class UIAppearanceProxy<View: UIAppearance> {
    let containers: [UIAppearanceContainer.Type]
    let traits: UITraitCollection
    
    var properties: [PartialKeyPath<View>: Any] = [:]
  }

Now, to make all properties of the `View` class settable on this class, you can overload the `additionalProperty` subscript to accept `View` keypaths:

  extension UIAppearanceProxy {
    subscript<T>(additionalProperty viewKeyPath: KeyPath<View, T>) -> T? {
      get {
        return properties[viewKeyPath] as! T?
      }
      set {
        properties[viewKeyPath] = newValue
      }
    }
  }

Swift would notice this overload and allow any `View` property to be set on `UIAppearanceProxy`. The subscript gives its return type as `T?`, so when it does so, it will add an extra level of optionality—since `UITextField.font` is of type `UIFont?`, `UIAppearanceProxy<UITextField>.font` will be of type `UIFont??`. You can modify types like that in any way the type system permits.

For the totally dynamic use case, like Python, you could overload `subscript(additionalProperty:)` to take a `String` (or any other `ExpressibleByStringLiteral` type, like a `RubySymbol`):

  extension PyVal {
    subscript(additionalProperty member: String) -> PyVal {
      get {
        let result = PyObject_GetAttrString(borrowedPyObject, member)!
        return PyRef(owned: result) // PyObject_GetAttrString returns +1 result.
      }
      set {
        PyObject_SetAttrString(borrowedPyObject, member,
          newValue.toPython().borrowedPyObject)
      }
    }
  }

Swift would map any completely unknown property to this key path, if present.

* * *

Methods, I think, could be handled analogously. If you wanted a fixed but arbitrary set of automatically-"generated" methods, you might say something like:

  enum PersonMethod: Method {
    case insertJob(Job.Type, at: Int.Type)
    case removeJob(at: Int.Type)

    func implementation<P>(for record: Record<P, Self>) -> (Any...) -> Any { … }
  }

  class Record<PropertyType: Property, MethodType: Method> … {
    …
    subscript (additionalMethod method: MethodType) -> (Any...) -> Any {
      get { return method.implementation(for: self) }
    }
  }

Swift will notice this overload and essentially augment `Record`'s methods with ones corresponding to the static methods of `MemberType`, so long as all of their parameters are metatypes. Attempting to use any of these methods will pass through this subscript. So `myPerson.insertJob(myJob, at: 0)` gets compiled into `myPerson[additionalMethods: PersonMethod.insertJob(Job.self, at: Int.self)](myJob, 0)`.

If you wanted stronger typing, you could do something like this:

  struct Method<RecordType: Record, ParameterTypes, ReturnType>: Method {
    enum Behavior {
      case insert
      case remove
    }
    
    let field: String
    let behavior: Behavior
    
    func implementation(for record: RecordType) -> (ParameterTypes) -> ReturnType { … }
  }
  extension PersonMethod where RecordType == Person, ParameterTypes == (Job.Type, Int.Type), ReturnType == Job {
    static func insertJob(Job.Type, at _: Int.Type) -> PersonMethod {
      return PersonMethod(field: "job", behavior: .insert)
    }
  }
  extension PersonMethod where RecordType == Person, ParameterTypes == (Int.Type), ReturnType == Void {
    static func removeJob(at _: Int.Type) -> PersonMethod {
      return PersonMethod(field: "job", behavior: .remove)
    }
  }

  protocol Record {
    …
  }
  extension Record {
    subscript <ParameterTypes, ReturnType>(additionalMethod method: Method<Self, ParameterTypes, ReturnType>) -> (ParameterTypes) -> ReturnType {
      get { return method.implementation(for: self) }
    }
  }

(This would require that `subscript(additionalMethod:)` be allowed to pack parameter types into a single tuple, unless we were willing to wait for variadic generics.)

We could perhaps add a special case for wrapping methods which would leverage the unbound methods on a type (or whatever future replacement for that feature we devise):

  class UIAppearanceProxy<View: UIAppearance> {
    let containers: [UIAppearanceContainer.Type]
    let traits: UITraitCollection
    
    var properties: [PartialKeyPath<View>: Any] = [:]
    var calls: [(View) -> Void]
  }

  extension UIAppearanceProxy {
    subscript <ParameterTypes, ReturnType>(additionalMethod method: (View) -> (ParameterTypes) -> ReturnType) -> (ParameterTypes) -> Void {
      return { params in
        self.calls.append({ view in _ = method(view)(params) })
      }
    }
  }

Note that here, we're completely changing the type of the method we return! Since we're deferring the call until later, we throw away the original return type and substitute `Void` instead. When you try to pull similar tricks in Objective-C (for instance, with `-[NSUndoManager prepareWithInvocationTarget:]`), you end up with invalid return values.

And of course, for Python and the like, you can use `ExpressibleByStringLiteral` types:

  extension PyVal {
    subscript (additionalMethod member: String) -> (PythonConvertible...) -> PyVal {
      let (baseName, labels) = splitMethodName(member)
      
      // Python has a unified namespace for methods and properties, so we'll just leverage the property lookup.
      let method = self[additionalProperty: baseName]
      return { arguments in method.dynamicCall(arguments: zip(labels, arguments)) }
    }
    
    private func splitMethodName(_ name: String) -> (baseName: Substring, labels: [Substring]) {
      guard let argListIndex = name.index(of: "(") else {
        return (name, )
      }
      
      let argListSansParens = name[argListIndex...].dropFirst().dropLast()
      return (name[...argListIndex], argListSansParens.split(separator: ":")
    }
  }

For types using both mechanisms, `subscript(additionalMethod:)` would be called whenever there was some sign—like a parameter list (with or without arguments) or an assignment to a function type—that we were looking up a method; `subscript(additionalProperty:)` would be called when there was no such sign. Python doesn't really need the two cases to be split up, but this would help a Ruby bridge differentiate between property and method accesses, and I think it would help with the more static cases as well.

* * *

This is a complex design, but I really like how much ground it covers. Python bridging is just the beginning—it can cover a *ton* of use cases with only a couple of variations. By not using protocols, it permits overloading and the use of generics tricks that might not be accessible to a protocol.

To begin with, we could support only `subscript(additionalProperty: String)` and `subscript(additionalMethod: String)`, and then expand over time to cover the more static use cases. That would get

···

On Nov 16, 2017, at 1:44 PM, Paul Cantrell <paul@bustoutsolutions.com> wrote:

On Nov 16, 2017, at 12:00 AM, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

--
Brent Royal-Gordon
Architechies

I think this protocol and its subscript need a better name. From a user of the class point of view, the subscript looks like you can retrieve the members of the class, whereas in reality you'll only get the ones you have manually implemented. Even methods and properties having the `dynamic` attribute won't be available, even though the subscript name would suggest that.

I would propose adding the word `supplemental`to the subscript name and the name of the protocol to make it clearer that this is only for adding members that are not declared in the class (including the `dynamic` ones).

As in `SupplementalDynamicMemberLookupProtocol` and `supplementalDynamicMember` for the subscript.

I’m totally open to suggestions for a better name, but I don’t see what “Supplemental” is adding here. Recall that this protocol is compiler “magic” that is part of an implementation of a type, it isn’t really part of the user-exposed API of the type. I wouldn’t expect a user to ever write:

   pyVal[dynamicMember: “foo”]

Even though they could.

I'm not sure why you say it's not part of the user-exposed API. The type conforms to the protocol, and the protocol has this subscript, and there is nothing preventing someone from using it. And if for some reason you have the name of the member in a string variable and want to use it, you *have to* use it to get to the variable. So it'll definitely get used.

Perhaps supplemental isn't the right word, but I wanted to convey that it supplements the Swift-defined methods and does not shadow them. For instance, in your PyVal protocol type there's a member `ownedPyObject` that won't be accessible with `dynamicMember` but will be dispatched through the protocol witness table.

Maybe we need to add private conformances to Swift or something :-)

Maybe, but that's not part of the proposal. :-)

···

Le 20 nov. 2017 à 21:22, Chris Lattner <sabre@nondot.org> a écrit :

On Nov 16, 2017, at 4:49 AM, Michel Fortin via swift-evolution <swift-evolution@swift.org> wrote:

--
Michel Fortin
https://michelf.ca

Looking at this again, I realize I accidentally generated a giant wall of text. tl;dr:

* There is no protocol; this is done through an ad-hoc mechanism to allow overloading.

* Use `subscript(additionalProperty:)` to dynamically create a property. The parameter type can be any of:

  * An arbitrary type: compiler will look for static constants (including no-payload cases) of the type accepted by the parameter, and treat them as valid properties of `Self`.
  * A `KeyPath` type: compiler will look for properties on the KeyPath's Root type and treat them as valid properties of `Self`.
  * An `ExpressibleByStringLiteral` type: compiler will treat any identifier as a valid property.

* Use `subscript(additionalMethod:)` to dynamically create methods. The subscript returns a closure which is already bound to `self`, but not to the parameters. The parameter type can be any of:

  * An arbitrary type: compiler will look for static methods (including cases) returning the type accepted by the parameter and whose parameters are all metatypes, and will treat them as valid methods of `Self` (after transforming all metatypes into plain types).
  * A `(Foo) -> (ParamList) -> ReturnValue` type (i.e. an unbound method): Compiler will treat all instance methods of `Foo` as valid methods of `Self`.
  * An `ExpressibleByStringLiteral` type: compiler will treat any identifier as a valid method (and will pass both the base name and argument labels in a single string).

* The string literal forms cover use cases where you want to accept anything and sort it out at runtime. The keypath/unbound method forms cover use cases where you're echoing the interface of an underlying type. The arbitrary type forms cover use cases where you want to create members based on compact tables of information.

tl;dr for the tl;dr: I think we can cover a ton of use cases for automatically "creating" properties and methods using minor variations on a single mechanism which only vaguely resembles this one.

···

On Nov 16, 2017, at 10:08 PM, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:

So, let me sketch a vague idea of how this might work. This is definitely not fully baked, but it might give you an idea.

--
Brent Royal-Gordon
Architechies

In the example you bring up:

you can write `def someMember` and `def someMember= (newValue)`)

…there is no overloading. The = is _part of the method name_, i.e. there is a `someMember` method and a `someMember=` method.

You're right—I was speaking imprecisely when I used the word "overloading". Nevertheless, Ruby doesn't quite directly interpret `x.someMember = y` as `x.someMember= (y)`—it supports operators like `+=`, which do a getter-operation-setter dance.

True, `foo.bar += x` is just sugar for `foo.bar = foo.bar + x`, which in turn is sugar for `foo.bar=(foo.bar.+(y))`. But does this pose a problem for a Swift → Ruby bridge? Presumably such a bridge would:

1. define a Swift += operator on RubyObj that does the same expansion as above, and

2. forward the property setter for `bar` to Ruby as a `bar=` invocation.

I don’t think there’s a problem here. But maybe I’m missing it?

The following are equivalent:

    foo.bar = 3 # just sugar
    foo.bar=(3)
    foo.send("bar=", 3)

Ruby allows ?, !, and = as the last char of method names, and AFAIK other than the special sugar around setters, they are just parts of the method name with no further semantic significance.

You're correct that, with this design, you could access Ruby accessors from Swift with syntax like:

  myObj.name()
  myObj.`name=`("Chris") // If we loosened the characters allowed in backticks

My point is simply that this is a poor mapping, for much the same reason `dog["add_trick"].call(…)` is a poor mapping. It's technically correct and exposes the functionality, but it's awkward and doesn't match the user's mental model.

Well sure, that would be awkward, but doesn’t Chris’s proposed protocol allow idiomatic access to work too?

    extension RubyObj: DynamicMemberLookupProtocol {
      subscript(dynamicMember member: String) -> RubyObj {
        get {
          return /* …some deferred callable thing… */
        }
        set {
          RubyObject_send(rubyObject, method: "\(member)=", args: [newValue])
        }
      }
    }

That would make `myObj.name = “Chris"` in Swift work as expected. (This is using a made-up RubyObj binding, but I think it makes the point.)

What _is_ a bit nasty, as you pointed out, is that `myObj.name` does _not_ work as expected. Instead of returning the name, it returns a closure that returns the name when called. Yuck.

In Ruby, `myObj.name()` is equivalent to `myObj.name`, and either works. In Swift, I don’t see that it’s possible to make both work with Chris’s proposal.

If we had separate subscripts for methods and properties, then the property subscript could immediately call the appropriate getters and setters, while the method subscript could return a ready-to-call `Method` object.

Hmm, yes, I think you’re right. It seems like that would fix the nastiness above. Better yet, why bother with the ready-to-call Method-like object? Just call it! A Ruby binding with separate property and method handling would then look like this:

    extension RubyObj: DynamicMemberLookupProtocol {
      func callDynamicMethod(dynamicMethod method: String, args: [RubyObject]) -> RubyObj {
        get {
          return RubyObject_send(rubyObject, method: member, args: args)
        }
      }
      
      subscript(dynamicMember member: String) -> RubyObj {
        get {
          return RubyObject_send(rubyObject, method: member, args: )
        }
        set {
          RubyObject_send(rubyObject, method: "\(member)=", args: [newValue])
        }
      }
    }

When Swift sees myObj.name, it uses the getter subscript. When Swift sees myObj.name(), it uses the method invocation. Both work in Swift just as they do in Ruby.

Note that for this to work, the Swift compiler itself has to tell the Ruby binding whether the member access looks like a property or method access.

• • •

I confess I didn’t follow every detail of your “wall of text,” but I did find this example compelling:

That would work for arbitrary fixed sets of properties, but we can extend this to wrapper types. Imagine you want to write the `UIAppearance` class I mentioned previously. (Actually, we'll call it `UIAppearanceProxy` to avoid names already used in UIKit.) Your basic structure looks like this:

  class UIAppearanceProxy<View: UIAppearance> {
    let containers: [UIAppearanceContainer.Type]
    let traits: UITraitCollection
    
    var properties: [PartialKeyPath<View>: Any] = [:]
  }

Now, to make all properties of the `View` class settable on this class, you can overload the `additionalProperty` subscript to accept `View` keypaths:

  extension UIAppearanceProxy {
    subscript<T>(additionalProperty viewKeyPath: KeyPath<View, T>) -> T? {
      get {
        return properties[viewKeyPath] as! T?
      }
      set {
        properties[viewKeyPath] = newValue
      }
    }
  }

That’s an impressive level of dynamism for a statically type-checked idea. Awful lot of complexity to bite off, though!

Cheers,

Paul

···

On Nov 17, 2017, at 12:08 AM, Brent Royal-Gordon <brent@architechies.com> wrote:

On Nov 16, 2017, at 1:44 PM, Paul Cantrell <paul@bustoutsolutions.com <mailto:paul@bustoutsolutions.com>> wrote:

As in `SupplementalDynamicMemberLookupProtocol` and `supplementalDynamicMember` for the subscript.

I’m totally open to suggestions for a better name, but I don’t see what “Supplemental” is adding here. Recall that this protocol is compiler “magic” that is part of an implementation of a type, it isn’t really part of the user-exposed API of the type. I wouldn’t expect a user to ever write:

   pyVal[dynamicMember: “foo”]

Even though they could.

I'm not sure why you say it's not part of the user-exposed API. The type conforms to the protocol, and the protocol has this subscript, and there is nothing preventing someone from using it.

Yes, I understand that of course.

And if for some reason you have the name of the member in a string variable and want to use it, you *have to* use it to get to the variable. So it'll definitely get used.

Not necessarily. Nothing prevents a *specific* client from implementing a better accessor for their specific use, and I’d strongly encourage them to do so. This is a compiler hook - just like ExpressibleByArrayLiteral exposes an ArrayLiteralElement associated type and init(arrayLiteral:..) initializer… even on Set!

I have definitely seen user code using these, but that isn’t the intention. The best solution to this is either to prevent them from being exported as API (my suggestion of something something like a “private conformance” implementation detail thing) or by simply marking these members with an attribute, so they don’t show up in code completion.

I do care about this sort of thing getting fixed, and I personally prefer the attribute+code completion change, but this is an existing problem in swift, orthogonal from this proposal. If you’d like to see if fixed, then by all means, please bring this up and fix it. It is more offensive that Int gets weird members like IntegerLiteralType and an integerLiteral initializer [[[which show up all the time…. :-( :-( ]]] than the specific impact this proposal will have on a small set of narrow types people hardly interact with.

Perhaps supplemental isn't the right word, but I wanted to convey that it supplements the Swift-defined methods and does not shadow them. For instance, in your PyVal protocol type there's a member `ownedPyObject` that won't be accessible with `dynamicMember` but will be dispatched through the protocol witness table.

Yes, I understand what you’re saying, but specific naming does matter. I don’t think that ownedPyObject is in huge danger of conflicting with something that matters, but if it were, I’d suggest name mangling it to something even less likely to conflict, and I’d mark ownedPyObject with the same attribute to hide it from code completion, so people don’t see it every time they code complete on a PyVal.

-Chris

···

On Nov 20, 2017, at 7:15 PM, Michel Fortin via swift-evolution <swift-evolution@swift.org> wrote:

In Ruby, `myObj.name()` is equivalent to `myObj.name`, and either works. In Swift, I don’t see that it’s possible to make both work with Chris’s proposal.

IIUC, the goal is not to make swift look and behave the same as ruby or python, but to be able to use ruby or python object in a swift way (without indirect call and other nasty constructions).

I don’t see requiring the .property syntax and prohibiting the .property() one as an issue. I would even say this is the thing to do, as it would make the swift code more understandable to Swift dev that are not used to Ruby.

In ruby, parens are optional. So,

v = foo.value

and

v = foo.value()

are identical.

Ok, I wasn’t aware of that. It isn’t clear that we’d want to carry that into a “Ruby APIs when used in Swift” though! One could definitely argue against the former calling a method, even if that is possible in Ruby APIs.

There dot syntax is only used for method invocation, so there is no external access to instance variables without some twiddling; similarly getting access to a Proc/lambda/Method requires twiddling in Ruby (although there are shortcuts in normal use, like Symbol#to_proc)

I think you’re missing the idea here: the idea isn’t to provide exactly syntax mapping of Ruby (or Python) into Swift, it is to expose the underlying semantic concepts in terms of Swift’s syntax. In the case of Python, there is a lot of direct overlap, but there is also some places where Swift and Python differs (e.g. Python slicing syntax vs Swift ranges). In my opinion, Swift syntax wins here, we shouldn’t try to ape a non-native syntax in Swift.

For mapping to Swift, I would say that parens are needed; we can’t guess whether a `foo.bar` is meant to be asking for the value of attribute bar or a reference to method bar.

+1

Chris, did you follow at all the earlier chain of emails where Brent, Jean-Daniel and I hashed this out at length? You may not have got to it yet….

Key excerpts:

–––––––––––––––––––––––––

An “always use parens” bridge to Ruby has bad ergonomics
Zero-arg Ruby methods are a mixture of property-like things that would certainly not use parens in Swift, and function-like things that certainly would:

    // Idiomatic Swift:
    post.author.name.reversed()

    // Swift bridging to Ruby…

    // …if no-args methods •must• use parens:
    post.author().name().reverse()

    // …if no-args methods •can’t• use parens:
    post.author.name.reverse

If the goal is to make Swift mostly feel like Swift even when bridging to Ruby, then the bridge needs to support both access forms.

–––––––––––––––––––––––––

Separating method calls from property accesses solves the problem

Brent wrote:

If we had separate subscripts for methods and properties, then the property subscript could immediately call the appropriate getters and setters, while the method subscript could return a ready-to-call `Method` object.

Better yet, why bother with the ready-to-call Method-like object? Just call it! A Ruby binding with separate property and method handling would then look like this:

    extension RubyObj: DynamicMemberLookupProtocol {
      func callDynamicMethod(dynamicMethod method: String, args: [RubyObject]) -> RubyObj {
        get {
          return RubyObject_send(rubyObject, method: member, args: args)
        }
      }
      
      subscript(dynamicMember member: String) -> RubyObj {
        get {
          return RubyObject_send(rubyObject, method: member, args: )
        }
        set {
          RubyObject_send(rubyObject, method: "\(member)=", args: [newValue])
        }
      }
    }

When Swift sees myObj.name, it uses the getter subscript. When Swift sees myObj.name(), it uses the method invocation. Both work in Swift just as they do in Ruby — and more importantly, Ruby APIs wouldn’t feel so very awkward when used from Swift.

···

On Nov 20, 2017, at 7:47 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:

On Nov 20, 2017, at 1:41 PM, David Waite <david@alkaline-solutions.com> wrote:

More difficult would be the use of ‘=‘, ‘!’, and ‘?’ - all legal in Ruby method names as suffixes.

Using those would require backquotes:

x.`what?`()

-Chris

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

It really wouldn’t. Zero-arg Ruby methods are a mixture of property-like things that would certainly not use parens in Swift, and function-like things that certainly would:

    // Idiomatic Swift:
    post.author.name.reversed()

    // Swift bridging to Ruby…

    // …if no-args methods •must• use parens:
    post.author().name().reverse()

    // …if no-args methods •can’t• use parens:
    post.author.name.reverse

If the goal is to make Swift mostly look like Swift even when bridging to Ruby, then the bridge needs to support both access forms.

Cheers, P

···

On Nov 18, 2017, at 1:44 AM, Jean-Daniel <mailing@xenonium.com> wrote:

In Ruby, `myObj.name()` is equivalent to `myObj.name`, and either works. In Swift, I don’t see that it’s possible to make both work with Chris’s proposal.

IIUC, the goal is not to make swift look and behave the same as ruby or python, but to be able to use ruby or python object in a swift way (without indirect call and other nasty constructions). I don’t see requiring the .property syntax and prohibiting the .property() one as an issue. I would even say this is the thing to do, as it would make the swift code more understandable to Swift dev that are not used to Ruby.

I think you’re missing the idea here: the idea isn’t to provide exactly syntax mapping of Ruby (or Python) into Swift, it is to expose the underlying semantic concepts in terms of Swift’s syntax. In the case of Python, there is a lot of direct overlap, but there is also some places where Swift and Python differs (e.g. Python slicing syntax vs Swift ranges). In my opinion, Swift syntax wins here, we shouldn’t try to ape a non-native syntax in Swift.

For mapping to Swift, I would say that parens are needed; we can’t guess whether a `foo.bar` is meant to be asking for the value of attribute bar or a reference to method bar.

+1

Chris, did you follow at all the earlier chain of emails where Brent, Jean-Daniel and I hashed this out at length? You may not have got to it yet….

Perhaps not, I’m just catching up on this thread now. Keep in mind I know almost nothing about Ruby :-)

An “always use parens” bridge to Ruby has bad ergonomics
Zero-arg Ruby methods are a mixture of property-like things that would certainly not use parens in Swift, and function-like things that certainly would:

    // Idiomatic Swift:
    post.author.name.reversed()

    // Swift bridging to Ruby…

    // …if no-args methods •must• use parens:
    post.author().name().reverse()

    // …if no-args methods •can’t• use parens:
    post.author.name.reverse

If the goal is to make Swift mostly feel like Swift even when bridging to Ruby, then the bridge needs to support both access forms.

Ok, that can certainly be implemented by the two proposals I have in flight. No obvious problem there.

Separating method calls from property accesses solves the problem

Brent wrote:

If we had separate subscripts for methods and properties, then the property subscript could immediately call the appropriate getters and setters, while the method subscript could return a ready-to-call `Method` object.

Better yet, why bother with the ready-to-call Method-like object? Just call it! A Ruby binding with separate property and method handling would then look like this:

    extension RubyObj: DynamicMemberLookupProtocol {
      func callDynamicMethod(dynamicMethod method: String, args: [RubyObject]) -> RubyObj {
        get {
          return RubyObject_send(rubyObject, method: member, args: args)
        }
      }
      
      subscript(dynamicMember member: String) -> RubyObj {
        get {
          return RubyObject_send(rubyObject, method: member, args: )
        }
        set {
          RubyObject_send(rubyObject, method: "\(member)=", args: [newValue])
        }
      }
    }

When Swift sees myObj.name, it uses the getter subscript. When Swift sees myObj.name(), it uses the method invocation. Both work in Swift just as they do in Ruby — and more importantly, Ruby APIs wouldn’t feel so very awkward when used from Swift.

Right, that should work directly!

-Chris

···

On Nov 20, 2017, at 6:06 PM, Paul Cantrell <cantrell@pobox.com> wrote:

I just sent out draft 2 of the DynamicCallable proposal, which directly includes support for this. Thanks for the discussion and insight!

-Chris

···

On Nov 20, 2017, at 6:12 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:

Separating method calls from property accesses solves the problem

Brent wrote:

If we had separate subscripts for methods and properties, then the property subscript could immediately call the appropriate getters and setters, while the method subscript could return a ready-to-call `Method` object.

Better yet, why bother with the ready-to-call Method-like object? Just call it! A Ruby binding with separate property and method handling would then look like this:

    extension RubyObj: DynamicMemberLookupProtocol {
      func callDynamicMethod(dynamicMethod method: String, args: [RubyObject]) -> RubyObj {
        get {
          return RubyObject_send(rubyObject, method: member, args: args)
        }
      }
      
      subscript(dynamicMember member: String) -> RubyObj {
        get {
          return RubyObject_send(rubyObject, method: member, args: )
        }
        set {
          RubyObject_send(rubyObject, method: "\(member)=", args: [newValue])
        }
      }
    }

When Swift sees myObj.name, it uses the getter subscript. When Swift sees myObj.name(), it uses the method invocation. Both work in Swift just as they do in Ruby — and more importantly, Ruby APIs wouldn’t feel so very awkward when used from Swift.

Right, that should work directly!

It’s unfortunate that we can’t know what is a property and what is a method, but if we can’t tell them appart, supporting both form seems reasonable.

···

Le 18 nov. 2017 à 17:13, Paul Cantrell via swift-evolution <swift-evolution@swift.org> a écrit :

On Nov 18, 2017, at 1:44 AM, Jean-Daniel <mailing@xenonium.com <mailto:mailing@xenonium.com>> wrote:

In Ruby, `myObj.name()` is equivalent to `myObj.name`, and either works. In Swift, I don’t see that it’s possible to make both work with Chris’s proposal.

IIUC, the goal is not to make swift look and behave the same as ruby or python, but to be able to use ruby or python object in a swift way (without indirect call and other nasty constructions). I don’t see requiring the .property syntax and prohibiting the .property() one as an issue. I would even say this is the thing to do, as it would make the swift code more understandable to Swift dev that are not used to Ruby.

It really wouldn’t. Zero-arg Ruby methods are a mixture of property-like things that would certainly not use parens in Swift, and function-like things that certainly would:

    // Idiomatic Swift:
    post.author.name.reversed()

    // Swift bridging to Ruby…

    // …if no-args methods •must• use parens:
    post.author().name().reverse()

    // …if no-args methods •can’t• use parens:
    post.author.name.reverse

If the goal is to make Swift mostly look like Swift even when bridging to Ruby, then the bridge needs to support both access forms.

Cheers, P