[swift-evolution] try? shouldn't work on non-method-call

Xiaodi Wu xiaodi.wu at gmail.com
Thu Aug 18 14:13:28 CDT 2016


On Thu, Aug 18, 2016 at 1:20 PM, John McCall <rjmccall at apple.com> wrote:

> On Aug 18, 2016, at 10:11 AM, Xiaodi Wu <xiaodi.wu at gmail.com> wrote:
> On Thu, Aug 18, 2016 at 11:30 AM, John McCall <rjmccall at apple.com> wrote:
>
>> On Aug 18, 2016, at 8:46 AM, Xiaodi Wu <xiaodi.wu at gmail.com> wrote:
>> The issue would be that, in the case of "try? foo()", nil and .some(nil)
>> might mean very different things.
>>
>>
>> This is true of a?.foo() as well.  But yes, I think it is more likely
>> that someone would want to treat them differently for try?.
>>
>
> Agreed.
>
> My proposed solution was half-baked, but it may be workable--I'm not
> suggesting typing decisions based on a dynamic property, of course. It'd be
> something like this:
>
> `as?` would produce a result of a type named something like
> CastingOptional<T>, which on assignment or essentially any other operation
> is promoted/bridged/[insert much more correct term here] to an Optional<T>
> like how T is automatically promoted to Optional<T>. However, `try?` will
> not wrap a CastingOptional<T> into an Optional<Optional<T>>.
>
>
> The way this is done for ?-chaining is that the result of the chain is
> coerced to T?, for a fresh unbound type T.  If the result is already of
> type U?, T will be bound to U and there's no "stacking" of optionals; if
> the result is a non-optional type V, T will be bound to V and therefore the
> chain gains a level of optionality.  I think that is simpler and more
> consistent than inventing a new flavor of Optional with complex conversion
> and defaulting rules.
>

Yeah, that's definitely much simpler.

I'm just not sure I'd be comfortable with `try?` refusing to stack
optionals for all arbitrary functions `(...) -> Optional<T>` just like it
works for ?-chaining, for the reasons we just discussed. So, if we're to
stick to consistent rules, I'd rather that `try?` continue to stack
optionals all the time.

The status quo isn't unteachable: if you have try? and as?, that's two
question marks, so you get back two stacked optionals. That's livable.


> John.
>
>
>
>> John.
>>
>> On Thu, Aug 18, 2016 at 10:40 John McCall <rjmccall at apple.com> wrote:
>>
>>> On Aug 18, 2016, at 8:19 AM, Xiaodi Wu via swift-evolution <
>>> swift-evolution at swift.org> wrote:
>>>
>>> Lots of interesting points here. I do think there's an improvement
>>> possible here, but it's actually along the lines of Sam's original
>>> suggestion #3 (not vis-a-vis all of Swift, but specifically for how try?
>>> composes with as?):
>>>
>>> A. I'm in favor of the current behavior where try prefixes an entire
>>> statement: it solves the precise issue of multiple nested optionals or
>>> multiple unwrapping of optionals in the situation where one statement has
>>> calls to many throwing functions. It says instead, I want nil if anything
>>> in this statement throws, otherwise, give me .some(value).
>>>
>>> Sam--I think you may have misunderstood Charles's explanation. He's not
>>> saying "try?" attaches with lower or higher precedence as compared to
>>> "as?". Rather, I think the mental model is that "try?" prefixes the whole
>>> right-hand side (rhs), and if *any* call on the rhs throws, the whole rhs
>>> evaluates to nil, but if *any* call could potentially throw but doesn't,
>>> "try?" wraps the entire rhs and gives you .some(value). IMO, this is pretty
>>> sensible for the reason he gives.
>>>
>>> B. I'm in favor of warning instead of error, for precisely the internal
>>> discussion rationale communicated by Slava. I'm willing to live with "try?
>>> 42" being only a warning if that means my code won't stop compiling when
>>> someone decides a library function doesn't need to throw.
>>>
>>> Sam--here, changing warning to error would not solve your original
>>> problem, because in that example "try?" does prefix at least one throwing
>>> function, so you wouldn't get an error anyway.
>>>
>>> C. However, given the thinking in (A), I do think how "try?" composes
>>> with "as?" is a little counterintuitive or at least overly ceremonious,
>>> though technically it is possible to reason through.
>>>
>>> It's true that currently you can use the multiple nested optionals to
>>> figure out whether either a throwing function threw (but not which throwing
>>> function out of potentially more than one) or whether the cast did not
>>> succeed. But, since "try?" after all means "give me nil if anything
>>> throws," it kind of makes less sense that you get all this nesting and
>>> detailed information when it composes with "as?". If you really wanted that
>>> level of detail, you could always evaluate "try?" and "as?" in separate
>>> statements. What I'd propose instead is this:
>>>
>>> If "try?" is composed with "as?", and "as?" yields "nil", then "try?"
>>> should not wrap that value in another optional.
>>>
>>>
>>> We can't make the typing decision dependent on a dynamic property like
>>> whether the cast fails.  And I don't like the idea of changing its typing
>>> rule based on the form of the nested expression.  But we could make "try?
>>> foo()" avoid adding an extra level of optionality, the same way that
>>> "a?.foo()" does.
>>>
>>> John.
>>>
>>>
>>> Does that sound sensible?
>>>
>>>
>>> On Thu, Aug 18, 2016 at 3:54 AM, Sikhapol Saijit via swift-evolution <
>>> swift-evolution at swift.org> wrote:
>>>
>>>>
>>>> On Aug 18, 2016, at 3:42 PM, Slava Pestov <spestov at apple.com> wrote:
>>>>
>>>>
>>>> On Aug 18, 2016, at 12:52 AM, David Hart via swift-evolution <
>>>> swift-evolution at swift.org> wrote:
>>>>
>>>> Opinions inline:
>>>>
>>>> On 18 Aug 2016, at 07:43, Sikhapol Saijit via swift-evolution <
>>>> swift-evolution at swift.org> wrote:
>>>>
>>>> Hi all,
>>>>
>>>>
>>>> Yesterday I tried this code:
>>>>
>>>> func couldFailButWillNot() throws -> Any {
>>>>     return 42
>>>> }
>>>>
>>>> if let a = try? couldFailButWillNot() as? Int {
>>>>     print(a)
>>>> }
>>>>
>>>> And was surprised that the output was *Optional(42)* on both Swift 2
>>>> and Swift 3.
>>>> I always have the impression that when a variable is resolved with if
>>>> let it will never be optional.
>>>>
>>>> So, with a little investigation, I found out that it happens because as
>>>> ? has higher precedence than try? and is evaluated first.
>>>> And the whole expression `try? couldFailButWillNot() as? Int` evaluated
>>>> as *Optional(Optional(42))*.
>>>>
>>>> Also, I’m surprised that try? can be used with non-method-call.
>>>> This code: `print(try? 42)` will print *Optional(42)*.
>>>>
>>>> So, the questions are:
>>>>
>>>> 1. Is it intentional that try? can be used with a "non-method-call"
>>>> and return an optional of the type that follows?
>>>>
>>>>
>>>> I think this is the real solution. try and try? should not be allowed
>>>> on non-throwing functions or expressions.
>>>>
>>>>
>>>> This is a warning right now — do you think it should be an error?
>>>>
>>>> Slavas-MacBook-Pro:~ slava$ cat ttt.swift
>>>> func f() {}
>>>>
>>>> func g() {
>>>>   try f()
>>>>   try? f()
>>>> }
>>>>
>>>> Slavas-MacBook-Pro:~ slava$ swiftc ttt.swift
>>>> *ttt.swift:4:3: **warning: **no calls to throwing functions occur
>>>> within 'try' expression*
>>>>   try f()
>>>> *  ^*
>>>> *ttt.swift:5:8: **warning: **no calls to throwing functions occur
>>>> within 'try' expression*
>>>>   try? f()
>>>> *       ^*
>>>>
>>>>
>>>> Thank you Slava,
>>>>
>>>> While I think using try/try? on anything but a throwing function call
>>>> should be an error, right now it even works with anything. `try? 42` will
>>>> just wrap 42 in an optional and give some warning now.
>>>>
>>>>
>>>>
>>>> 2. Should we design try? to have higher precedence than as? or any
>>>> operators at all?
>>>> My intuition tells me that
>>>> let a = try? couldFailButWillNot() as? Int
>>>> should be equivalent to
>>>> let a = (try? couldFailButWillNot()) as? Int
>>>>
>>>>
>>>> That’s worth considering. try feels like it should tie very strongly
>>>> with the throwing expression.
>>>>
>>>> 3. Do you think that doubly-nested optional (or multi-level-nested
>>>> optional) is confusing and should be removed from Swift? (Yes, I’ve seen
>>>> this blog post Optionals Case Study: valuesForKeys
>>>> <https://developer.apple.com/swift/blog/?id=12>).
>>>> For me *Optional(nil)* (aka *Optional.Some(Optional.None))*) doesn’t
>>>> make much sense.
>>>> Maybe, one of the solution is to always have optional of optional
>>>> merged into a single level optional? Like
>>>> *Optional(Optional(Optional(42)))* should be the merged to and
>>>> evaluated as *Optional(42)*.
>>>>
>>>>
>>>> I don’t think this is the solution. Even if it was, how would you
>>>> expect to “remove” them from Swift? Optionals are simply an enum with an
>>>> associated value. We’d have to introduce a language feature to restrict
>>>> values that can be stored in enum cases? It sounds awfully complicated.
>>>>
>>>> BTW, the code above is merely for a demonstration. The actual code was
>>>> more of something like this:
>>>>
>>>> func parse(JSON: Data) throws -> Any {
>>>>     // …
>>>> }
>>>>
>>>> if let dict = try? parse(JSON: json) as? [String: Any] {
>>>>     // assume dict is a valid [String: Any] dictionary
>>>>     // …
>>>> }
>>>>
>>>> I’m new to this mailing list so I’m not sure if this belongs here. I’m
>>>> sorry in advance if it doesn’t.
>>>>
>>>>
>>>> Thank you,
>>>> Sam
>>>> _______________________________________________
>>>> swift-evolution mailing list
>>>> swift-evolution at swift.org
>>>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>>>
>>>>
>>>> _______________________________________________
>>>> swift-evolution mailing list
>>>> swift-evolution at swift.org
>>>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>>>
>>>>
>>>>
>>>> _______________________________________________
>>>> swift-evolution mailing list
>>>> swift-evolution at swift.org
>>>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>>>
>>>>
>>> _______________________________________________
>>> swift-evolution mailing list
>>> swift-evolution at swift.org
>>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>>
>>>
>>>
>>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160818/37642e31/attachment.html>


More information about the swift-evolution mailing list