0

Why is total optional on line return total + 1?

return first.enumerated().reduce(0) { total, letter in
   let index = first.index(first.startIndex, offsetBy: letter.offset)
   if first[index] != second[index]{
       return total + 1
   }
   return total
}

Value of optional type 'Int?' must be unwrapped to a value of type'Int' Coalesce using '??' to provide a default when the optional value contains 'nil' Force-unwrap using '!' to abort execution if the optional value contains 'nil'

So this fixes it:

return first.enumerated().reduce(0) { total, letter in
   let index = first.index(first.startIndex, offsetBy: letter.offset)
   if first[index] != second[index]{
       return total! + 1
   }
   return total
}

If I break it down the change happens on adding let index....

OK - This returns the total count of first and total is not optional:

return first.reduce(0) { total, letter in
    return total + 1
}

OK - This enumerated and total is not optional:

return first.enumerated().reduce(0) { total, letter in
    return total + 1
}

ERROR - This gets a compile error that total is optional

return first.enumerated().reduce(0) { total, letter in
    let index = first.index(first.startIndex, offsetBy: letter.offset)
    return total + 1
}
Turnipdabeets
  • 5,815
  • 8
  • 40
  • 63
  • What is the type of `first`? Where are you writing this code? – Sweeper Jan 12 '19 at 20:36
  • Both first and second are `String`s – Turnipdabeets Jan 12 '19 at 20:36
  • 2
    Can you please provide a [mcve]? I would guess that the return type of the function you're within is optional, and therefore because the [bodies of multi-statement closures don't participate in type inference](https://stackoverflow.com/a/42213785/2976878), the compiler is assuming that the `Result` placeholder binds exactly to the return type. – Hamish Jan 12 '19 at 20:37
  • I assume you are writing this `return` statement in a function. What does the function return? – Sweeper Jan 12 '19 at 20:37
  • @Sweeper you're right - I'm writing this function inside another function that returns an optional, but it doesn't make sense to me that `total` isn't optional until I add `let index` ... – Turnipdabeets Jan 12 '19 at 20:40
  • 1
    Note also that your code (unless I am mistaken) exhibits undefined behavior: `index` in an index into the first string and must not be used as subscript into a different string. – Btw, if the purpose is to count the number of distinct characters of two strings: there are simpler solutions. – Martin R Jan 12 '19 at 20:44
  • @MartinR Yes, the purpose is to count the number of distinct characters of two strings. `index` seems to be working for both `first` and `second`. I haven't seen an error about using a subscript to a different string. I'm trying to understand why `total` would change from non-optional to optional depending on if I add more code to the closure or not. – Turnipdabeets Jan 12 '19 at 20:49
  • As @Hamish said above: Type inference in closures is different in single-statement and multi-statement closures. – Btw, another “fix” seems to be `return first.enumerated().reduce(Int(0)) { ... }` – Martin R Jan 12 '19 at 20:51
  • Try your code with `let first = ""; let second = "abc"` – You'll get a “Fatal error: String index range is out of bounds” – Martin R Jan 12 '19 at 20:56
  • @MartinR true! I have a guard that checks for equal counts. :) When you say there are simpler solutions are you thinking to use sets? – Turnipdabeets Jan 12 '19 at 20:57
  • 1
    Those two strings *have* the same count (three). – `return zip(first, second).filter(!=).count` would be a simple solution equivalent to your code. – Martin R Jan 12 '19 at 20:58
  • oh! hmm so what makes that String index range is out of bounds? Is that because the smilies are math-based? – Turnipdabeets Jan 12 '19 at 21:01
  • 1
    @Turnipdabeets that's because an emoji character can not be represented by a single byte. try `"".utf8.count` – Leo Dabus Jan 12 '19 at 21:02

1 Answers1

0

In order for you to get this result at all (as far as I can tell), the enclosing function must return an Int?. The implication is that reduce can return an optional. Absent the conditional, the compiler can determine that reduce will never return nil, i.e., total is never nil. So, the compiler infers that the return type of the closure is Int. The compiler appears to be entangling type inferencing for the reduce closure and total. Once you add the conditional, the compiler is incapable of determining whether the reduce will return nil or not. Now when it unnecessarily infers the type for total it gets it wrong.

To me this looks like a case of Swift type inferencing gone astray. Clearly, total is never nil based on the documentation of enumerated.

If you modify the code slightly you get the expected result:

   return first.enumerated().reduce(0) { (total: Int, letter) in
       let index = first.index(first.startIndex, offsetBy: letter.offset)
       if first[index] != second[index]{
          return total + 1
       }
       return total
   }

Swift makes a lot of type inferences and it is really great because I get strong typing while retaining many of the benefits of a dynamic language. In my experience, however, swift's inferences can be mystifying sometimes. It handles arcane situations with ease and stumbles over something I think is obvious.

It looks like a bug to me.

John Morris
  • 396
  • 2
  • 10