> On 3 Nov 2016, at 19:23, Nevin Brackett-Rozinsky 
> <[email protected]> wrote:
> For the motivating examples, there is already a much cleaner solution:
> (foo as? B)?.someMethodSpecificToB()

Eh, kinda; you've taken an example showing branching and boiled it down into a 
single line; what if my intention was to call multiple B specific methods on 
foo? I think you've seen a simple example and tried to simplify it further, but 
I suppose I could put another method call in it to clarify.

> Aside from any implementation concerns, this proposal substantially increases 
> the cognitive load on developers.

I'm not so sure of this; the type is never going to be wider than what you 
originally set, so at the very least you can do whatever its original explicit 
type or inferred type would let you do, the main difference is that if you do 
something that makes no sense anymore then the type-checker can inform you of 
this. Otherwise if you know you've narrowed the type, then you can do things 
with that narrowed type and either the type-checker will allow it, or inform 
you that it's not possible, thus warning you that your conditional(s) etc. 
don't work as intended.

Really the burden is no different to the techniques that already exist; if 
you've tested a value with `is` then you know what the type is within that 
block of code, this is just ensuring that the type checker also knows it. 
Likewise with an optional, if you test that it's not nil then you know it's 
not, and so should the type-checker.

I should probably put more under motivation about why this feature works for 
the example cases given, and what the impact is on a larger scale; as the 
narrowing has the potential to pick up a bunch of little errors that standard 
type-checking alone may not, the trick is coming up with the example code to 
show it that isn't enormous, as I'm trying to avoid too much complexity in the 
Motivation section so I can build up the examples; maybe I'll add an 
"Advantages" section further down to detail more of what can be done *after* 
demonstrating the feature in stages, rather than trying to do it at the start.

> the proposed “solution” has the same concurrency failure, but hides it even 
> worse because the force-unwrap operator doesn’t even appear in the code:
> 
> var foo:A? = A()
> if foo != nil {
>    foo.someMethod()          // Bad news!
>    foo.someMutatingMethod()  // Bad news!
> }

Actually the issue is visible in the code; it's the conditional. If you can't 
trust `foo` not to change then testing its value and then acting upon the 
result without some form of local copy or locking is the part that explicitly 
isn't thread safe.

I'm not really sure of the need to focus on thread safety issues here as I'm 
not sure they're unique to this feature, or even to optionals; anything that is 
part of a shared class and thus potentially shared with other threads is 
unsafe, whether it's optional or not. While optionals might produce errors if 
force unwrapped and nil, they just as easily might not but end up with 
inconsistent values instead, so I'm not sure having the force unwrap operators 
actually makes you any safer; put another way, if you're relying on force 
unwrapping to catch thread safety issues then I'm not sure that's a good 
strategy, as it can only detect the issues at runtime, and only if the exact 
conditions necessary actually occur to trigger the failure.

I don't know what's planned for Swift's native concurrency support, but 
personally I'm hoping we might get a feature of classes that requires them 
(and/or their methods) to be marked as explicitly safe (except where it can be 
inferred), producing warnings otherwise, forcing developers to consider if 
their type/method has been properly reviewed for thread safety. Thread safety 
after all really is specific to classes, which are mostly discouraged in Swift 
anyway, so it makes sense to focus efforts towards making classes safer, and 
that you're handling your struct copies efficiently.

>> On Thu, Nov 3, 2016 at 1:04 PM, Haravikk via swift-evolution 
>> <[email protected] <mailto:[email protected]>> wrote:
>> 
>> To avoid hijacking the guard let x = x thread entirely I've decided to try 
>> to write up a proposal on type narrowing in Swift.
>> Please give your feedback on the functionality proposed, as well as the 
>> clarity of the proposal/examples themselves; I've tried to keep it 
>> straightforward, but I do tend towards being overly verbose, I've always 
>> tried to have the examples build upon one another to show how it all stacks 
>> up.
>> 
>> 
>> 
>> Type Narrowing
>> 
>> Proposal: SE-NNNN 
>> <https://github.com/Haravikk/swift-evolution/blob/master/proposals/NNNN-type-narrowing.md>
>> Author: Haravikk <https://github.com/haravikk>
>> Status: Awaiting review
>> Review manager: TBD
>>  
>> <https://github.com/Haravikk/swift-evolution/tree/master/proposals#introduction>Introduction
>> 
>> This proposal is to introduce type-narrowing to Swift, enabling the 
>> type-checker to automatically infer a narrower type from context such as 
>> conditionals.
>> 
>> Swift-evolution thread: Discussion thread topic for that proposal 
>> <http://news.gmane.org/gmane.comp.lang.swift.evolution>
>>  
>> <https://github.com/Haravikk/swift-evolution/tree/master/proposals#motivation>Motivation
>> 
>> Currently in Swift there are various pieces of boilerplate required in order 
>> to manually narrow types. The most obvious is in the case of polymorphism:
>> 
>> let foo:A = B() // B extends A
>> if foo is B {
>>     (foo as B).someMethodSpecificToB()
>> }
>> But also in the case of unwrapping of optionals:
>> 
>> var foo:A? = A()
>> if var foo = foo { // foo is now unwrapped and shadowed
>>     foo.someMethod()
>>     foo!.someMutatingMethod() // Can't be done
>> }
>>  
>> <https://github.com/Haravikk/swift-evolution/tree/master/proposals#proposed-solution>Proposed
>>  solution
>> 
>> The proposed solution to the boiler-plate is to introduce type-narrowing, 
>> essentially a finer grained knowledge of type based upon context. Thus as 
>> any contextual clue indicating a more or less specific type are encountered, 
>> the type of the variable will reflect this from that point onwards.
>> 
>>  
>> <https://github.com/Haravikk/swift-evolution/tree/master/proposals#detailed-design>Detailed
>>  design
>> 
>> The concept of type-narrowing would essentially treat all variables as 
>> having not just a single type, but instead as having a stack of increasingly 
>> specific (narrow) types.
>> 
>> Whenever a contextual clue such as a conditional is encountered, the type 
>> checker will infer whether this narrows the type, and add the new narrow 
>> type to the stack from that point onwards. Whenever the type widens again 
>> narrower types are popped from the stack.
>> 
>> Here are the above examples re-written to take advantage of type-narrowing:
>> 
>> let foo:A = B() // B extends A
>> if foo is B { // B is added to foo's type stack
>>     foo.someMethodSpecificToB()
>> }
>> // B is popped from foo's type stack
>> var foo:A? = A()
>> if foo != nil { // Optional<A>.some is added to foo's type stack
>>    foo.someMethod()
>>    foo.someMutatingMethod() // Can modify mutable original
>> }
>> // Optional<A>.some is popped from foo's type stack
>>  
>> <https://github.com/Haravikk/swift-evolution/tree/master/proposals#enum-types>Enum
>>  Types
>> 
>> As seen in the simple optional example, to implement optional support each 
>> case in an enum is considered be a unique sub-type of the enum itself, thus 
>> allowing narrowing to nil (.none) and non-nil (.some) types.
>> 
>> This behaviour actually enables some other useful behaviours, specifically, 
>> if a value is known to be either nil or non-nil then the need to unwrap or 
>> force unwrap the value can be eliminated entirely, with the compiler able to 
>> produce errors if these are used incorrectly, for example:
>> 
>> var foo:A? = A()
>> foo.someMethod() // A is non-nil, no operators required!
>> foo = nil
>> foo!.someMethod() // Error: foo is always nil at this point
>> However, unwrapping of the value is only possible if the case contains 
>> either no value at all, or contains a single value able to satisfy the 
>> variable's original type requirements. In other words, the value stored in 
>> Optional<A>.some satisfies the type requirements of var foo:A?, thus it is 
>> implicitly unwrapped for use. For general enums this likely means no cases 
>> are implicitly unwrapped unless using a type of Any.
>> 
>>  
>> <https://github.com/Haravikk/swift-evolution/tree/master/proposals#type-widening>Type
>>  Widening
>> 
>> In some cases a type may be narrowed, only to be used in a way that makes no 
>> sense for the narrowed type. In cases such as these the operation is tested 
>> against each type in the stack to determine whether the type must instead be 
>> widened. If a widened type is found it is selected (with re-narrowing where 
>> possible) otherwise an error is produced as normal.
>> 
>> For example:
>> 
>> let foo:A? = A()
>> if (foo != nil) { // Type of foo is Optional<A>.some
>>     foo.someMethod()
>>     foo = nil // Type of foo is widened to Optional<A>, then re-narrowed to 
>> Optional<A>.none
>> } // Type of foo is Optional<A>.none
>> foo.someMethod() // Error: foo is always nil at this point
>>  
>> <https://github.com/Haravikk/swift-evolution/tree/master/proposals#multiple-conditions-and-branching>Multiple
>>  Conditions and Branching
>> 
>> When dealing with complex conditionals or branches, all paths must agree on 
>> a common type for narrowing to occur. For example:
>> 
>> let foo:A? = B() // B extends A
>> let bar:C = C() // C extends B
>> 
>> if (foo != nil) || (foo == bar) { // Optional<A>.some is added to foo's type 
>> stack
>>     if foo is B { // Optional<B>.some is added to foo's type stack
>>         foo.someMethodSpecificToB()
>>     } // Optional<B>.some is popped from foo's type stack
>>     foo = nil // Type of foo is re-narrowed as Optional<A>.none
>> } // Type of foo is Optional<A>.none in all branches
>> foo.someMethod() // Error: foo is always nil at this point
>> Here we can see that the extra condition (foo == bar) does not prevent 
>> type-narrowing, as the variable bar cannot be nil so both conditions require 
>> a type of Optional<A>.some as a minimum.
>> 
>> In this example foo is also nil at the end of both branches, thus its type 
>> can remain narrowed past this point.
>> 
>>  
>> <https://github.com/Haravikk/swift-evolution/tree/master/proposals#context-triggers>Context
>>  Triggers
>> 
>> Trigger      Impact
>> as   Explicitly narrows a type with as! failing and as? narrowing to Type? 
>> instead when this is not possible.
>> is   Anywhere a type is tested will allow the type-checker to infer the new 
>> type if there was a match (and other conditions agree).
>> case Any form of exhaustive test on an enum type allows it to be narrowed 
>> either to that case or the opposite, e.g- foo != nil eliminates .none, 
>> leaving only .some as the type, which can then be implicitly unwrapped (see 
>> Enum Types above).
>> =    Assigning a value to a type will either narrow it if the new value is a 
>> sub-type, or will trigger widening to find a new common type, before 
>> attempting to re-narrow from there.
>> There may be other triggers that should be considered.
>> 
>>  
>> <https://github.com/Haravikk/swift-evolution/tree/master/proposals#impact-on-existing-code>Impact
>>  on existing code
>> 
>> Although this change is technically additive, it will impact any code in 
>> which there are currently errors that type-narrowing would have detected; 
>> for example, attempting to manipulate a predictably nil value.
>> 
>>  
>> <https://github.com/Haravikk/swift-evolution/tree/master/proposals#alternatives-considered>Alternatives
>>  considered
>> 
>> One of the main advantages of type-narrowing is that it functions as an 
>> alternative to other features. This includes alternative syntax for 
>> shadowing/unwrapping of optionals, in which case type-narrowing allows an 
>> optional to be implicitly unwrapped simply by testing it, and without the 
>> need to introduce any new syntax.
>> 
>> _______________________________________________
>> swift-evolution mailing list
>> [email protected] <mailto:[email protected]>
>> https://lists.swift.org/mailman/listinfo/swift-evolution 
>> <https://lists.swift.org/mailman/listinfo/swift-evolution>
>> 
>> 
> 
_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to