[swift-evolution] Type-safe selectors
Joe Groff
jgroff at apple.com
Fri Dec 4 20:45:33 CST 2015
> On Dec 4, 2015, at 3:27 PM, Kevin Ballard <kevin at sb.org> wrote:
>
> The @convention(selector) as proposed is a neat idea, but it will
> completely break target/action. This is because the
> @convention(selector) is a strongly-typed function signature, but
> target/action relies on the fact that it can provide 2 parameters to the
> method and this will work with any method that matches one of the 3
> forms (2 forms on OS X):
>
> - (void)action
> - (void)action:(id)sender
> - (void)action:(id)sender forEvent:(UIEvent *)event
>
> But these 3 forms translate into the 3 distinct types:
>
> @convention(selector) T -> () -> Void
> @convention(selector) T -> AnyObject -> Void
> @convention(selector) T -> AnyObject -> UIEvent -> Void
>
> But the only way to handle this in a reasonable fashion is to allow
> these 3 types to implicitly coerce to each other, which seems like a bad
> idea and removes a lot of the benefit of trying to strongly type them.
> There's also the confusion around the receiver type T here; a selector
> can't possibly encode the receiver type, because the whole point of
> selectors is the caller doesn't care what the receiver is, it only cares
> how the receiver behaves. You could make the receiver be AnyObject, but
> now you can create a selector from one type and call it with another
> type.
Why couldn't this be covered by overloads? It's unlikely we'd be able to import selectors from Objective-C to specific types because of this and other reasons, so we'd still need an untyped Selector type, but we could easily provide well-typed overloads in Swift overlays that convert from typed selectors to untyped.
>
> Furthermore, how would you even handle this for methods that take
> selectors of arbitrary types, e.g. respondsToSelector() or various obj-c
> runtime methods? Allowing implicit conversion to a single common form
> like `@convention(selector) () -> Void` is no better than keeping the
> current Selector (and is in fact worse because it implies strong typing
> where there is none), and keeping the current Selector in addition to
> @convention(selector) is not a great solution either (it leaves the
> language as more complex, without really providing the strong typing
> that @convention(selector) looks like it's trying to do).
It seems to me that things like performSelector could be replaced by generics in the overlay in a similar way. 'respondsToSelector' is probably fine taking untyped Selectors (with a subtype relationship from @convention(selector) T -> U to Selector).
-Joe
> I also worry that allowing something like @convention(selector) would be
> confusing, because it would look like the following two code snippets
> should be identical:
>
> foo.performSelector(Foo.handleBar)
>
> and
>
> let sel = Foo.handleBar
> foo.performSelector(sel)
>
> But this can't work because it requires the ability to convert from
> @convention(swift) T -> U into @convention(selector) T -> U, which can't
> work because not all closures will have associated selectors. You'd need
> to introduce something else, like @convention(objc_swift) T -> U, that
> includes a selector and implicitly converts to both @convention(swift)
> and @convention(objc), but this is quickly becoming needlessly complex.
> And it still doesn't solve the type-safety issues above.
>
> ---
>
> My simpler proposal here would be to simply embrace the fact that
> selectors are weakly-typed, to say that any API that wants type safety
> should be changed to just take a closure (or to have an overload that
> does), and then to just have a bit of Swift syntax that gives you the
> selector for any method. I'm not sure offhand what the syntax should be,
> but I filed a couple of radars a long time ago that also asked for a
> syntax to get at the willSet/didSet property observers and the
> underlying storage for lazy properties, and suggested that these could
> all use the same bit of syntax. I don't know what the syntax should be,
> but general meaning of the syntax would be to access pieces of
> information about members (where "members" means properties and
> methods). As an example of what I'm talking about, if we decided that
> the syntax should be to use `` to access things like "foo.storage" as a
> value the way you can use it for keywords-as-identifiers, then you could
> say things like
>
> notificationCenter.addObserver(foo, selector:
> Foo.`observeBar.selector`, name: /* ... */)
> self.`lazyBar.storage` = nil
> self.`baz.didSet`(oldValue: qux)
>
> Note that I'm not actually suggesting this is the right syntax to use
> (while I like that it re-uses existing syntax, it's also pretty weird),
> but the concept is sound.
>
> -Kevin Ballard
>
> On Fri, Dec 4, 2015, at 02:26 PM, Joe Groff wrote:
>>
>>> On Dec 4, 2015, at 2:22 PM, Michel Fortin <michel.fortin at michelf.ca> wrote:
>>>
>>> Currently in Swift you can get a closure by referring to a method:
>>>
>>> let x = NSString.hasPrefix
>>> // x is of type NSString -> String -> Bool
>>>
>>> Something that would be useful here is if the closure created from Objective-C methods were special in that they could implicitly be converted to a Selector. Instead of writing manually a selector as a string, you'd just have to refer to the method, and you know there's no typo (or else you get a compile-time error). For instance, adding an observer to a NSNotificationCenter would work like this:
>>>
>>> notificationCenter.addObserver(self, selector: MyClass.observeNotification, name: NSSomeNotificationName, object: nil)
>>>
>>> This, making sure the correct selector is used for the designated method, seem like it should be somewhat more important in Swift 3 if it includes Evolution Proposal 0005 that suggests many Objective-C methods will be given Swift-specific names.
>>> https://github.com/apple/swift-evolution/blob/master/proposals/0005-objective-c-name-translation.md
>>>
>>> But why stop there when you can go one step further and actually improve type-safety? Instead of taking a Selector parameter, the NSNotificationCenter.addObserver method above could request a @convention(selector) closure of this form:
>>>
>>> @convention(selector) AnyObject -> NSNotification -> Void
>>>
>>> Under the hood that closure is still a plain selector pointer, but the compiler attaches proper type information to the arguments. Since `addObserver` now declares it wants a selector with the given signature, the compiler can enforce that the arguments and return type for the passed selector are compatible. You
>>
>> This is a great approach, and it's mostly exactly what I've had in mind
>> for this. Another nice thing about @convention(selector) is that the
>> compiler could also context-free closures, like with @convention(c), by
>> compiling them down to categories with mangled methods.
>>
>>>
>>> Moreover, the @convention(selector) closure you get can then be used as a normal closure inside a Swift method that can be called from Objective-C:
>>>
>>> @objc func callSelector(selector: @convention(selector) NSString -> String -> Bool) -> Bool {
>>> let str = NSString(string: "hello")
>>> return selector(str)("hell")
>>> }
>>>
>>> let x = NSString.hasPrefix
>>> // x is of type @convention(selector) NSString -> String -> Bool
>>> callSelector(x)
>>>
>>> So that would make selectors less error-prone because the compiler can type-check them, and you can use selectors in Swift code in a very natural manner.
>>
>> I would say that 'let x = NSString.hasPrefix' should still give you a
>> @convention(swift) function value by default; that's what we do with
>> method references in general, but you could ask for a
>> @convention(selector) reference explicitly:
>>
>> let x: @convention(selector) X -> Y -> Z = X.hasPrefix
>>
>> -Joe
>>
>>> - - -
>>>
>>> This is inspired from the D/Objective-C compiler I prototyped a while ago. There, I made selectors typed with their arguments because:
>>>
>>> 1. I wanted to preserve D's type-safety while still being able to use selectors, and
>>> 2. I needed to decouple selector names from method names in the code; this would later allow me to implement overloading by adding some name mangling in selectors. Obtaining selectors by referring to the method allowed selectors to become an implementation detail while providing a nice way to associate the parameter types.
>>>
>>> For reference, here is the meager documentation for that feature:
>>> https://michelf.ca/projects/d-objc/syntax/#selector-literals
>>> And for the selector-name mangling:
>>> https://michelf.ca/projects/d-objc/syntax/#generated-selectors
>>>
>>>
>>> --
>>> Michel Fortin
>>> michel.fortin at michelf.ca
>>> https://michelf.ca
>>>
>>> _______________________________________________
>>> swift-evolution mailing list
>>> swift-evolution at swift.org
>>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution at swift.org
>> https://lists.swift.org/mailman/listinfo/swift-evolution
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
More information about the swift-evolution
mailing list