? suffix for <, >, <=, >= comparisons with optionals to prevent subtle bugs


(Al Skipp) #1

To me it seems logical that comparing Optional<Int> with Int (or another
Optional<Int>), if it's allowed at all, should return Optional<Bool>. Since
conditional statements only accept Bool, the user is forced to handle the
nil case explicitly.

I disagree that comparing Optional values should have a return value of Optional<Bool>.
If the following were to be true:

.None < .Some(0) == .None

Then logically, this would be too:

[] < [1,2,3] == []

I think most people would agree that the correct result to that comparison should be ‘true’, not [].

When comparing ‘container’ types I think it’s important to have a simple Bool result, otherwise things get very peculiar (conceptually the Optional type is really just a container with a maximum count of 1).


(Lucas Derraugh) #2

This is related to a question I asked on SO a while back: http://stackoverflow.com/questions/26172911/swift-nil-has-a-numeric-value

Seems that many others have had confusion with the same problem, and it can sneak into your code without being aware of it. The simple case that I ran into was comparing an Optional Int to an Int. It is a very subtle bug and one that I don’t think should be allowed to occur. To me the intent isn't clear in this situation. I don’t think nil should be considered true or false if compared against. The only alternative I can think of is if comparing anything to a nil value, the result would be false; this would probably still lead to unexpected behavior though.

Lucas Derraugh
lucas.derraugh@me.com
607-793-3517

···

On December 9, 2015 at 6:10:40 AM, Al Skipp via swift-evolution (swift-evolution@swift.org) wrote:

To me it seems logical that comparing Optional<Int> with Int (or another
Optional<Int>), if it's allowed at all, should return Optional<Bool>. Since
conditional statements only accept Bool, the user is forced to handle the
nil case explicitly.
I disagree that comparing Optional values should have a return value of Optional<Bool>.
If the following were to be true:

.None < .Some(0) == .None

Then logically, this would be too:

[] < [1,2,3] == []

I think most people would agree that the correct result to that comparison should be ‘true’, not [].

When comparing ‘container’ types I think it’s important to have a simple Bool result, otherwise things get very peculiar (conceptually the Optional type is really just a container with a maximum count of 1).
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Al Skipp) #3

I don’t have a problem with Optionals being comparable, but I do see the potential for bugs occurring as Swift automatically wraps values as Optionals when required. Not sure what the solution would be here, other than to make the wrapping of values explicit?

Here’s an example of when automatic Optional wrapping can cause unexpected results:

struct Pet {
  let age: Int
}

struct Person {
  let name: String
  let pet: Pet?
}

let peeps = [
  Person(name: "Fred", pet: Pet(age: 5)),
  Person(name: "Jill", pet: .None), // no pet here
  Person(name: "Burt", pet: Pet(age: 10)),
]

let ps = peeps.filter { $0.pet?.age < 6 }

ps == [Fred, Jill] // if you don’t own a pet, your non-existent pet is considered to be younger than any actual pet :dog:

If the Optional wrapping of ‘6’ in the comparison had to be explicit, then the result wouldn't be so unexpected.

Al

···

On 9 Dec 2015, at 11:31, Lucas Derraugh <lucas.derraugh@me.com> wrote:

This is related to a question I asked on SO a while back: http://stackoverflow.com/questions/26172911/swift-nil-has-a-numeric-value

Seems that many others have had confusion with the same problem, and it can sneak into your code without being aware of it. The simple case that I ran into was comparing an Optional Int to an Int. It is a very subtle bug and one that I don’t think should be allowed to occur. To me the intent isn't clear in this situation. I don’t think nil should be considered true or false if compared against. The only alternative I can think of is if comparing anything to a nil value, the result would be false; this would probably still lead to unexpected behavior though.

Lucas Derraugh
lucas.derraugh@me.com <mailto:lucas.derraugh@me.com>
607-793-3517

On December 9, 2015 at 6:10:40 AM, Al Skipp via swift-evolution (swift-evolution@swift.org <mailto:swift-evolution@swift.org>) wrote:

To me it seems logical that comparing Optional<Int> with Int (or another
Optional<Int>), if it's allowed at all, should return Optional<Bool>. Since
conditional statements only accept Bool, the user is forced to handle the
nil case explicitly.

I disagree that comparing Optional values should have a return value of Optional<Bool>.
If the following were to be true:

.None < .Some(0) == .None

Then logically, this would be too:

[] < [1,2,3] == []

I think most people would agree that the correct result to that comparison should be ‘true’, not [].

When comparing ‘container’ types I think it’s important to have a simple Bool result, otherwise things get very peculiar (conceptually the Optional type is really just a container with a maximum count of 1).
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(thorsten@portableinnovations.de) #4

That's why I would prefer that optionals are not comparable by default.

Then I would simply have to write

let ps = peeps.filter { $0.pet?.age.map { age in age < 6 } ?? false }

making the intended semantics explicit, which is necessary IMHO because there is no default semantics for comparing nil with something else (depending on my specific use case I might have chosen to return true in the nil case).

-Thorsten

···

Am 09.12.2015 um 13:03 schrieb Al Skipp via swift-evolution <swift-evolution@swift.org>:

I don’t have a problem with Optionals being comparable, but I do see the potential for bugs occurring as Swift automatically wraps values as Optionals when required. Not sure what the solution would be here, other than to make the wrapping of values explicit?

Here’s an example of when automatic Optional wrapping can cause unexpected results:

struct Pet {
  let age: Int
}

struct Person {
  let name: String
  let pet: Pet?
}

let peeps = [
  Person(name: "Fred", pet: Pet(age: 5)),
  Person(name: "Jill", pet: .None), // no pet here
  Person(name: "Burt", pet: Pet(age: 10)),
]

let ps = peeps.filter { $0.pet?.age < 6 }

ps == [Fred, Jill] // if you don’t own a pet, your non-existent pet is considered to be younger than any actual pet :dog:

If the Optional wrapping of ‘6’ in the comparison had to be explicit, then the result wouldn't be so unexpected.

Al

On 9 Dec 2015, at 11:31, Lucas Derraugh <lucas.derraugh@me.com> wrote:

This is related to a question I asked on SO a while back: http://stackoverflow.com/questions/26172911/swift-nil-has-a-numeric-value

Seems that many others have had confusion with the same problem, and it can sneak into your code without being aware of it. The simple case that I ran into was comparing an Optional Int to an Int. It is a very subtle bug and one that I don’t think should be allowed to occur. To me the intent isn't clear in this situation. I don’t think nil should be considered true or false if compared against. The only alternative I can think of is if comparing anything to a nil value, the result would be false; this would probably still lead to unexpected behavior though.

Lucas Derraugh
lucas.derraugh@me.com
607-793-3517

On December 9, 2015 at 6:10:40 AM, Al Skipp via swift-evolution (swift-evolution@swift.org) wrote:

To me it seems logical that comparing Optional<Int> with Int (or another
Optional<Int>), if it's allowed at all, should return Optional<Bool>. Since
conditional statements only accept Bool, the user is forced to handle the
nil case explicitly.

I disagree that comparing Optional values should have a return value of Optional<Bool>.
If the following were to be true:

.None < .Some(0) == .None

Then logically, this would be too:

[] < [1,2,3] == []

I think most people would agree that the correct result to that comparison should be ‘true’, not [].

When comparing ‘container’ types I think it’s important to have a simple Bool result, otherwise things get very peculiar (conceptually the Optional type is really just a container with a maximum count of 1).
_______________________________________________
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


(Al Skipp) #5

It’s curious that your example won’t compile (didn’t when I tried it in a Playground), however the following does:

let ps = peeps.filter { $0.pet.map { pet in pet.age < 6 } ?? false }

My perspective is still that the issue isn’t with Optionals being comparable, but in certain edge cases the auto wrapping of non Optionals is what causes the confusion, as it’s not obvious that the comparison is between Optionals.

Regarding the default semantics, I’d say the semantics of Optional comparison is pretty reasonable and fits with:

"" < "abc" == true
[] < [1,2,3] == true
.None < .Some(0) == true

Al

···

On 9 Dec 2015, at 17:44, thorsten@portableinnovations.de wrote:

That's why I would prefer that optionals are not comparable by default.

Then I would simply have to write

let ps = peeps.filter { $0.pet?.age.map { age in age < 6 } ?? false }

making the intended semantics explicit, which is necessary IMHO because there is no default semantics for comparing nil with something else (depending on my specific use case I might have chosen to return true in the nil case).

-Thorsten

Am 09.12.2015 um 13:03 schrieb Al Skipp via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>>:

I don’t have a problem with Optionals being comparable, but I do see the potential for bugs occurring as Swift automatically wraps values as Optionals when required. Not sure what the solution would be here, other than to make the wrapping of values explicit?

Here’s an example of when automatic Optional wrapping can cause unexpected results:

struct Pet {
  let age: Int
}

struct Person {
  let name: String
  let pet: Pet?
}

let peeps = [
  Person(name: "Fred", pet: Pet(age: 5)),
  Person(name: "Jill", pet: .None), // no pet here
  Person(name: "Burt", pet: Pet(age: 10)),
]

let ps = peeps.filter { $0.pet?.age < 6 }

ps == [Fred, Jill] // if you don’t own a pet, your non-existent pet is considered to be younger than any actual pet :dog:

If the Optional wrapping of ‘6’ in the comparison had to be explicit, then the result wouldn't be so unexpected.

Al

On 9 Dec 2015, at 11:31, Lucas Derraugh <lucas.derraugh@me.com <mailto:lucas.derraugh@me.com>> wrote:

This is related to a question I asked on SO a while back: http://stackoverflow.com/questions/26172911/swift-nil-has-a-numeric-value

Seems that many others have had confusion with the same problem, and it can sneak into your code without being aware of it. The simple case that I ran into was comparing an Optional Int to an Int. It is a very subtle bug and one that I don’t think should be allowed to occur. To me the intent isn't clear in this situation. I don’t think nil should be considered true or false if compared against. The only alternative I can think of is if comparing anything to a nil value, the result would be false; this would probably still lead to unexpected behavior though.

Lucas Derraugh
lucas.derraugh@me.com <mailto:lucas.derraugh@me.com>
607-793-3517

On December 9, 2015 at 6:10:40 AM, Al Skipp via swift-evolution (swift-evolution@swift.org <mailto:swift-evolution@swift.org>) wrote:

To me it seems logical that comparing Optional<Int> with Int (or another
Optional<Int>), if it's allowed at all, should return Optional<Bool>. Since
conditional statements only accept Bool, the user is forced to handle the
nil case explicitly.

I disagree that comparing Optional values should have a return value of Optional<Bool>.
If the following were to be true:

.None < .Some(0) == .None

Then logically, this would be too:

[] < [1,2,3] == []

I think most people would agree that the correct result to that comparison should be ‘true’, not [].

When comparing ‘container’ types I think it’s important to have a simple Bool result, otherwise things get very peculiar (conceptually the Optional type is really just a container with a maximum count of 1).
_______________________________________________
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 <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(thorsten@portableinnovations.de) #6

let ps = peeps.filter { $0.pet?.age.map { age in age < 6 } ?? false }

It’s curious that your example won’t compile (didn’t when I tried it in a Playground)

You are right, the example does not compile (wrote it in the train far away from Xcode). It gives an error of „value of type ‚Int‘ has no member ‚map‘“.

Hmm, I consider that as a bug in the definition of optional chaining, as the documentation clearly states
"Optional chaining is a process for querying and calling properties, methods, and subscripts on an optional that might currently be nil. If the optional contains a value, the property, method, or subscript call succeeds; if the optional is nil, the property, method, or subscript call returns nil.“ (note the last sentence).

So if $0.pet?.age should return nil in case of $0.pet being nil, the type of $0.pet?.age has to be Int? and therefore understand map().

peeps[0].pet?.age.dynamicType // => Int.Type this is wrong!! Should be Optional<Int>.Type
let y: Int = peeps[1].pet?.age // This gives a type error which is right (but inconsistent with the dynamicType just claimed)
let x = peeps[1].pet?.age // Now let’s see what the type is after assigning the expression
x.dynamicType // => Optional<Int>.Type as expected
x.map { age in age < 6 } ?? false // => false as expected

So, obviously a bug in the compiler.

My perspective is still that the issue isn’t with Optionals being comparable, but in certain edge cases the auto wrapping of non Optionals is what causes the confusion, as it’s not obvious that the comparison is between Optionals.

You are certainly right that the real issue is with auto wrapping.

Regarding the default semantics, I’d say the semantics of Optional comparison is pretty reasonable and fits with:

"" < "abc" == true
[] < [1,2,3] == true
.None < .Some(0) == true

You have me convinced here as the cases for the String and the List are quite convincing and the analogy with .None and .Some compelling.
Thanks!

Just checked: Haskell defines the same order for its Maybe monad, i.e. (Nothing < Just 0) == True.

-Thorsten


(Fabian Ehrentraud) #7

The pet example took a while to digest. Not really intuitive, but makes sense when thinking it through.

BTW, thank you for "Swift Adventures In Monad Land", Al :slight_smile:

···

On 09.12.2015, at 19:30, Al Skipp via swift-evolution <swift-evolution@swift.org<mailto:swift-evolution@swift.org>> wrote:

It’s curious that your example won’t compile (didn’t when I tried it in a Playground), however the following does:

let ps = peeps.filter { $0.pet.map { pet in pet.age < 6 } ?? false }

My perspective is still that the issue isn’t with Optionals being comparable, but in certain edge cases the auto wrapping of non Optionals is what causes the confusion, as it’s not obvious that the comparison is between Optionals.

Regarding the default semantics, I’d say the semantics of Optional comparison is pretty reasonable and fits with:

"" < "abc" == true
[] < [1,2,3] == true
.None < .Some(0) == true

Al

On 9 Dec 2015, at 17:44, thorsten@portableinnovations.de<mailto:thorsten@portableinnovations.de> wrote:

That's why I would prefer that optionals are not comparable by default.

Then I would simply have to write

let ps = peeps.filter { $0.pet?.age.map { age in age < 6 } ?? false }

making the intended semantics explicit, which is necessary IMHO because there is no default semantics for comparing nil with something else (depending on my specific use case I might have chosen to return true in the nil case).

-Thorsten

Am 09.12.2015 um 13:03 schrieb Al Skipp via swift-evolution <swift-evolution@swift.org<mailto:swift-evolution@swift.org>>:

I don’t have a problem with Optionals being comparable, but I do see the potential for bugs occurring as Swift automatically wraps values as Optionals when required. Not sure what the solution would be here, other than to make the wrapping of values explicit?

Here’s an example of when automatic Optional wrapping can cause unexpected results:

struct Pet {
  let age: Int
}

struct Person {
  let name: String
  let pet: Pet?
}

let peeps = [
  Person(name: "Fred", pet: Pet(age: 5)),
  Person(name: "Jill", pet: .None), // no pet here
  Person(name: "Burt", pet: Pet(age: 10)),
]

let ps = peeps.filter { $0.pet?.age < 6 }

ps == [Fred, Jill] // if you don’t own a pet, your non-existent pet is considered to be younger than any actual pet :dog:

If the Optional wrapping of ‘6’ in the comparison had to be explicit, then the result wouldn't be so unexpected.

Al

On 9 Dec 2015, at 11:31, Lucas Derraugh <lucas.derraugh@me.com<mailto:lucas.derraugh@me.com>> wrote:

This is related to a question I asked on SO a while back: http://stackoverflow.com/questions/26172911/swift-nil-has-a-numeric-value

Seems that many others have had confusion with the same problem, and it can sneak into your code without being aware of it. The simple case that I ran into was comparing an Optional Int to an Int. It is a very subtle bug and one that I don’t think should be allowed to occur. To me the intent isn't clear in this situation. I don’t think nil should be considered true or false if compared against. The only alternative I can think of is if comparing anything to a nil value, the result would be false; this would probably still lead to unexpected behavior though.

Lucas Derraugh
lucas.derraugh@me.com<mailto:lucas.derraugh@me.com>
607-793-3517

On December 9, 2015 at 6:10:40 AM, Al Skipp via swift-evolution (swift-evolution@swift.org<mailto:swift-evolution@swift.org>) wrote:

To me it seems logical that comparing Optional<Int> with Int (or another
Optional<Int>), if it's allowed at all, should return Optional<Bool>. Since
conditional statements only accept Bool, the user is forced to handle the
nil case explicitly.

I disagree that comparing Optional values should have a return value of Optional<Bool>.
If the following were to be true:

.None < .Some(0) == .None

Then logically, this would be too:

[] < [1,2,3] == []

I think most people would agree that the correct result to that comparison should be ‘true’, not [].

When comparing ‘container’ types I think it’s important to have a simple Bool result, otherwise things get very peculiar (conceptually the Optional type is really just a container with a maximum count of 1).
_______________________________________________
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<mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

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


(Greg Titus) #8

This works:

let ps = peeps.filter { ($0.pet?.age).map { age in age < 6 } ?? false }

Essentially the “?.” operator unwraps the optional, and any further “.” in the chain get applied with the non-optional types as long as the results are non-nil. In order to get an optional result again, you need to break the “.” chain, as with the parens in this case.

I’m not sure: is this actually a compiler bug, and the type constraint system ought to consider binding to members of “T?" as well as “T" at each further “.”, or is this an expected (but obscure) part of the language, in which case the bug is maybe just that if no members match for “T" the error could suggest this paren fix if there is a member match for “T?”?

  - Greg

···

On Dec 10, 2015, at 9:30 AM, thorsten--- via swift-evolution <swift-evolution@swift.org> wrote:

let ps = peeps.filter { $0.pet?.age.map { age in age < 6 } ?? false }

It’s curious that your example won’t compile (didn’t when I tried it in a Playground)

You are right, the example does not compile (wrote it in the train far away from Xcode). It gives an error of „value of type ‚Int‘ has no member ‚map‘“.

Hmm, I consider that as a bug in the definition of optional chaining, as the documentation clearly states
"Optional chaining is a process for querying and calling properties, methods, and subscripts on an optional that might currently be nil. If the optional contains a value, the property, method, or subscript call succeeds; if the optional is nil, the property, method, or subscript call returns nil.“ (note the last sentence).

So if $0.pet?.age should return nil in case of $0.pet being nil, the type of $0.pet?.age has to be Int? and therefore understand map().

peeps[0].pet?.age.dynamicType // => Int.Type this is wrong!! Should be Optional<Int>.Type
let y: Int = peeps[1].pet?.age // This gives a type error which is right (but inconsistent with the dynamicType just claimed)
let x = peeps[1].pet?.age // Now let’s see what the type is after assigning the expression
x.dynamicType // => Optional<Int>.Type as expected
x.map { age in age < 6 } ?? false // => false as expected

So, obviously a bug in the compiler.


(Al Skipp) #9

Interesting stuff!

It did confuse me too, as I was expecting your initial code sample to work. My understanding is that ‘?’ is syntactic sugar for ‘map’ in this context. Anything following the ‘?’ is inside the Optional context.

Here is a comparison between ‘map’ and optional chaining (with and without parentheses).

let type1 = peeps[0].pet.map { $0.age }.dynamicType // Optional<Int>.Type
let type2 = peeps[0].pet.map { $0.age.dynamicType } // Int.Type

(peeps[0].pet?.age).dynamicType // Optional<Int>.Type
peeps[0].pet?.age.dynamicType // Int.Type

···

On 10 Dec 2015, at 18:01, Greg Titus <greg@omnigroup.com> wrote:

On Dec 10, 2015, at 9:30 AM, thorsten--- via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

let ps = peeps.filter { $0.pet?.age.map { age in age < 6 } ?? false }

It’s curious that your example won’t compile (didn’t when I tried it in a Playground)

You are right, the example does not compile (wrote it in the train far away from Xcode). It gives an error of „value of type ‚Int‘ has no member ‚map‘“.

Hmm, I consider that as a bug in the definition of optional chaining, as the documentation clearly states
"Optional chaining is a process for querying and calling properties, methods, and subscripts on an optional that might currently be nil. If the optional contains a value, the property, method, or subscript call succeeds; if the optional is nil, the property, method, or subscript call returns nil.“ (note the last sentence).

So if $0.pet?.age should return nil in case of $0.pet being nil, the type of $0.pet?.age has to be Int? and therefore understand map().

peeps[0].pet?.age.dynamicType // => Int.Type this is wrong!! Should be Optional<Int>.Type
let y: Int = peeps[1].pet?.age // This gives a type error which is right (but inconsistent with the dynamicType just claimed)
let x = peeps[1].pet?.age // Now let’s see what the type is after assigning the expression
x.dynamicType // => Optional<Int>.Type as expected
x.map { age in age < 6 } ?? false // => false as expected

So, obviously a bug in the compiler.

This works:

let ps = peeps.filter { ($0.pet?.age).map { age in age < 6 } ?? false }

Essentially the “?.” operator unwraps the optional, and any further “.” in the chain get applied with the non-optional types as long as the results are non-nil. In order to get an optional result again, you need to break the “.” chain, as with the parens in this case.

I’m not sure: is this actually a compiler bug, and the type constraint system ought to consider binding to members of “T?" as well as “T" at each further “.”, or is this an expected (but obscure) part of the language, in which case the bug is maybe just that if no members match for “T" the error could suggest this paren fix if there is a member match for “T?”?

  - Greg


(thorsten@portableinnovations.de) #10

Interesting stuff indeed!

Interesting stuff!

It did confuse me too, as I was expecting your initial code sample to work. My understanding is that ‘?’ is syntactic sugar for ‘map’ in this context. Anything following the ‘?’ is inside the Optional context.

That is a great insight! Of course, ?. is map! That does make sense! And using parentheses to mark the end of the Optional context introduced by ?. makes sense, too.

Thanks, Greg and Al!

-Thorsten

···

Am 10.12.2015 um 19:21 schrieb Al Skipp <al_skipp@fastmail.fm>:

Here is a comparison between ‘map’ and optional chaining (with and without parentheses).

let type1 = peeps[0].pet.map { $0.age }.dynamicType // Optional<Int>.Type
let type2 = peeps[0].pet.map { $0.age.dynamicType } // Int.Type

(peeps[0].pet?.age).dynamicType // Optional<Int>.Type
peeps[0].pet?.age.dynamicType // Int.Type

On 10 Dec 2015, at 18:01, Greg Titus <greg@omnigroup.com <mailto:greg@omnigroup.com>> wrote:

On Dec 10, 2015, at 9:30 AM, thorsten--- via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

let ps = peeps.filter { $0.pet?.age.map { age in age < 6 } ?? false }

It’s curious that your example won’t compile (didn’t when I tried it in a Playground)

You are right, the example does not compile (wrote it in the train far away from Xcode). It gives an error of „value of type ‚Int‘ has no member ‚map‘“.

Hmm, I consider that as a bug in the definition of optional chaining, as the documentation clearly states
"Optional chaining is a process for querying and calling properties, methods, and subscripts on an optional that might currently be nil. If the optional contains a value, the property, method, or subscript call succeeds; if the optional is nil, the property, method, or subscript call returns nil.“ (note the last sentence).

So if $0.pet?.age should return nil in case of $0.pet being nil, the type of $0.pet?.age has to be Int? and therefore understand map().

peeps[0].pet?.age.dynamicType // => Int.Type this is wrong!! Should be Optional<Int>.Type
let y: Int = peeps[1].pet?.age // This gives a type error which is right (but inconsistent with the dynamicType just claimed)
let x = peeps[1].pet?.age // Now let’s see what the type is after assigning the expression
x.dynamicType // => Optional<Int>.Type as expected
x.map { age in age < 6 } ?? false // => false as expected

So, obviously a bug in the compiler.

This works:

let ps = peeps.filter { ($0.pet?.age).map { age in age < 6 } ?? false }

Essentially the “?.” operator unwraps the optional, and any further “.” in the chain get applied with the non-optional types as long as the results are non-nil. In order to get an optional result again, you need to break the “.” chain, as with the parens in this case.

I’m not sure: is this actually a compiler bug, and the type constraint system ought to consider binding to members of “T?" as well as “T" at each further “.”, or is this an expected (but obscure) part of the language, in which case the bug is maybe just that if no members match for “T" the error could suggest this paren fix if there is a member match for “T?”?

  - Greg


(Al Skipp) #11

Thanks : ) Glad you found it useful, though it needs updating for Swift 2, and then Swift 3… :grimacing:

···

On 15 Dec 2015, at 20:28, Fabian Ehrentraud <Fabian.Ehrentraud@willhaben.at> wrote:

The pet example took a while to digest. Not really intuitive, but makes sense when thinking it through.

BTW, thank you for "Swift Adventures In Monad Land", Al :slight_smile:


(Al Skipp) #12

Just to add further salt, (or perhaps more appropriately, sugar) to optional chaining, it's not just map, it’s also flatMap, as and when required. Here’s a very silly example demonstrating it:

enum Hat { case Cap, Stetson, Helmet }

struct Pet { let age: Int, hat: Hat? } // a pet might wear a hat?!

struct Person { let name: String, pet: Pet? }

let p = Person(name: "Burt", pet: Pet(age: 10, hat: .Stetson))

(p.pet?.hat).dynamicType // Optional<Hat>.Type
p.pet.map { $0.hat }.dynamicType // Optional<Optional<Hat>>.Type
p.pet.flatMap { $0.hat }.dynamicType // Optional<Hat>.Type

This topic is going somewhat off-piste, so I’ll stop now!

Al

···

On 10 Dec 2015, at 18:40, thorsten@portableinnovations.de wrote:

Interesting stuff indeed!

Am 10.12.2015 um 19:21 schrieb Al Skipp <al_skipp@fastmail.fm <mailto:al_skipp@fastmail.fm>>:

Interesting stuff!

It did confuse me too, as I was expecting your initial code sample to work. My understanding is that ‘?’ is syntactic sugar for ‘map’ in this context. Anything following the ‘?’ is inside the Optional context.

That is a great insight! Of course, ?. is map! That does make sense! And using parentheses to mark the end of the Optional context introduced by ?. makes sense, too.

Thanks, Greg and Al!

-Thorsten