[Proposal] Type Narrowing

If I correctly understand the proposal, I'm -1 on this.

(1) You can define different methods with the same name on T and
Optional<T> (description is such an example). Then what does this do?

// someMethod is defined both for T and T?
// var foo: T?
if foo != nil {
    foo.someMethod()
}

I say there is a clear expectation that foo.someMethod() should call the
method of T?, even inside the if block, since this is how the dot works.
However, according to the proposal it will call another method (or become
an error?).

I think the languages that use optional narrowing are the ones where T? is
not a separate type, so that it cannot have its own methods.

(2) Should this work?

// compilcatedStuff is a method of T
// class A { var foo: T? }

if foo != nil {
    foo.compilcatedStuff()
    foo.compilcatedStuff()
    foo.compilcatedStuff()
}

Suppose the compiler doesn't have enough information about compilcatedStuff
to know what happens inside. Then it's possible that foo.compilcatedStuff
will actually change foo (for example, foo could be a relationship and
compilcatedStuff may be deleting the relationship). So, what is the
suggestion for this example? Perhaps

if foo != nil {
    foo.compilcatedStuff()
    foo?.compilcatedStuff()
    foo?.compilcatedStuff()
}

or some other choice?

···

On Tue, Nov 8, 2016 at 9:44 AM Haravikk via swift-evolution < swift-evolution@swift.org> wrote:

On 7 Nov 2016, at 19:31, Charlie Monroe <charlie@charliemonroe.net> wrote:
There are two cases:

if foo != nil {
    foo!.doSomething()
}

Currently, accessing a non-optional value with ! produces an error:

let foo = Bar()
foo!.doSomething() // ERROR

Second:

if foo != nil {
    // Using ? to be extra cautious, if foo is var
    foo?.doSomething()
}

This again currently produces an error:

let foo = Bar()
foo?.doSomething() // ERROR

Which is generally, what would semantically happen - the variable would
loose it optionality. Or am I wrong?

I probably haven't clarified well enough but under type-narrowing these
would be warnings rather than errors; i.e- the general type of foo is still
Optional, the type-checker merely knows that it can't be nil at that point,
so would inform you that the ? or ! are unnecessary.

This is what I was trying to get at in the type-widening section;
basically, if you have a variable whose type is narrowed, but do something
that makes no sense for the narrowed type, then the type is widened until
either a match is found or it can't go any wider (producing an error as
normal).

So in your examples foo is Optional<Bar>.some, as a result the ? and !
operators make no sense, so the type is widened back to Optional<Bar> where
it does make sense and the code compiles, but a warning is produced to
inform you that you don't need to use those operators.

On 7 Nov 2016, at 19:31, Charlie Monroe <charlie@charliemonroe.net> wrote:
I agree that designing a language around the compiler speed is wrong, but
I believe designing the language without taking it into account is just as
wrong. It's not worth designing features that would make the compilation so
slow it would render the language unusable.

Note that I have only very limited experience with compiler
implementation, I've only made a few minor things with Clang a few years
back, so please feel free to correct me.

I'm not that familiar with the actual architecture either, but narrowing
*should* be fairly simple; basically any time the compiler hits a condition
or statement defined as a narrowing trigger, it pops the new narrower type
onto a stack of types for that variable (in that branch). Now whenever the
compiler reaches another statement for that variable (method call etc.) it
resolves it first against the narrowest type, otherwise it goes up the
stack (widening) till it finds a match or fails.

When a branch closes with a stack of types, the compiler will compare to
other branches to see which type is the narrowest that they have in common;
this is actually fairly simple (shorten the stack for each branch to the
length of the shortest stack, then discard elements until the current one
is a match for all branches, thus you now know what the narrowest type is
past that point).

So, for types that never narrow there should be no speed difference, while
for narrowed types there shouldn't be much of a difference, as these stacks
of types shouldn't get very large in most cases (I'd expect anything more
than three to be pretty rare).
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

(1) You can define different methods with the same name on T and Optional<T> (description is such an example). Then what does this do?

// someMethod is defined both for T and T?
// var foo: T?
if foo != nil {
    foo.someMethod()
}

I say there is a clear expectation that foo.someMethod() should call the method of T?, even inside the if block, since this is how the dot works. However, according to the proposal it will call another method (or become an error?).

I think the languages that use optional narrowing are the ones where T? is not a separate type, so that it cannot have its own methods.

Hmm, that is definitely a wrinkle in the plan; it's very much an edge case (someone defining something on optionals that they probably shouldn't) but not an easy one to resolve. I suppose I'll have to amend the proposal to suggest type widening in that case, such that you would need to use ! or ? as normal to specify the unwrapped value. The tricky part is that it means the value would have to be widened from the start, otherwise you'd be accessing the value in two different ways in the same block of code, which would mean that narrowing would need to be blocked if there's an incompatible statement further down… ugh, perhaps a keyword will be necessary then? I was really hoping to avoid having to add one though.

(2) Should this work?

// compilcatedStuff is a method of T
// class A { var foo: T? }

if foo != nil {
    foo.compilcatedStuff()
    foo.compilcatedStuff()
    foo.compilcatedStuff()
}

Suppose the compiler doesn't have enough information about compilcatedStuff to know what happens inside. Then it's possible that foo.compilcatedStuff will actually change foo (for example, foo could be a relationship and compilcatedStuff may be deleting the relationship). So, what is the suggestion for this example? Perhaps

if foo != nil {
    foo.compilcatedStuff()
    foo?.compilcatedStuff()
    foo?.compilcatedStuff()
}

or some other choice?

What do you imagine being inside .complicatedStuff()? It shouldn't be possible for it to change foo to nil, as even if .complicatedStuff() reassigned this, it would be doing so as type T.

···

On 8 Nov 2016, at 12:22, ilya <ilya.nikokoshev@gmail.com> wrote:

On 9 Nov 2016, at 06:51, David Hart <david@hartbit.com> wrote:

On 3 Nov 2016, at 20:23, Nevin Brackett-Rozinsky via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

This looks like a lot of complexity for very little gain.

Aside from any implementation concerns, this proposal substantially increases the cognitive load on developers. To figure out what a piece of code means, someone reading it will have to mentally keep track of a “type stack” for every variable. That is the opposite of “clarity at the point of use”.

Very well said. I think this is perhaps the number one complaint I have about the proposal.

Did you see my response to this? There should be no particular cognitive load increase; think of the feature like type inference, the idea here is that the type-checker is gaining the same knowledge that you already have, i.e- you know something isn't nil, so the type-checker should too.

What do you imagine being inside .complicatedStuff()? It shouldn't be

possible for it to change foo to nil, as even if .complicatedStuff()
reassigned this, it would be doing so as type T.

Why do you think so? All objects can be changed in any method. Let's rename
foo to pet and complicatedStuff to setFree to give a specific example:

class Pet {
    var owner: Person?
    func setFree()
    {
       // Maintain the relationship correctness.
        owner?.pet = nil
        owner = nil
    }
}

class Person {

var pet: Pet?

func ... {
  if pet != nil {
    pet.setFree() // this sets pet = nil
    pet.setFree() // ???? what does this do ????
    pet.setFree()
  }
}
}

···

On Wed, Nov 9, 2016 at 10:51 AM Haravikk <swift-evolution@haravikk.me> wrote:

On 8 Nov 2016, at 12:22, ilya <ilya.nikokoshev@gmail.com> wrote:

(1) You can define different methods with the same name on T and
Optional<T> (description is such an example). Then what does this do?

// someMethod is defined both for T and T?
// var foo: T?
if foo != nil {
    foo.someMethod()
}

I say there is a clear expectation that foo.someMethod() should call the
method of T?, even inside the if block, since this is how the dot works.
However, according to the proposal it will call another method (or become
an error?).

I think the languages that use optional narrowing are the ones where T? is
not a separate type, so that it cannot have its own methods.

Hmm, that is definitely a wrinkle in the plan; it's very much an edge case
(someone defining something on optionals that they probably shouldn't) but
not an easy one to resolve. I suppose I'll have to amend the proposal to
suggest type widening in that case, such that you would need to use ! or ?
as normal to specify the unwrapped value. The tricky part is that it means
the value would have to be widened from the start, otherwise you'd be
accessing the value in two different ways in the same block of code, which
would mean that narrowing would need to be blocked if there's an
incompatible statement further down… ugh, perhaps a keyword will be
necessary then? I was really hoping to avoid having to add one though.

(2) Should this work?

// compilcatedStuff is a method of T
// class A { var foo: T? }

if foo != nil {
    foo.compilcatedStuff()
    foo.compilcatedStuff()
    foo.compilcatedStuff()
}

Suppose the compiler doesn't have enough information about
compilcatedStuff to know what happens inside. Then it's possible
that foo.compilcatedStuff will actually change foo (for example, foo could
be a relationship and compilcatedStuff may be deleting the relationship).
So, what is the suggestion for this example? Perhaps

if foo != nil {
    foo.compilcatedStuff()
    foo?.compilcatedStuff()
    foo?.compilcatedStuff()
}

or some other choice?

What do you imagine being inside .complicatedStuff()? It shouldn't be
possible for it to change foo to nil, as even if .complicatedStuff()
reassigned this, it would be doing so as type T.

On 9 Nov 2016, at 06:51, David Hart <david@hartbit.com> wrote:

On 3 Nov 2016, at 20:23, Nevin Brackett-Rozinsky via swift-evolution < > swift-evolution@swift.org> wrote:

This looks like a lot of complexity for very little gain.

Aside from any implementation concerns, this proposal substantially
increases the cognitive load on developers. To figure out what a piece of
code means, someone reading it will have to mentally keep track of a “type
stack” for every variable. That is the opposite of “clarity at the point of
use”.

Very well said. I think this is perhaps the number one complaint I have
about the proposal.

Did you see my response to this? There should be no particular cognitive
load increase; think of the feature like type inference, the idea here is
that the type-checker is gaining the same knowledge that you already have,
i.e- you know something isn't nil, so the type-checker should too.

Locally in short routines yes.
But in larger modules and non-local this does not apply.
Imo it should always be possible to look at a type declaration and -from that- derive all necessary knowledge about the type.
Besides when using a narrowed type as a parameter for an optional it must be automatically be widened again? hence you would mentally keep track of the status of that variable.

Regards,
Rien

Site: http://balancingrock.nl
Blog: http://swiftrien.blogspot.com
Github: Swiftrien (Rien) · GitHub
Project: http://swiftfire.nl

···

On 09 Nov 2016, at 10:51, Haravikk via swift-evolution <swift-evolution@swift.org> wrote:

On 8 Nov 2016, at 12:22, ilya <ilya.nikokoshev@gmail.com> wrote:

(1) You can define different methods with the same name on T and Optional<T> (description is such an example). Then what does this do?

// someMethod is defined both for T and T?
// var foo: T?
if foo != nil {
    foo.someMethod()
}

I say there is a clear expectation that foo.someMethod() should call the method of T?, even inside the if block, since this is how the dot works. However, according to the proposal it will call another method (or become an error?).

I think the languages that use optional narrowing are the ones where T? is not a separate type, so that it cannot have its own methods.

Hmm, that is definitely a wrinkle in the plan; it's very much an edge case (someone defining something on optionals that they probably shouldn't) but not an easy one to resolve. I suppose I'll have to amend the proposal to suggest type widening in that case, such that you would need to use ! or ? as normal to specify the unwrapped value. The tricky part is that it means the value would have to be widened from the start, otherwise you'd be accessing the value in two different ways in the same block of code, which would mean that narrowing would need to be blocked if there's an incompatible statement further down… ugh, perhaps a keyword will be necessary then? I was really hoping to avoid having to add one though.

(2) Should this work?

// compilcatedStuff is a method of T
// class A { var foo: T? }

if foo != nil {
    foo.compilcatedStuff()
    foo.compilcatedStuff()
    foo.compilcatedStuff()
}

Suppose the compiler doesn't have enough information about compilcatedStuff to know what happens inside. Then it's possible that foo.compilcatedStuff will actually change foo (for example, foo could be a relationship and compilcatedStuff may be deleting the relationship). So, what is the suggestion for this example? Perhaps

if foo != nil {
    foo.compilcatedStuff()
    foo?.compilcatedStuff()
    foo?.compilcatedStuff()
}

or some other choice?

What do you imagine being inside .complicatedStuff()? It shouldn't be possible for it to change foo to nil, as even if .complicatedStuff() reassigned this, it would be doing so as type T.

On 9 Nov 2016, at 06:51, David Hart <david@hartbit.com> wrote:

On 3 Nov 2016, at 20:23, Nevin Brackett-Rozinsky via swift-evolution <swift-evolution@swift.org> wrote:

This looks like a lot of complexity for very little gain.

Aside from any implementation concerns, this proposal substantially increases the cognitive load on developers. To figure out what a piece of code means, someone reading it will have to mentally keep track of a “type stack” for every variable. That is the opposite of “clarity at the point of use”.

Very well said. I think this is perhaps the number one complaint I have about the proposal.

Did you see my response to this? There should be no particular cognitive load increase; think of the feature like type inference, the idea here is that the type-checker is gaining the same knowledge that you already have, i.e- you know something isn't nil, so the type-checker should too.

Ah, this comes back to the issue of class concurrency problems; the solution to this is that class references won't be narrowed, but the type-narrowing checks will still note cases that *could* have been narrowed, so that they can produce concurrency errors at runtime rather than the more generic error.

I just realised I never did provide a link to the working copy of the proposal, you can view some of the updates I've made here, which includes a note on classes and concurrency:
https://github.com/Haravikk/swift-evolution/blob/master/proposals/NNNN-type-narrowing.md

Though there is still a lot to do, especially if I'm going to have to change the proposal to use a keyword instead to avoid the Optional<T> to T method shadowing problem.

So yeah, in other words I'm assuming narrowing only on structs, as these are the only types where it is possible for the type-checker to be certain; though this could be extended to classes in future if we gain some attribute for indicating when they are "safe" (e.g- changed through copy-on-write, thus safe for narrowing).

···

On 9 Nov 2016, at 10:00, ilya <ilya.nikokoshev@gmail.com> wrote:

> What do you imagine being inside .complicatedStuff()? It shouldn't be possible for it to change foo to nil, as even if .complicatedStuff() reassigned this, it would be doing so as type T.

Why do you think so? All objects can be changed in any method. Let's rename foo to pet and complicatedStuff to setFree to give a specific example:

class Pet {
    var owner: Person?
    func setFree()
    {
       // Maintain the relationship correctness.
        owner?.pet = nil
        owner = nil
    }
}

class Person {

var pet: Pet?

func ... {
  if pet != nil {
    pet.setFree() // this sets pet = nil
    pet.setFree() // ???? what does this do ????
    pet.setFree()
  }
}
}

This looks like a lot of complexity for very little gain.

Aside from any implementation concerns, this proposal substantially increases the cognitive load on developers. To figure out what a piece of code means, someone reading it will have to mentally keep track of a “type stack” for every variable. That is the opposite of “clarity at the point of use”.

Very well said. I think this is perhaps the number one complaint I have about the proposal.

Did you see my response to this? There should be no particular cognitive load increase; think of the feature like type inference, the idea here is that the type-checker is gaining the same knowledge that you already have, i.e- you know something isn't nil, so the type-checker should too.

Locally in short routines yes.
But in larger modules and non-local this does not apply.

I'm not sure what you mean; type-narrowing doesn't occur across scopes, ultimately you will always have some starting type where the variable was declared as a property, function argument or local variable, and it is narrow only where it is used, and the narrowing only occurs within that scope for as long as it is relevant.

In other words, the narrowing is always local. If you know your method takes an optional string for example then you know that that variable is still an optional string throughout that method, type-narrowing just helps to guarantee that it is nil or non-nil where you expect it to be.

Imo it should always be possible to look at a type declaration and -from that- derive all necessary knowledge about the type.

As I say, narrowing never changes the type; if you have a variable with a declared type of Foo, that is narrowed to Bar, then it is because Bar extends Foo and thus is compatible with it, giving you access to any additional methods of Bar without interfering with what you know of Foo.

Besides when using a narrowed type as a parameter for an optional it must be automatically be widened again? hence you would mentally keep track of the status of that variable.

I'm not sure I follow this question; do you mean something like this:

  func someMethod(foo:Foo?) {
    var bar:Foo? = nil // bar is Optional<Foo>.none
    if (foo != nil) { // foo is Optional<Foo>.some
      bar = foo // both foo and bar are Optional<Foo>.some
    }
    // both foo and bar are Optional<Foo> (alternative branch places no mutual guarantee on type)
    if bar != nil { // bar is Optional<Foo>.some
      bar.someMutatingMethod()
    }
  }

But these are all things that the developer knows; bar can't be non-nil until a value for it is set, foo is definitely non-nil within the block etc. The only difference here is that instead of the developer having to use ! unnecessarily (or risk making a mistake) they can just use their knowledge of what it is to interact directly, as the type-checker will now also know the same thing.

However, with the Optional<T> to T method shadowing issue it seems we probably will need to require a keyword, or at the very least restrict automatic narrowing to polymorphism. For optionals the code will have to look something like this:

  func someMethod(foo:Foo?) {
    var bar:Foo? = nil
    if unwrap foo {
      bar = foo
    }

    if unwrap bar {
      bar.someMutatingMethod()
    }
  }

The difference here is that unlike shadowing (if let foo = foo), if foo were mutable then it could still be mutated directly, no need to do it with force unwrapping. Of course in these simple examples you could just use the question mark operator instead, but pretend we're doing more than one thing per conditional :wink:

···

On 9 Nov 2016, at 12:19, Rien <Rien@balancingrock.nl> wrote:

On 9 Nov 2016, at 06:51, David Hart <david@hartbit.com> wrote:

On 3 Nov 2016, at 20:23, Nevin Brackett-Rozinsky via swift-evolution <swift-evolution@swift.org> wrote:

The difference here is that unlike shadowing (if let foo = foo), if foo

were mutable then it could still be mutated directly, no need to do it with
force unwrapping.

FWIW, there is no need for force unwrapping in any case.

var bar:Foo? = nil

if let real_foo = foo {
bar = real_foo
}

if var bar_copy = bar {
bar_copy.someMutatingMethod()
                        bar = bar_copy
}

···

On Wed, Nov 9, 2016 at 3:28 PM Haravikk <swift-evolution@haravikk.me> wrote:

On 9 Nov 2016, at 12:19, Rien <Rien@balancingrock.nl> wrote:

On 9 Nov 2016, at 06:51, David Hart <david@hartbit.com> wrote:

On 3 Nov 2016, at 20:23, Nevin Brackett-Rozinsky via swift-evolution < > swift-evolution@swift.org> wrote:

This looks like a lot of complexity for very little gain.

Aside from any implementation concerns, this proposal substantially
increases the cognitive load on developers. To figure out what a piece of
code means, someone reading it will have to mentally keep track of a “type
stack” for every variable. That is the opposite of “clarity at the point of
use”.

Very well said. I think this is perhaps the number one complaint I have
about the proposal.

Did you see my response to this? There should be no particular cognitive
load increase; think of the feature like type inference, the idea here is
that the type-checker is gaining the same knowledge that you already have,
i.e- you know something isn't nil, so the type-checker should too.

Locally in short routines yes.
But in larger modules and non-local this does not apply.

I'm not sure what you mean; type-narrowing doesn't occur across scopes,
ultimately you will always have some starting type where the variable was
declared as a property, function argument or local variable, and it is
narrow only where it is used, and the narrowing only occurs within that
scope for as long as it is relevant.

In other words, the narrowing is always local. If you know your method
takes an optional string for example then you know that that variable is
still an optional string throughout that method, type-narrowing just helps
to guarantee that it is nil or non-nil where you expect it to be.

Imo it should always be possible to look at a type declaration and -from
that- derive all necessary knowledge about the type.

As I say, narrowing never changes the type; if you have a variable with a
declared type of Foo, that is narrowed to Bar, then it is because Bar
extends Foo and thus is compatible with it, giving you access to any
additional methods of Bar without interfering with what you know of Foo.

Besides when using a narrowed type as a parameter for an optional it must
be automatically be widened again? hence you would mentally keep track of
the status of that variable.

I'm not sure I follow this question; do you mean something like this:

func someMethod(foo:Foo?) {
var bar:Foo? = nil // bar is Optional<Foo>.none
if (foo != nil) { // foo is Optional<Foo>.some
bar = foo // both foo and bar are Optional<Foo>.some
}
// both foo and bar are Optional<Foo> (alternative branch places no mutual
guarantee on type)
if bar != nil { // bar is Optional<Foo>.some
bar.someMutatingMethod()
}
}

But these are all things that the developer knows; bar can't be non-nil
until a value for it is set, foo is definitely non-nil within the block
etc. The only difference here is that instead of the developer having to
use ! unnecessarily (or risk making a mistake) they can just use their
knowledge of what it is to interact directly, as the type-checker will now
also know the same thing.

However, with the Optional<T> to T method shadowing issue it seems we
probably will need to require a keyword, or at the very least restrict
automatic narrowing to polymorphism. For optionals the code will have to
look something like this:

func someMethod(foo:Foo?) {
var bar:Foo? = nil
if unwrap foo {
bar = foo
}

if unwrap bar {
bar.someMutatingMethod()
}
}

The difference here is that unlike shadowing (if let foo = foo), if foo
were mutable then it could still be mutated directly, no need to do it with
force unwrapping. Of course in these simple examples you could just use the
question mark operator instead, but pretend we're doing more than one thing
per conditional :wink:

I get narrowing, and have admittedly often wished for it myself. Hence I have kept out of this discussion.
But the argument for cognitive overload is imo convincing.

When we create examples, there is almost no (or even negative) cognitive load associated with narrowing.
However when you get a piece of code in front of you with 10+ optionals, 5 if-statements deep with loops intermixed, and you have to find where the nil-error occurs?
It would drive me nuts… especially in these kind of cases:

if foo != nil {
  …
  foo = newFoo()
  …
  if foo != nil {
  …
  }
}

Mind you, I am against automagic narrowing because I like things to be simple, but it is not a make or break deal.

Btw, I think that using a keyword makes it more palatable.

if unwrap foo {
  …
  foo = newFoo()
  …
  if unwrap foo {
  …
  }
}

Now it is clear that something is done to foo, its no longer a compare with side effects but an operation on foo itself.

Regards,
Rien

Site: http://balancingrock.nl
Blog: http://swiftrien.blogspot.com
Github: Swiftrien (Rien) · GitHub
Project: http://swiftfire.nl

···

On 09 Nov 2016, at 15:28, Haravikk <swift-evolution@haravikk.me> wrote:

On 9 Nov 2016, at 12:19, Rien <Rien@balancingrock.nl> wrote:

On 9 Nov 2016, at 06:51, David Hart <david@hartbit.com> wrote:

On 3 Nov 2016, at 20:23, Nevin Brackett-Rozinsky via swift-evolution <swift-evolution@swift.org> wrote:

This looks like a lot of complexity for very little gain.

Aside from any implementation concerns, this proposal substantially increases the cognitive load on developers. To figure out what a piece of code means, someone reading it will have to mentally keep track of a “type stack” for every variable. That is the opposite of “clarity at the point of use”.

Very well said. I think this is perhaps the number one complaint I have about the proposal.

Did you see my response to this? There should be no particular cognitive load increase; think of the feature like type inference, the idea here is that the type-checker is gaining the same knowledge that you already have, i.e- you know something isn't nil, so the type-checker should too.

Locally in short routines yes.
But in larger modules and non-local this does not apply.

I'm not sure what you mean; type-narrowing doesn't occur across scopes, ultimately you will always have some starting type where the variable was declared as a property, function argument or local variable, and it is narrow only where it is used, and the narrowing only occurs within that scope for as long as it is relevant.

In other words, the narrowing is always local. If you know your method takes an optional string for example then you know that that variable is still an optional string throughout that method, type-narrowing just helps to guarantee that it is nil or non-nil where you expect it to be.

Imo it should always be possible to look at a type declaration and -from that- derive all necessary knowledge about the type.

As I say, narrowing never changes the type; if you have a variable with a declared type of Foo, that is narrowed to Bar, then it is because Bar extends Foo and thus is compatible with it, giving you access to any additional methods of Bar without interfering with what you know of Foo.

Besides when using a narrowed type as a parameter for an optional it must be automatically be widened again? hence you would mentally keep track of the status of that variable.

I'm not sure I follow this question; do you mean something like this:

  func someMethod(foo:Foo?) {
    var bar:Foo? = nil // bar is Optional<Foo>.none
    if (foo != nil) { // foo is Optional<Foo>.some
      bar = foo // both foo and bar are Optional<Foo>.some
    }
    // both foo and bar are Optional<Foo> (alternative branch places no mutual guarantee on type)
    if bar != nil { // bar is Optional<Foo>.some
      bar.someMutatingMethod()
    }
  }

But these are all things that the developer knows; bar can't be non-nil until a value for it is set, foo is definitely non-nil within the block etc. The only difference here is that instead of the developer having to use ! unnecessarily (or risk making a mistake) they can just use their knowledge of what it is to interact directly, as the type-checker will now also know the same thing.

However, with the Optional<T> to T method shadowing issue it seems we probably will need to require a keyword, or at the very least restrict automatic narrowing to polymorphism. For optionals the code will have to look something like this:

  func someMethod(foo:Foo?) {
    var bar:Foo? = nil
    if unwrap foo {
      bar = foo
    }

    if unwrap bar {
      bar.someMutatingMethod()
    }
  }

The difference here is that unlike shadowing (if let foo = foo), if foo were mutable then it could still be mutated directly, no need to do it with force unwrapping. Of course in these simple examples you could just use the question mark operator instead, but pretend we're doing more than one thing per conditional :wink:

So I'm trying to re-write the proposal with the use of a keyword for unwrapping in mind, to keep it simpler for myself I've done this as two separate proposals for the time being, one for simpler unwrapping of optionals, and one for type-narrowing of polymorphic types:

https://github.com/Haravikk/swift-evolution/blob/master/proposals/NNNN-optional-unwrapping.md
https://github.com/Haravikk/swift-evolution/blob/master/proposals/NNNN-type-narrowing.md

In addition to feedback on each proposal, I'm interested to know whether people think it is better to keep these separate? They're still very similar features, but the differences make it pretty awkward to keep them in one big proposal.

I've also given up on integrating enums generically into it; as I don't think it's possible to do it in a similar enough way, and some extension to pattern matching would probably be better anyway.

So a lot of concerns here especially ilya's are ones that wouldn't be
brought up if people looked at existing successful implementations like
Kotlin where they are clearly solved. (fyi, answer is only narrowing with
immutable values.)

Personally I think type narrowing with explicit opt-in has no value. All or
nothing, the whole meat of the proposal is in it being implicit.

I see too many people predisposed to considering this as if it's "compiler
magic" to the point where I don't feel the cost of arguing is worth what it
would bring to the language. Sure, it's a nice piece of syntax sugar but
it's not going to revolutionise it, and if it makes compilation times even
slower I'm probably against it - xcode in general has been driving me up a
wall lately with a matter of minutes for compiling and signing our (not
huge) project, so any compiler speed improvements take on increased
precedence for me.

Just my 2c.

Dennis

···

On Wed, Nov 9, 2016, 13:52 Haravikk via swift-evolution < swift-evolution@swift.org> wrote:

So I'm trying to re-write the proposal with the use of a keyword for
unwrapping in mind, to keep it simpler for myself I've done this as two
separate proposals for the time being, one for simpler unwrapping of
optionals, and one for type-narrowing of polymorphic types:

https://github.com/Haravikk/swift-evolution/blob/master/proposals/NNNN-optional-unwrapping.md

https://github.com/Haravikk/swift-evolution/blob/master/proposals/NNNN-type-narrowing.md

In addition to feedback on each proposal, I'm interested to know whether
people think it is better to keep these separate? They're still very
similar features, but the differences make it pretty awkward to keep them
in one big proposal.

I've also given up on integrating enums generically into it; as I don't
think it's possible to do it in a similar enough way, and some extension to
pattern matching would probably be better anyway.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

So a lot of concerns here especially ilya's are ones that wouldn't be brought up if people looked at existing successful implementations like Kotlin where they are clearly solved. (fyi, answer is only narrowing with immutable values.)

Personally I think type narrowing with explicit opt-in has no value. All or nothing, the whole meat of the proposal is in it being implicit.

Right you are, I think an explicit keyword is only required for optionals; stripping them out into their own proposal simplifies things considerably. I've tweaked the type-narrowing specific proposal to return implicit narrowing (or explicit via the is keyword and assignment, depending upon how you want to look at it).

I do think there is still value in handling reference types as well, but I'm proposing that this is done with a new force is! keyword which forces the narrowing (but causes a runtime concurrent modification error if the type no longer matches what the type-checker expects), as well as a new @concurrency(safe) attribute that can be used to indicate variables that posses a safe reference to an instance, e.g- for types that use a storage class for copy-on-write functionality, or where the value is local to a method etc. (though this isn't enforced).

if it makes compilation times even slower I'm probably against it - xcode in general has been driving me up a wall lately with a matter of minutes for compiling and signing our (not huge) project, so any compiler speed improvements take on increased precedence for me.

While I agree that the current performance can leave a lot to be desired, I don't think that this should actually slow it down by any meaningful amount; most variables won't narrow so will just be a single type as normal, and with optionals removed from the narrowing process the type-checker should only ever need to compare against the narrowest type on the stack, i.e- the only time wider types are considered is when you assign a wider type to a narrowed variable, and that's just popping types off the stack to get the new current type.

But yeah, if it were have a big impact on performance I'd recommend delaying it until major optimisation work has been done, but I don't see that it should make much of a difference, and it really shouldn't be any more of a burden than shadowing is today.

···

On 10 Nov 2016, at 10:32, Dennis Lysenko <dennis.s.lysenko@gmail.com> wrote:

On Wed, Nov 9, 2016, 13:52 Haravikk via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
So I'm trying to re-write the proposal with the use of a keyword for unwrapping in mind, to keep it simpler for myself I've done this as two separate proposals for the time being, one for simpler unwrapping of optionals, and one for type-narrowing of polymorphic types:

https://github.com/Haravikk/swift-evolution/blob/master/proposals/NNNN-optional-unwrapping.md
https://github.com/Haravikk/swift-evolution/blob/master/proposals/NNNN-type-narrowing.md

In addition to feedback on each proposal, I'm interested to know whether people think it is better to keep these separate? They're still very similar features, but the differences make it pretty awkward to keep them in one big proposal.

I've also given up on integrating enums generically into it; as I don't think it's possible to do it in a similar enough way, and some extension to pattern matching would probably be better anyway.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

Haravikk,

I think you missed ilya’s point with the owner/pet example:

// This is inside the Owner class...func freeMyPetIfIHaveOne {
  if pet != nil {
    pet.setFree() // this sets pet = nil
    // self.pet is now nil - not due to concurrency, it was set to nil
on this thread in the function above.
    // However, the compiler considers it a non-optional at this point
    pet.doStuff() // Compiler allows, bad things happen!
  }
}

As Dennis mentioned, narrowing only works for immutable values, and since
optionals are always mutable it defeats the whole justification for it. I
like the concept, but maybe it should be for immutables only?

···

On Thu, 10 Nov 2016 at 11:27 Haravikk via swift-evolution < swift-evolution@swift.org> wrote:

On 10 Nov 2016, at 10:32, Dennis Lysenko <dennis.s.lysenko@gmail.com> > wrote:

So a lot of concerns here especially ilya's are ones that wouldn't be
brought up if people looked at existing successful implementations like
Kotlin where they are clearly solved. (fyi, answer is only narrowing with
immutable values.)

Personally I think type narrowing with explicit opt-in has no value. All
or nothing, the whole meat of the proposal is in it being implicit.

Right you are, I think an explicit keyword is only required for optionals;
stripping them out into their own proposal simplifies things considerably.
I've tweaked the type-narrowing specific proposal to return implicit
narrowing (or explicit via the is keyword and assignment, depending upon
how you want to look at it).

I do think there is still value in handling reference types as well, but
I'm proposing that this is done with a new force is! keyword which forces
the narrowing (but causes a runtime concurrent modification error if the
type no longer matches what the type-checker expects), as well as a new
@concurrency(safe) attribute that can be used to indicate variables that
posses a safe reference to an instance, e.g- for types that use a storage
class for copy-on-write functionality, or where the value is local to a
method etc. (though this isn't enforced).

if it makes compilation times even slower I'm probably against it - xcode
in general has been driving me up a wall lately with a matter of minutes
for compiling and signing our (not huge) project, so any compiler speed
improvements take on increased precedence for me.

While I agree that the current performance can leave a lot to be desired,
I don't think that this should actually slow it down by any meaningful
amount; most variables won't narrow so will just be a single type as
normal, and with optionals removed from the narrowing process the
type-checker should only ever need to compare against the narrowest type on
the stack, i.e- the only time wider types are considered is when you assign
a wider type to a narrowed variable, and that's just popping types off the
stack to get the new current type.

But yeah, if it were have a big impact on performance I'd recommend
delaying it until major optimisation work has been done, but I don't see
that it should make much of a difference, and it really shouldn't be any
more of a burden than shadowing is today.

On Wed, Nov 9, 2016, 13:52 Haravikk via swift-evolution < > swift-evolution@swift.org> wrote:

So I'm trying to re-write the proposal with the use of a keyword for
unwrapping in mind, to keep it simpler for myself I've done this as two
separate proposals for the time being, one for simpler unwrapping of
optionals, and one for type-narrowing of polymorphic types:

https://github.com/Haravikk/swift-evolution/blob/master/proposals/NNNN-optional-unwrapping.md

https://github.com/Haravikk/swift-evolution/blob/master/proposals/NNNN-type-narrowing.md

In addition to feedback on each proposal, I'm interested to know whether
people think it is better to keep these separate? They're still very
similar features, but the differences make it pretty awkward to keep them
in one big proposal.

I've also given up on integrating enums generically into it; as I don't
think it's possible to do it in a similar enough way, and some extension to
pattern matching would probably be better anyway.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

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

If pet is of type Optional<T> then a .setFree() method on T cannot set self = nil, as self is of type T only in that scope.

The only way you can do something like that is to declare the setFree() method on Optional where Element:T, but that won't work on the updated proposal which uses a keyword to explicitly unwrap the variable (to avoid precisely that kind of Optional<T> vs T method conflict), you can view the updated proposals here:

https://github.com/Haravikk/swift-evolution/blob/master/proposals/NNNN-optional-unwrapping.md
https://github.com/Haravikk/swift-evolution/blob/master/proposals/NNNN-type-narrowing.md

I've also checked and a similar case like this for polymorphic types shouldn't be an issue either, as you can't assign self to a wider (or even orthogonal) type.

In other words, the only way that .setFree() can make changes that would break narrowing would be to have a reference to the instance you're working with, which is what the proposals now guard against. But for value types this should not be an issue at all.

This is all of course unless I'm missing something else, but I tried in a playground and I can't assign anything to self that would break narrowing/unwrapping that I can see, except through a reference type.

···

On 10 Nov 2016, at 16:53, Jay Abbott <jay@abbott.me.uk> wrote:

Haravikk,

I think you missed ilya’s point with the owner/pet example:

// This is inside the Owner class...
func freeMyPetIfIHaveOne {
  if pet != nil {
    pet.setFree() // this sets pet = nil
    // self.pet is now nil - not due to concurrency, it was set to nil on this thread in the function above.
    // However, the compiler considers it a non-optional at this point
    pet.doStuff() // Compiler allows, bad things happen!
  }
}
As Dennis mentioned, narrowing only works for immutable values, and since optionals are always mutable it defeats the whole justification for it. I like the concept, but maybe it should be for immutables only?

Consider this code:

struct Pet {
    let name: String
    weak var owner: Person?

    init(name: String, owner: Person?) {
        self.name = name
        self.owner = owner
        owner?.pet = self
    }

    mutating func transferOwnership(to newOwner: Person) {
        let previousOwner = owner
        owner = newOwner
        newOwner.pet = self
        if(previousOwner != nil) {
            previousOwner!.pet = nil
        }
    }

    func feed() {
    }
}
class Person {
    let name: String
    var pet: Pet?

    init(name: String) {
        self.name = name
    }

    func givePetAway(to someone: Person) {
        if pet != nil {
            pet!.transferOwnership(to: someone)
            //pet!.feed()
        }
    }
}
let bert = Person(name: "Bert")let ernie = Person(name: "Ernie")var
elmo = Pet(name: "Elmo", owner: nil)

elmo.transferOwnership(to: bert)print("Bert's pet is \(bert.pet) -
Ernie's pet is \(ernie.pet)")

bert.givePetAway(to: ernie)print("Bert's pet is \(bert.pet) - Ernie's
pet is \(ernie.pet)")

This works as expected, but if you uncomment pet!.feed() in givePetAway(to:)
it will crash, because the mutating function modifies the two-way
relationship between pet and owner.

In the code I use if pet != nil to demonstrate, in your proposal for unwrap,
if I used it to unwrap pet (a value-type, but accessed through self so it
can be modified after unwrapping because it’s not nil at the moment) the
compiler would assume I could use it and pet.feed() would crash, just as
pet!.feed() does now. In your proposal for type narrowing, it would be the
same problem. This is like your foo.value example from the proposal.

I don’t think you can get around the fact that the compiler can’t guarantee
a type-narrowed or even unwrapped mutable value, which is why if let works
as it does with an immutable snapshot.

However, type narrowing for immutable values would still be good.

This isn't a problem of mutability, it's a problem due to the use of a reference type (Person); this is what the classes and concurrency section is supposed to be describing, but perhaps I haven't made it clear enough. So in the example you've given self.pet can't be unwrapped because self is a reference type, thus you need to either use force unwrapping either with the unwrap! keyword or direct force unwrapping like you've used (since behind the scenes it's the same thing).

You are right though that the resulting error isn't necessarily a concurrency issue, so I'll make a note of this for the proposed new error, and also clarify that the reference type restriction doesn't just apply to the property itself, but also to whatever it belongs to (and so-on up the chain, so if any part of foo.bar.a.b.c is a reference type it can't be soft unwrapped).

···

On 10 Nov 2016, at 21:42, Jay Abbott <jay@abbott.me.uk> wrote:

Consider this code:

struct Pet {
    let name: String
    weak var owner: Person?

    init(name: String, owner: Person?) {
        self.name = name
        self.owner = owner
        owner?.pet = self
    }

    mutating func transferOwnership(to newOwner: Person) {
        let previousOwner = owner
        owner = newOwner
        newOwner.pet = self
        if(previousOwner != nil) {
            previousOwner!.pet = nil
        }
    }

    func feed() {
    }
}

class Person {
    let name: String
    var pet: Pet?

    init(name: String) {
        self.name = name
    }

    func givePetAway(to someone: Person) {
        if pet != nil {
            pet!.transferOwnership(to: someone)
            //pet!.feed()
        }
    }
}

let bert = Person(name: "Bert")
let ernie = Person(name: "Ernie")
var elmo = Pet(name: "Elmo", owner: nil)

elmo.transferOwnership(to: bert)
print("Bert's pet is \(bert.pet) - Ernie's pet is \(ernie.pet)")

bert.givePetAway(to: ernie)
print("Bert's pet is \(bert.pet) - Ernie's pet is \(ernie.pet)")
This works as expected, but if you uncomment pet!.feed() in givePetAway(to:) it will crash, because the mutating function modifies the two-way relationship between pet and owner.

In the code I use if pet != nil to demonstrate, in your proposal for unwrap, if I used it to unwrap pet (a value-type, but accessed through self so it can be modified after unwrapping because it’s not nil at the moment) the compiler would assume I could use it and pet.feed() would crash, just as pet!.feed() does now. In your proposal for type narrowing, it would be the same problem. This is like your foo.value example from the proposal.

I don’t think you can get around the fact that the compiler can’t guarantee a type-narrowed or even unwrapped mutable value, which is why if let works as it does with an immutable snapshot.

However, type narrowing for immutable values would still be good.

That's a good point in Jay's example and from what I can tell a good way to
address it, Haravikk.

I've done some work in a language that only provides type narrowing for
immutable types (that'd be Kotlin as I've mentioned before) and whenever
I've used it it feels like the only thing it's really been missing is the
"if let" construct allowing you to bind and unwrap mutable values, which
leads me to think that synergistically, in Swift, this would be fantastic.
The main benefit probably is that it would allow code to read much better.

I think the "type stack" phrasing in the proposal is throwing some people
for a loop making them think it'll have way more mental overhead than it
actually does in practice...really, in practice, I've used this either (a)
for checking nullity or (b) for checking for a specific subclass. There's
little to no mental overhead in either of those cases, as your "type stack"
is simply 2-long (this was an Int? outside of this conditional block, and
it's an Int inside. This was a UIViewController outside of this conditional
block, and it's a UINavigationController inside.)

While it may not be a panacea that allows new applications that no one
could have thought of, I have no doubt that it would greatly improve the
experience of coding in the language by, as you said, allowing more
flexibility in expressiveness. Regardless of one's opinion on the efficacy
of this feature as a whole, there *are* frequent situations where this
feature leads to substantially better-reading code. The "unwrap" keyword
proposed in another thread, critically, solves only *half* of the problems
that this proposal solves (as far as I could tell from reading a few emails
in the chain, it left the subclass inference completely untouched). Ability
to say "if (controller is SongSelectionViewController) {
controller.search(for: "mozart") }" is mentally freeing and helps me stay
in coder zen.

IMO, it would effectively be the cream cheese icing on the top of the
carrot cake of Swift's unwrapping and type inference features. A good
tasteful cream cheese icing always improves a carrot cake.

The question then becomes, is it really worth the implementation time? This
is something that, presumably, someone from the Swift team would need to be
involved in answering.

···

On Fri, Nov 11, 2016 at 10:52 AM Haravikk via swift-evolution < swift-evolution@swift.org> wrote:

On 10 Nov 2016, at 21:42, Jay Abbott <jay@abbott.me.uk> wrote:

Consider this code:

struct Pet {
    let name: String
    weak var owner: Person?

    init(name: String, owner: Person?) {
        self.name = name
        self.owner = owner
        owner?.pet = self
    }

    mutating func transferOwnership(to newOwner: Person) {
        let previousOwner = owner
        owner = newOwner
        newOwner.pet = self
        if(previousOwner != nil) {
            previousOwner!.pet = nil
        }
    }

    func feed() {
    }
}
class Person {
    let name: String
    var pet: Pet?

    init(name: String) {
        self.name = name
    }

    func givePetAway(to someone: Person) {
        if pet != nil {
            pet!.transferOwnership(to: someone)
            //pet!.feed()
        }
    }
}
let bert = Person(name: "Bert")let ernie = Person(name: "Ernie")var elmo = Pet(name: "Elmo", owner: nil)

elmo.transferOwnership(to: bert)print("Bert's pet is \(bert.pet) - Ernie's pet is \(ernie.pet)")

bert.givePetAway(to: ernie)print("Bert's pet is \(bert.pet) - Ernie's pet is \(ernie.pet)")

This works as expected, but if you uncomment pet!.feed() in
givePetAway(to:) it will crash, because the mutating function modifies
the two-way relationship between pet and owner.

In the code I use if pet != nil to demonstrate, in your proposal for
unwrap, if I used it to unwrap pet (a value-type, but accessed through
self so it can be modified after unwrapping because it’s not nil at the
moment) the compiler would assume I could use it and pet.feed() would
crash, just as pet!.feed() does now. In your proposal for type narrowing,
it would be the same problem. This is like your foo.value example from
the proposal.

I don’t think you can get around the fact that the compiler can’t
guarantee a type-narrowed or even unwrapped mutable value, which is why if
let works as it does with an immutable snapshot.

However, type narrowing for immutable values would still be good.

This isn't a problem of mutability, it's a problem due to the use of a
reference type (Person); this is what the classes and concurrency section
is supposed to be describing, but perhaps I haven't made it clear enough.
So in the example you've given self.pet can't be unwrapped because self is
a reference type, thus you need to either use force unwrapping either with
the unwrap! keyword or direct force unwrapping like you've used (since
behind the scenes it's the same thing).

You are right though that the resulting error isn't necessarily a
concurrency issue, so I'll make a note of this for the proposed new error,
and also clarify that the reference type restriction doesn't just apply to
the property itself, but also to whatever it belongs to (and so-on up the
chain, so if any part of foo.bar.a.b.c is a reference type it can't be soft
unwrapped).
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

That's a good point in Jay's example and from what I can tell a good way to address it, Haravikk.

I've done some work in a language that only provides type narrowing for immutable types (that'd be Kotlin as I've mentioned before) and whenever I've used it it feels like the only thing it's really been missing is the "if let" construct allowing you to bind and unwrap mutable values, which leads me to think that synergistically, in Swift, this would be fantastic. The main benefit probably is that it would allow code to read much better.

IMHO, the Kotlin solution is flaw. The fact that type narrowing does not works for var and that there is no simple way to unwrap optional just force the developer either to introduce local variable, or to use the force unwrap operator.
Moreover, introducing a unwrap syntax (if let) like in swift would just result in having 2 different and inconsistent way to do the same thing.

···

Le 13 nov. 2016 à 03:37, Dennis Lysenko via swift-evolution <swift-evolution@swift.org> a écrit :

I think the "type stack" phrasing in the proposal is throwing some people for a loop making them think it'll have way more mental overhead than it actually does in practice...really, in practice, I've used this either (a) for checking nullity or (b) for checking for a specific subclass. There's little to no mental overhead in either of those cases, as your "type stack" is simply 2-long (this was an Int? outside of this conditional block, and it's an Int inside. This was a UIViewController outside of this conditional block, and it's a UINavigationController inside.)

While it may not be a panacea that allows new applications that no one could have thought of, I have no doubt that it would greatly improve the experience of coding in the language by, as you said, allowing more flexibility in expressiveness. Regardless of one's opinion on the efficacy of this feature as a whole, there *are* frequent situations where this feature leads to substantially better-reading code. The "unwrap" keyword proposed in another thread, critically, solves only half of the problems that this proposal solves (as far as I could tell from reading a few emails in the chain, it left the subclass inference completely untouched). Ability to say "if (controller is SongSelectionViewController) { controller.search(for: "mozart") }" is mentally freeing and helps me stay in coder zen.

IMO, it would effectively be the cream cheese icing on the top of the carrot cake of Swift's unwrapping and type inference features. A good tasteful cream cheese icing always improves a carrot cake.

The question then becomes, is it really worth the implementation time? This is something that, presumably, someone from the Swift team would need to be involved in answering.

On Fri, Nov 11, 2016 at 10:52 AM Haravikk via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On 10 Nov 2016, at 21:42, Jay Abbott <jay@abbott.me.uk <mailto:jay@abbott.me.uk>> wrote:

Consider this code:

struct Pet {
    let name: String
    weak var owner: Person?

    init(name: String, owner: Person?) {
        self.name = name
        self.owner = owner
        owner?.pet = self
    }

    mutating func transferOwnership(to newOwner: Person) {
        let previousOwner = owner
        owner = newOwner
        newOwner.pet = self
        if(previousOwner != nil) {
            previousOwner!.pet = nil
        }
    }

    func feed() {
    }
}

class Person {
    let name: String
    var pet: Pet?

    init(name: String) {
        self.name = name
    }

    func givePetAway(to someone: Person) {
        if pet != nil {
            pet!.transferOwnership(to: someone)
            //pet!.feed()
        }
    }
}

let bert = Person(name: "Bert")
let ernie = Person(name: "Ernie")
var elmo = Pet(name: "Elmo", owner: nil)

elmo.transferOwnership(to: bert)
print("Bert's pet is \(bert.pet) - Ernie's pet is \(ernie.pet)")

bert.givePetAway(to: ernie)
print("Bert's pet is \(bert.pet) - Ernie's pet is \(ernie.pet)")
This works as expected, but if you uncomment pet!.feed() in givePetAway(to:) it will crash, because the mutating function modifies the two-way relationship between pet and owner.

In the code I use if pet != nil to demonstrate, in your proposal for unwrap, if I used it to unwrap pet (a value-type, but accessed through self so it can be modified after unwrapping because it’s not nil at the moment) the compiler would assume I could use it and pet.feed() would crash, just as pet!.feed() does now. In your proposal for type narrowing, it would be the same problem. This is like your foo.value example from the proposal.

I don’t think you can get around the fact that the compiler can’t guarantee a type-narrowed or even unwrapped mutable value, which is why if let works as it does with an immutable snapshot.

However, type narrowing for immutable values would still be good.

This isn't a problem of mutability, it's a problem due to the use of a reference type (Person); this is what the classes and concurrency section is supposed to be describing, but perhaps I haven't made it clear enough. So in the example you've given self.pet can't be unwrapped because self is a reference type, thus you need to either use force unwrapping either with the unwrap! keyword or direct force unwrapping like you've used (since behind the scenes it's the same thing).

You are right though that the resulting error isn't necessarily a concurrency issue, so I'll make a note of this for the proposed new error, and also clarify that the reference type restriction doesn't just apply to the property itself, but also to whatever it belongs to (and so-on up the chain, so if any part of foo.bar.a.b.c is a reference type it can't be soft unwrapped).
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Jean-Daniel, I agree with the first part of your assessment fully, which in
my opinion is actually why I think the Kotlin style combined with the Swift
style would pull in the best of both worlds and create a complete solution.
I do share some of your reservation in the second part of your assessment,
which is why I'm a little hesitant on the proposal, but as I've described
before I think it would help expressiveness.

···

On Sun, Nov 13, 2016 at 11:16 AM Jean-Daniel <dev@xenonium.com> wrote:

Le 13 nov. 2016 à 03:37, Dennis Lysenko via swift-evolution < > swift-evolution@swift.org> a écrit :

That's a good point in Jay's example and from what I can tell a good way
to address it, Haravikk.

I've done some work in a language that only provides type narrowing for
immutable types (that'd be Kotlin as I've mentioned before) and whenever
I've used it it feels like the only thing it's really been missing is the
"if let" construct allowing you to bind and unwrap mutable values, which
leads me to think that synergistically, in Swift, this would be fantastic.
The main benefit probably is that it would allow code to read much better.

IMHO, the Kotlin solution is flaw. The fact that type narrowing does not
works for var and that there is no simple way to unwrap optional just force
the developer either to introduce local variable, or to use the force
unwrap operator.
Moreover, introducing a unwrap syntax (if let) like in swift would just
result in having 2 different and inconsistent way to do the same thing.

I think the "type stack" phrasing in the proposal is throwing some people
for a loop making them think it'll have way more mental overhead than it
actually does in practice...really, in practice, I've used this either (a)
for checking nullity or (b) for checking for a specific subclass. There's
little to no mental overhead in either of those cases, as your "type stack"
is simply 2-long (this was an Int? outside of this conditional block, and
it's an Int inside. This was a UIViewController outside of this conditional
block, and it's a UINavigationController inside.)

While it may not be a panacea that allows new applications that no one
could have thought of, I have no doubt that it would greatly improve the
experience of coding in the language by, as you said, allowing more
flexibility in expressiveness. Regardless of one's opinion on the efficacy
of this feature as a whole, there *are* frequent situations where this
feature leads to substantially better-reading code. The "unwrap" keyword
proposed in another thread, critically, solves only *half* of the
problems that this proposal solves (as far as I could tell from reading a
few emails in the chain, it left the subclass inference completely
untouched). Ability to say "if (controller is SongSelectionViewController)
{ controller.search(for: "mozart") }" is mentally freeing and helps me stay
in coder zen.

IMO, it would effectively be the cream cheese icing on the top of the
carrot cake of Swift's unwrapping and type inference features. A good
tasteful cream cheese icing always improves a carrot cake.

The question then becomes, is it really worth the implementation time?
This is something that, presumably, someone from the Swift team would need
to be involved in answering.

On Fri, Nov 11, 2016 at 10:52 AM Haravikk via swift-evolution < > swift-evolution@swift.org> wrote:

On 10 Nov 2016, at 21:42, Jay Abbott <jay@abbott.me.uk> wrote:

Consider this code:

struct Pet {
    let name: String
    weak var owner: Person?

    init(name: String, owner: Person?) {
        self.name = name
        self.owner = owner
        owner?.pet = self
    }

    mutating func transferOwnership(to newOwner: Person) {
        let previousOwner = owner
        owner = newOwner
        newOwner.pet = self
        if(previousOwner != nil) {
            previousOwner!.pet = nil
        }
    }

    func feed() {
    }
}
class Person {
    let name: String
    var pet: Pet?

    init(name: String) {
        self.name = name
    }

    func givePetAway(to someone: Person) {
        if pet != nil {
            pet!.transferOwnership(to: someone)
            //pet!.feed()
        }
    }
}
let bert = Person(name: "Bert")let ernie = Person(name: "Ernie")var elmo = Pet(name: "Elmo", owner: nil)

elmo.transferOwnership(to: bert)print("Bert's pet is \(bert.pet) - Ernie's pet is \(ernie.pet)")

bert.givePetAway(to: ernie)print("Bert's pet is \(bert.pet) - Ernie's pet is \(ernie.pet)")

This works as expected, but if you uncomment pet!.feed() in
givePetAway(to:) it will crash, because the mutating function modifies
the two-way relationship between pet and owner.

In the code I use if pet != nil to demonstrate, in your proposal for
unwrap, if I used it to unwrap pet (a value-type, but accessed through
self so it can be modified after unwrapping because it’s not nil at the
moment) the compiler would assume I could use it and pet.feed() would
crash, just as pet!.feed() does now. In your proposal for type narrowing,
it would be the same problem. This is like your foo.value example from
the proposal.

I don’t think you can get around the fact that the compiler can’t
guarantee a type-narrowed or even unwrapped mutable value, which is why if
let works as it does with an immutable snapshot.

However, type narrowing for immutable values would still be good.

This isn't a problem of mutability, it's a problem due to the use of a
reference type (Person); this is what the classes and concurrency section
is supposed to be describing, but perhaps I haven't made it clear enough.
So in the example you've given self.pet can't be unwrapped because self is
a reference type, thus you need to either use force unwrapping either with
the unwrap! keyword or direct force unwrapping like you've used (since
behind the scenes it's the same thing).

You are right though that the resulting error isn't necessarily a
concurrency issue, so I'll make a note of this for the proposed new error,
and also clarify that the reference type restriction doesn't just apply to
the property itself, but also to whatever it belongs to (and so-on up the
chain, so if any part of foo.bar.a.b.c is a reference type it can't be soft
unwrapped).
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

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

I'll have to take a closer look at how Kotlin does this when I get a chance, but how would this affect the two proposals as they currently stand?

https://github.com/Haravikk/swift-evolution/blob/master/proposals/NNNN-type-narrowing.md
https://github.com/Haravikk/swift-evolution/blob/master/proposals/NNNN-optional-unwrapping.md

These keep automatic narrowing of polymorphic types, but requires explicit narrow/unwrapping of optionals (because we can define methods/properties on Optional there's no other choice unfortunately); it would work for both mutable and immutable values, but mutable reference types require an extra step due to the potential for unsafe operations.

I think that's about as flexible as we're going to be able to get without introducing other difficulties.

···

On 13 Nov 2016, at 16:16, Jean-Daniel via swift-evolution <swift-evolution@swift.org> wrote:

Le 13 nov. 2016 à 03:37, Dennis Lysenko via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> a écrit :

That's a good point in Jay's example and from what I can tell a good way to address it, Haravikk.

I've done some work in a language that only provides type narrowing for immutable types (that'd be Kotlin as I've mentioned before) and whenever I've used it it feels like the only thing it's really been missing is the "if let" construct allowing you to bind and unwrap mutable values, which leads me to think that synergistically, in Swift, this would be fantastic. The main benefit probably is that it would allow code to read much better.

IMHO, the Kotlin solution is flaw. The fact that type narrowing does not works for var and that there is no simple way to unwrap optional just force the developer either to introduce local variable, or to use the force unwrap operator.
Moreover, introducing a unwrap syntax (if let) like in swift would just result in having 2 different and inconsistent way to do the same thing.

While the proposals try to be less restrictive than Kotlin, I don’t like the way they handle concurrency.
All usage of the ‘!’ operator in swift guarantee a crash at the call site if something is wrong (try!, as!, optional!, …). In the proposals, the ‘!’ operator means that the app may crash at call site or may crash later.

···

Le 14 nov. 2016 à 10:10, Haravikk <swift-evolution@haravikk.me> a écrit :

On 13 Nov 2016, at 16:16, Jean-Daniel via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Le 13 nov. 2016 à 03:37, Dennis Lysenko via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> a écrit :

That's a good point in Jay's example and from what I can tell a good way to address it, Haravikk.

I've done some work in a language that only provides type narrowing for immutable types (that'd be Kotlin as I've mentioned before) and whenever I've used it it feels like the only thing it's really been missing is the "if let" construct allowing you to bind and unwrap mutable values, which leads me to think that synergistically, in Swift, this would be fantastic. The main benefit probably is that it would allow code to read much better.

IMHO, the Kotlin solution is flaw. The fact that type narrowing does not works for var and that there is no simple way to unwrap optional just force the developer either to introduce local variable, or to use the force unwrap operator.
Moreover, introducing a unwrap syntax (if let) like in swift would just result in having 2 different and inconsistent way to do the same thing.

I'll have to take a closer look at how Kotlin does this when I get a chance, but how would this affect the two proposals as they currently stand?

https://github.com/Haravikk/swift-evolution/blob/master/proposals/NNNN-type-narrowing.md
https://github.com/Haravikk/swift-evolution/blob/master/proposals/NNNN-optional-unwrapping.md

These keep automatic narrowing of polymorphic types, but requires explicit narrow/unwrapping of optionals (because we can define methods/properties on Optional there's no other choice unfortunately); it would work for both mutable and immutable values, but mutable reference types require an extra step due to the potential for unsafe operations.

I think that's about as flexible as we're going to be able to get without introducing other difficulties.