> On Dec 29, 2015, at 6:10 PM, Brent Royal-Gordon <br...@architechies.com> 
> wrote:
> 
>>> * 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.

I gave this enough consideration to be leaning pretty strongly in the direction 
that protocols are the best way to do this.  

Doing this properly for the concrete interface would require an `InvariantSelf` 
type that could be used in any method signature.  It would also need to be 
actually used correctly in practice by types that were forwarded to.  The 
distinction is pretty subtle when you’re dealing with a concrete interface and 
my instinct is that people would get it wrong a lot of the time.  

Because protocols are inherently generic it is a little more straightforward to 
think about when you mean `Self` and when you mean a concrete type.

The good news is that you can declare a protocol containing the full interface 
of a concrete type if you really want or need to and use that for forwarding.  
The advantage of requiring a protocol here is that it requires you to 
consciously think about whether each parameter and return type is intended to 
be concrete or an abstract Self.  It also allows you to properly forward an 
interface even if the original author did not consider these issues when 
implementing the type.

In the following example, should the other parameter and the return type be 
`Self ` or `Double`?  It is not possible to know unless there is a protocol 
that declares foo.

extension Double {
    func foo(other: Double) -> Double {
        return self
    }
}

> 
> 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.

I can see how it might be desirable to forward to a type you receive as a 
generic parameter.  However, you would need to constrain that type to a 
protocol(s) in order to actually do anything useful with it.  That same 
protocol(s) could also be used in the forwarding declaration.

If you want is to be able to forward a set of methods that is determined by the 
generic parameter, that just isn’t going to be possible.  At least not without 
significant changes to other parts of the language providing capabilities that 
would allow you to implement something like that manually.

> 
>>> * 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.

Right, I am generally leaning pretty strongly towards changing the proposal to 
use a solution similar to what you suggest.

> 
>> 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.

I was actually indicating an advantage of the approach you suggested because it 
provides a solution to that problem. :)

> 
>>> * 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 would guess you are right about this being something that is never or almost 
never the right thing to do.  Maybe a solution that actually prevents you from 
doing this would be a better one for that reason. 

> 
> 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?

I hope not! :)  I hadn’t looked too hard for an example where it would be the 
right thing to do and I think you are right that such an example would be 
pretty hard to come by.  Thanks for pushing back on this one! :)

> 
>>> * 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.

Think of a state or strategy pattern.  You might have an internal protocol with 
several implementations representing the various states or strategies.  You 
forward to a private member that is an existential of the protocol type.  The 
synthesized forwarding methods represent the public interface.  However, the 
protocol itself is not public as it is an implementation detail and the 
forwarder doesn’t even conform to the protocol.  The protocol is strictly used 
as an implementation detail of the public interface of your type.

I honestly don’t understand the concern about this proposal accidentally 
exposing things you meant to keep private.  You have full control over access 
control of everything, including the synthesized methods.  The proposal doesn’t 
do anything you couldn’t already do manually and it makes it very easy to keep 
them private if desired.  If somebody wanted to manually write a forwarder that 
declares forwarding methods public while forwarding to internal methods on the 
forwardee they can do that already today!  

IMO the language needs to provide the tools to specify your intent clearly and 
easily and offer sensible defaults, but it can’t do more than that.  I believe 
this proposal falls in line with that principle and also inline with how access 
control already works in the language today (the default for synthesized 
members matches the default for manual member declarations).

I am very concerned about not exposing details that shouldn’t be exposed and 
that is one of the big drawbacks of the approach to protocol forwarding that 
Kevin Ballard shared.

> 
>>> * 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!

Yes, I’m glad you brought them up!

> 
> -- 
> Brent Royal-Gordon
> Architechies
> 

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

Reply via email to