> On Nov 17, 2017, at 12:08 AM, Brent Royal-Gordon <[email protected]>
> wrote:
>
>> On Nov 16, 2017, at 1:44 PM, Paul Cantrell <[email protected]
>> <mailto:[email protected]>> wrote:
>>
>> 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
_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution