[swift-evolution] [Idea] Use optionals for non-optional parameters

Dennis Lysenko dennis.s.lysenko at gmail.com
Sun Sep 25 03:35:09 CDT 2016


Could take a page out of Kotlin's book:

x.let { foo(it) } // default argument name that isn't $0

IMO, this syntax looks better as a one liner than "if x? { foo(x) }".

The edge case here, with foo and bar being optional-returning functions,
becomes:
(foo(42), bar(43)).let { x, y in foo(x, y) } // default argument "it" here
is a tuple so you can't pass it directly to the function, therefore you
have to name x and y explicitly

In which case it should be abundantly clear to the reader that there is NO
short-circuiting and both foo and bar are evaluated as a tuple before the
"let" function is called on that tuple.

On Sun, Sep 25, 2016 at 3:24 AM Justin Jia via swift-evolution <
swift-evolution at swift.org> wrote:

> On Sep 24, 2016, at 7:31 PM, Dany St-Amant <dsa.mls at icloud.com> wrote:
>
> Reading some old threads...
>
> Le 16 août 2016 à 15:12, Xiaodi Wu via swift-evolution <
> swift-evolution at swift.org> a écrit :
>
> On Tue, Aug 16, 2016 at 12:14 PM, Justin Jia <
> justin.jia.developer at gmail.com> wrote:
>
>> I was trying to argue with reason. You kept stating your *opinion*. You
>> still didn't explain *why*. For example, why "if statements can and
>> function calls can't" and why "this severely violates user expectations"?
>>
>
> These were not meant as expressions of opinion. Currently, in Swift, `if`
> statements can short circuit and function calls cannot. Your proposal would
> introduce short-circuiting where currently there can be none, and thus it
> would severely violate user expectations.
>
>
> Function calls can currently be short-circuited... if there's try and
> throw involved.
>
> let z = try g(f1(a), f2(b), f3(c)) // Must be within do {} catch {}
>
> Assuming f1(), f2(), f3() can all throws, f2() is only called if f1() did
> not throw, and f3() if neither f1() nor f2() threw. The behavior is/seems
> to be: Pure left to right execution order regardless of throwing ability.
>
> If g() doesn't throw, the above (with exact same behavior) can be written
> more verbosely and explicitly as:
>
> let y = g(try f1(a), try f2(b), try f3(c)) // Must be within do {} catch {}
>
> Yet another way to do the call is:
>
> let x = try? g(f1(a), f2(b), f3(c)) // z is nil on throws
>
> So implementing something like what Justin is asking should fit within
> Swift, as long as it follows the try short-circuit logic. However, the use
> of a trailing question-mark to provide this functionality is not explicit
> enough to my taste (and to my weakening eyesight). Could we reuse let but
> with a question-mark? Or maybe guard.
>
>
> Good point! Edge cases always exist.
>
> let z = g(let? f1(a), let? f2(b), let? f3(c))
> // z is nil on fX() == nil, otherwise Optional(g()) just like try? wrapping
> // left to right short-circuit à la let z = try?
>
>
> This sounds like a good idea. Moving the keyword to the front is more
> explicit. But `let? f1(a)` seems a little bit weird to me.
>
> What about `let x = if? foo(x, y)` But… should we introduce `if!` as well?
>
> ```
> func foo(x: Any, y: Any) { … }
>
> let x: Any? = nil
> let y: Any? = nil
> if? foo(x, y) // If x and y both can be unwrapped, execute foo().
> if? foo(x?, y?) // Or if we want to be a little bit more clear, specify
> which argument could be optional
> ```
>
> ```
> if! foo(x, y) // Will crash if nil is found.
> if! foo(x!, y!) // To be more clear
> ```
>
> Backward compatibility:
>
> ```
> foo(x!, y!) // warning: add if! before the function
> ```
>
> Though, this may ask for also supporting something like:
>
> let x = g(let? try? f1(a), let? try? f2(b), let? try? f3(c))
>
> This allow selective discard of throws, instead of a discard all of a
> plain 'let x = try? ...'
>
>
> Yes.
>
> Dany
>
> ... snip ...
>>
>
>>>> On Aug 16, 2016, at 1:16 AM, Xiaodi Wu <xiaodi.wu at gmail.com> wrote:
>>>>>
>>>>> On Mon, Aug 15, 2016 at 12:07 PM, Xiaodi Wu <xiaodi.wu at gmail.com>
>>>>> wrote:
>>>>>
>>>>>> On Mon, Aug 15, 2016 at 11:43 AM, Justin Jia <
>>>>>> justin.jia.developer at gmail.com> wrote:
>>>>>>
>>>>>>> I believe the core team has considered 99% of the ideas in the
>>>>>>> mailing list in the past, but it doesn’t mean we can’t discuss it, right?
>>>>>>>
>>>>>> .. snip ...
>>>>>>
>>>>>>>
>>>>>>> Back to the original topic.
>>>>>>>
>>>>>>> I spent some time thinking and changed my mind again. I think
>>>>>>> solution 1 is most reasonable. It is consistent with if statements. Instead
>>>>>>> of treating it as sugar for `if let`, we can treat it as sugar for `guard`,
>>>>>>> which is much easy to understand and remember.
>>>>>>>
>>>>>>> -
>>>>>>>
>>>>>>> Below is the reason why I think this feature is important (quoted
>>>>>>> from another email).
>>>>>>>
>>>>>>> The problem with `if let` is you need to call the function inside {
>>>>>>> }.
>>>>>>>
>>>>>>> ```
>>>>>>> /* code 1 */
>>>>>>> if let x = x, let y = y {
>>>>>>>     /* code 2, depends on x and y to be non-optional */
>>>>>>>     let z = foo(x, y)
>>>>>>>     if let z = z {
>>>>>>>         bar(z)
>>>>>>>     }
>>>>>>>     /* code 3, depends on x and y to be non-optional */
>>>>>>> }
>>>>>>> /* code 4 */
>>>>>>> ```
>>>>>>>
>>>>>>> I can't use `guard` for this situation because guard will force me
>>>>>>> to leave the entire function.
>>>>>>>
>>>>>>
>>>>>>> ```
>>>>>>> /* code 1 */
>>>>>>> guard let x = x, y = y else { return }
>>>>>>> /* code 2, depends on x and y to be non-optional */
>>>>>>> guard let z = foo(x, y) else { return }
>>>>>>> bar(z)
>>>>>>> /* code 3, depends on x and y to be non-optional */ <- This won't
>>>>>>> execute if z is nil
>>>>>>> /* code 4 */ <- This won't execute if x, y or z is nil
>>>>>>> ```
>>>>>>>
>>>>>>
>>>>>> Then surround it with a do block.
>>>>>>
>>>>>> ```
>>>>>> out: do {
>>>>>>   guard foo else { break out }
>>>>>>   guard bar else { break out }
>>>>>>   /* other code */
>>>>>> }
>>>>>> ```
>>>>>>
>>>>>
>>>>> Or, more idiomatically, since your use case is that you want /* code 4
>>>>> */ to be executed no matter what, while everything else depends on x and y
>>>>> not being nil:
>>>>>
>>>>> ```
>>>>> defer { /* code 4 */ }
>>>>> guard let x = x, let y = y else { return }
>>>>> /* code 2 */
>>>>> /* code 3 */
>>>>> ```
>>>>>
>>>>>
>>>>>>> What I really want is some like this:
>>>>>>>
>>>>>>> ```
>>>>>>> / * code 1 */
>>>>>>> let z = foo(x?, y?)
>>>>>>> /* code 2, depends on x and y to be non-optional, use x? and y? */
>>>>>>> bar(z?)
>>>>>>> /* code 3, depends on x and y to be non-optional, use x? and y? */
>>>>>>> /* code 4 */
>>>>>>> ```
>>>>>>> This is much easier to read. Sometimes people choose to use `guard`
>>>>>>> to avoid `{ }`, which usually lead to code could easily get wrong (like the
>>>>>>> second example).
>>>>>>>
>>>>>>> Sincerely,
>>>>>>> Justin
>>>>>>>
>>>>>>>
>>>>>>>
>>>>>>>
>>>>>>> On Aug 15, 2016, at 11:41 PM, Xiaodi Wu <xiaodi.wu at gmail.com> wrote:
>>>>>>>
>>>>>>> What do you mean, limited to variables? What about a computed
>>>>>>> property? You will have the same problem.
>>>>>>>
>>>>>>> I'm not sure where you want to go with this, given that the core
>>>>>>> team has considered the same idea in the past and found these issues to
>>>>>>> have no good solution.
>>>>>>>
>>>>>>> On Mon, Aug 15, 2016 at 04:56 Justin Jia <
>>>>>>> justin.jia.developer at gmail.com> wrote:
>>>>>>>
>>>>>>>> IMO I don't this bar should be evaluated unless we decide if let
>>>>>>>> can accept non-optional values.
>>>>>>>>
>>>>>>>> Actually, what if we allow if let to accept non-optional values?
>>>>>>>>
>>>>>>>> I agree this is confusing at the beginning. But people who are not
>>>>>>>> familiar with the detail design can avoid this situation easily. People who
>>>>>>>> are familiar with the design can adopt it quickly. Sometimes, this is
>>>>>>>> unavoidable.
>>>>>>>>
>>>>>>>> Btw, do you think this is still something nice to have if we limit
>>>>>>>> this syntax to only variables?
>>>>>>>>
>>>>>>>> On Aug 15, 2016, at 4:59 PM, Xiaodi Wu <xiaodi.wu at gmail.com> wrote:
>>>>>>>>
>>>>>>>> On Mon, Aug 15, 2016 at 3:55 AM, Xiaodi Wu <xiaodi.wu at gmail.com>
>>>>>>>> wrote:
>>>>>>>>
>>>>>>>>> On Mon, Aug 15, 2016 at 3:25 AM, Justin Jia via swift-evolution <
>>>>>>>>> swift-evolution at swift.org> wrote:
>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> On Aug 15, 2016, at 4:09 PM, Charlie Monroe <
>>>>>>>>>> charlie at charliemonroe.net> wrote:
>>>>>>>>>>
>>>>>>>>>> The example above was to better demonstrate the problem with
>>>>>>>>>> *when* to evaluate the latter argument. Why should both arguments be
>>>>>>>>>> evaluated *before* the if statement? If both calls return Optionals,
>>>>>>>>>>
>>>>>>>>>> if let x = bar(42), y = baz(42) { ... }
>>>>>>>>>>
>>>>>>>>>> is how would I write it without the suggested syntax - baz(42)
>>>>>>>>>> will *not* be evaluated if bar(42) returns nil. Which bears a question why
>>>>>>>>>> would
>>>>>>>>>>
>>>>>>>>>> foo(bar(42)?, baz(42)?)
>>>>>>>>>>
>>>>>>>>>> evaluate both arguments even if the first one is nil, making it
>>>>>>>>>> incosistent with the rest of the language?
>>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> I see your point. I understand that maybe 1/2 of the people think
>>>>>>>>>> we should evaluate both arguments and 1/2 of the people think we should
>>>>>>>>>> only evaluate the first argument.
>>>>>>>>>>
>>>>>>>>>> I changed my idea a little bit. Now I think you are right. We
>>>>>>>>>> should only evaluate the first argument in your example. It’s not only
>>>>>>>>>> because of inconsistent, but also because the language should at least
>>>>>>>>>> provide a way to “short-circuit” to rest of the arguments.
>>>>>>>>>>
>>>>>>>>>> If they want to opt-out this behavior, they can always write:
>>>>>>>>>>
>>>>>>>>>> ```
>>>>>>>>>> let x = bar(42)
>>>>>>>>>> let y = baz(42)
>>>>>>>>>> foo(x?, y?)
>>>>>>>>>> ```
>>>>>>>>>>
>>>>>>>>>
>>>>>>>>> Well, that was just the easy part. Now, suppose bar is the
>>>>>>>>> function that isn't optional.
>>>>>>>>>
>>>>>>>>> ```
>>>>>>>>> foo(bar(42), baz(42)?)
>>>>>>>>> ```
>>>>>>>>>
>>>>>>>>> Is bar evaluated if baz returns nil? If you want this syntax to be
>>>>>>>>> sugar for if let, then the answer is yes.
>>>>>>>>>
>>>>>>>>
>>>>>>>> s/yes/no/
>>>>>>>>
>>>>>>>>
>>>>>>>>> If short-circuiting works left-to-right, then the answer is no.
>>>>>>>>>
>>>>>>>>
>>>>>>>> s/no/yes/
>>>>>>>>
>>>>>>>> (See? Confusing.)
>>>>>>>>
>>>>>>>>
>>>>>>>>> This is very confusing, and there is no good intuitive answer.
>>>>>>>>>
>>>>>>>>>
>>>>>>>>>> _______________________________________________
>>>>>>>>>> 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/20160925/ce2b23e8/attachment.html>


More information about the swift-evolution mailing list