[swift-evolution] [Proposal Draft] automatic protocol forwarding

Brent Royal-Gordon brent at architechies.com
Tue Dec 29 18:10:29 CST 2015


>> * Does it have to be a protocol? Why not also allow the concrete type of the property you're forwarding to? Obviously you couldn't form a subtype relationship (unless you could...), but this might be useful to reduce boilerplate when you're proxying something.
> 
> This is addressed in the alternatives considered section.

Sorry, I missed that, probably because the sample code in that section didn't show such a forwarding.

> The short answer is that there the direct interface of the concrete type does not contain sufficient information about potential Self parameters to do this well.  This information does exist in the protocol declarations.  Allowing this information to be specified in concrete interfaces would add enough complexity to the language that I don’t think it is worthwhile.

That's a good point. You could perhaps add a way to tweak the forwarding of certain members, but that'd be a little tricky.

One of the things I'd like to see is the ability to proxy for an instance without the person writing the proxy knowing which instances it'll be used with. Think, for example, of the Cocoa animator proxy, or `NSUndoManager.prepareWithInvocationTarget(_:)`. It'd be nice if a Swift equivalent could return, say, `NSAnimatorProxy<View>` or `NSUndoManager.InvocationTarget<Target>`, which has all the methods of the generic type but records the calls for later use.

Of course, what you really want is for only a particular subset of the methods to be available on the proxy (animated methods on `NSAnimatorProxy`, Void methods on `NSUndoManager.InvocationTarget`), and of course in these cases you're not calling directly through to the underlying methods. So I might just be barking up the wrong tree here.

>> * Why the method-based conversion syntax for return values, rather than something a little more like a property declaration?
>> 
>> 	var number: Int
>> 	forward IntegerType to number {
>> 		static return(newValue: Int) {
>> 			return NumberWrapper(newValue)
>> 		}
>> 		return(newValue: Int) {
>> 			return NumberWrapper(newValue)
>> 		}
>> 	}
> 
> This is actually a really good idea to consider!  I didn’t consider something like this mostly because I didn’t think of it.  I’m going to seriously consider adopting an approach along these lines.

Great.

> One possible advantage of the approach I used is that the initializer may already exist for other reasons and you would not need to do any extra work.

True. But it may also exist and *not* do what you want in the forwarding case. It's easier to explicitly use the right initializer than it is to work around the forwarding system implicitly using the wrong one.

> A big advantage of this approach is that it would work even when you are forwarding different protocols to more than one member with the same type.

But again, if that's the wrong behavior, there's no good way to fix it.

>> * If you want to keep the method-based syntax, would it make sense to instead have an initializer for instance initializers too, and just have it take a second parameter with the instance?
>> 
>> 	init(forwardedReturnValue: Int) {...}
>> 	init(forwardedReturnValue: Int, from: NumberWrapper) {…}
> 
> Part of the reason the instance method was used is because sometimes the right thing to do might be to mutate and then return self.  Using an instance method gives you the flexibility to do that if necessary.

In practice, I'm not sure that's actually the case very often. How frequently will the internal type return a changed value, but your identically-named cover method ought to mutate the property? That completely changes the semantics of the underlying call.

I mean, what you're proposing would be something like this:

	class ListOfThings {
		private var actualList: [Thing]
		
		func filter(predicate: Thing -> Bool) -> ListOfThings {
			let returnValue = actualList.filter(predicate)
			actualList = returnValue
			return ListOfThings(returnValue)
		}
	}

Is that a thing you actually expect people to do?

>> * Does this mean that a `public forward` declaration would forward `internal` members through synthesized `public` interfaces, if the forwarder and forwardee happened to be in the same module?
>> 
>>> All synthesized members recieve access control modifiers matching the access control modifier applied to the forward declaration.
> 
> Yes, if the forwardee had internal visibility and the forwarder was public the forwarder could publicly forward the interface.  This is intentional behavior.  The forwardee may well be an internal implementation detail while the methods of the protocol are part of the public interface of the forwarder.  It is possible to write code that does this manually today.

I suppose that, if it's always a protocol you're forwarding to, you can assume that none of the protocol methods are internal-only implementation details. But I have to admit that I'm still concerned about this; it just seems like a recipe for accidentally exposing things you meant to keep private.

>> * You don't explicitly mention this, but I assume mutating methods work and mutate `self`?
> 
> Mutating methods are something I didn’t think about carefully yet.  Thanks for pointing that out!  But generally, yes a forwarding implementation of a mutating method would need to mutate the forwardee which is part of self, thus mutating self.

Well, as long as they're thought about at some point!

-- 
Brent Royal-Gordon
Architechies



More information about the swift-evolution mailing list