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

Xiaodi Wu xiaodi.wu at gmail.com
Tue Aug 16 14:12:32 CDT 2016


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.


> I'm always willing to discuss the problem, and I don't mind to reevaluate
> my ideas if I was wrong. I know my solution is not ideal, and that's why I
> posted it on the mailing list.
>
> And you spent all your time arguing against my solution but provide no
> helpful suggestions.
>

The purpose of my replies was to convince you that your solution cannot be
salvaged, and that in light of this shortcoming, the current status quo has
virtue. In other words, I want to convince you that you should *like* and
*be proud of* your code that uses `guard let x = x else { return }`!


> I know you care about swift. I do too. But please, discussing something is
> always better than discussing nothing.
>

Not always true, IMO. But if you think that you have a novel solution to a
problem, I hope that nothing I (or anyone else) on this list has said would
discourage you from coming forward and sharing it! I am engaging because
I'm actively interested in debating these points with you.


> I'm not going to write a proposal. I'm not going to waste the core team's
> time. I don't want to win the debate. I want swift to be a better language.
> If you don't like an idea, simply say your concerns and move on to the next
> thread. I really appreciate your first email. But I don't know why, then
> you spent all your email just trying to prove I was wrong. At least I tried
> to discuss a few alternative solutions. Maybe I should stop the meaningless
> discussion a day ago.
>

The purpose of the replies was to push you to address, in your proposed
solutions, the big shortcoming that you have not addressed. I take it as
given that there is a desire for a facility similar to what you propose.
However, the big shortcoming is that short-circuiting introduces control
flow that is difficult to reason about.

In the past, this shortcoming was serious enough that the core team decided
against implementing the feature. Therefore, at least IMO, it does not
advance the discussion to continue arguing that the sugar is nice (I don't
particularly feel the need, but I wouldn't strictly be opposed to some
sugar there). And, it is no use trying to argue that the short-circuiting
problem is not serious, which is the argument you have repeated multiple
times.

A successful proposal would demonstrate an adequate _solution_ to the
short-circuiting problem. I have no such solution and don't believe such a
solution could exist, which is why I think your proposal can't be salvaged.
But if you believe it's important to keep exploring your proposal, then
I'll keep prompting you to address the issue, because IMO that is the
barrier to the proposal becoming a reality and I would want to help you get
there. Now, if you don't want to write a proposal on this topic, then
there's little point in continuing the discussion along those lines.


> I know you have spent way more time in the swift evolution process than
> me. I respect your opinion. But I'm afraid your attitude will make other
> newcomers afraid to speak.
>

I sincerely apologize if anything I've said has been unwelcoming to you.


> Last time I checked with the data. It shows that we are experiencing a
> decrease in the mailing list volume. Let me be clear, I'm not saying it's
> because of you. But this is something important that we should be aware of.
>
> Other people who disagree with me replied with a few alternatives (or how
> they choose to solve this problem) and I really like their ideas.
>
> If you believe me, I'm saying everything from my heart because I want
> swift to be a better language. I don't want to win the debate.
>
> Thank you. But we are wasting our time. Let's end our discussions now,
> unless you change your attitude or can provide some useful information.
> I'll try to find other possible solutions / workarounds to this problem.
>
> Sincerely,
> Justin
>
> On Aug 16, 2016, at 11:31 PM, Xiaodi Wu <xiaodi.wu at gmail.com> wrote:
>
> We don't design the language in a vacuum. If statements can short-circuit
> and function calls can't. You are proposing a function call that can
> short-circuit. This severely violates user expectations.
> On Tue, Aug 16, 2016 at 10:03 Justin Jia <justin.jia.developer at gmail.com>
> wrote:
>
>> I will reply both of your email in this simple email.
>>
>>
>> On Aug 16, 2016, at 10:26 PM, Xiaodi Wu <xiaodi.wu at gmail.com> wrote:
>>
>> Top-replying because Google is forcing me to:
>>
>> If you want to print an error for all early exits, declare a variable to
>> keep track of exit status such as `var isEarlyExit = false`, then use a
>> defer block that prints `error` only after checking `isEarlyExit` (or, you
>> know, design your code so that `error` itself would be nil if you're
>> exiting without an error).
>>
>>
>> IMHO `var is EarlyExist = false` is really ugly. Sometimes we can’t
>> design our code so that `error` itself would be nil if existing without an
>> error simply because we de depending on Cocoa Touch and many third party
>> frameworks.
>>
>> It is not "really bad" if your code "fails" unless the lines of code are
>> executed in the explicitly written order. There are no tricks hidden in
>> that behavior: lines of code are *supposed* to be executed from top to
>> bottom in the absence of a control flow statement, because Swift is a
>> procedural programming language. Proceeding from one line to the next is
>> the absolute most primitive flow of control.
>>
>>
>> Not always, but sometimes. Maybe I should say it’s “better” if changing
>> the order of the code won’t produce any unintentional behaviors? We are
>> talking about how to improve Swift, right?
>>
>> `guard` and `defer` were introduced in a later version of Swift to solve
>> a practical problem encountered in daily use, the nested pyramid of doom
>> from too many `if let` blocks. The point is that `guard` and `defer`
>> together constitute an ingenious and *complete* solution to that problem;
>> you have not shown me any scenario that cannot be trivially refactored to
>> avoid nested blocks using these two language constructs. So more sugar is
>> not necessary to solve this problem.
>>
>>
>> Well, it’s Turing Complete. I can’t argue against it. But I can give you
>> an example that needs multiple defer. I think this greatly hinders
>> readability. Also, I think making our code less order independent is
>> already important enough.
>>
>> "This is not explicit enough" *is* an argument against almost any sugar
>> you can propose. I think you are seeing why the core team is actively
>> discouraging sugar proposals on this list. Unless something comes along
>> that totally blows the alternative out of the water, I'm inclined to agree
>> that more sugar is almost a non-goal for Swift.
>>
>> (What would be something that could change my mind? Here would be my
>> criteria:
>>
>> * The non-sugared version is extremely painful to write (>>5 LOC, maybe
>> >>20), difficult to write correctly, and even if correctly written, does
>> not express the intended solution clearly to the reader.
>>
>>
>> I don’t know how many time you spent on writing swift code in the past. I
>> also don’t know whether your code depends on Cocoa or not. At least,
>> personally, this is the no.1 request in my wish list. I think `if let` is
>> extremely painful to write (not because >>20, but because it occurs too
>> often and keeps bugging me). Maybe you feel differently. Then it’s really
>> hard for me to convince you and it’s also really hard for you to convince
>> me. Time will tell how many developers want this feature.
>>
>> * There is a single, overwhelmingly obvious, universally or nearly
>> universally appropriate solution, and the proposed sugar would always be a
>> shorthand for that one solution.
>>
>>
>> If we choose to reinvent if statements, short-circuiting will not be a
>> nearly universally appreciate solution. Not even close.
>>
>> Something like a copy-on-write attribute would fit the bill, because good
>> luck implementing that by hand over and over again, and if you're a reader
>> of code, good luck verifying that all that code does what you think.)
>>
>>
>> Maybe. But’s that’s another story.
>>
>> I have already explained why your proposal is not at all like optional
>> chaining. Your proposal hides complicated control flow changes, but
>> optional chaining does not.
>>
>> It does not do you any good to argue that "most people won't nest
>> functions inside functions". First of all, that's an unbelievable claim.
>> Second of all, computed properties can have side effects, since they are
>> essentially functions under the hood. Have you never referred to `foo.bar`
>> inside a function call? You literally cannot know if a property is
>> computed, potentially with side effects, unless you inspect the source
>> code. Thus, a programmer cannot know if they "choose to nest functions
>> inside functions". It does not matter if they are a genius.
>>
>>
>> Same apply to if statement. IMO, this paragraph can be used to argue
>> against all statements that will short-circuit in some way. I’m still not
>> convinced why `if` can be used but the proposed solution can’t.
>>
>> On Mon, Aug 15, 2016 at 23:56 Justin Jia <justin.jia.developer at gmail.com>
>> wrote:
>>
>>> On Aug 16, 2016, at 1:51 AM, Xiaodi Wu <xiaodi.wu at gmail.com> wrote:
>>>
>>> On Mon, Aug 15, 2016 at 12:31 PM, Justin Jia <
>>> justin.jia.developer at gmail.com> wrote:
>>>
>>>>
>>>> Since you mentioned do and defer:
>>>>
>>>> ```
>>>> func foo(wantsToBreak: Bool) {
>>>>     out: do {
>>>>         defer { print("Hello, world!") }
>>>>         guard wantsToBreak else { break out }
>>>>     }
>>>>     print("End of function.")
>>>> }
>>>>
>>>> foo(wantsToBreak: true) // Output: Hello, world!\nEnd of function.
>>>> foo(wantsToBreak: false) // Output: Hello, world!\nEnd of function.
>>>> ```
>>>>
>>>> Do you think this is confusing?
>>>>
>>>
>>> No, I don't. But I also don't see why you would put `defer` inside `do`
>>> like that. `defer` and `guard` can be used profitably without nesting
>>> inside blocks.
>>>
>>>
>>>
>>> Because I don’t want `defer` to execute outside do block. Let me give
>>> you a simplified example: I wanted to print error for all early exits
>>> except normal return (reaches last line). I would like to use defer
>>> otherwise I need to write `else { print(error); return }` for all guards.
>>> The intuitive way of achieving this for me was to nest defer inside do
>>> blocks. But it turned out that defer will be executed even if you choose to
>>> break a block. I’m not arguing this is a bad design decision. My point is:
>>> sometimes non-intuitive design decisions are non-avoidable.
>>>
>>>
>>> At least it confused me in the fast. However, defer is still very useful.
>>>>
>>>> Even if I choose to use guard, defer and do, it will still look like
>>>> the one with `if let`. Lots of blocks. The code should be straightforward
>>>> without any brackets.
>>>>
>>>
>>> Huh? I don't buy this argument at all. You don't like the look of `{ }`,
>>> so you are proposing new sugar using `?`--is that what you're claiming?
>>> This sounds to me like the same motivation as that behind early suggestions
>>> to move to a Python-like syntax.
>>>
>>>
>>>> See this example (since it’s a lot of code I rendered a PDF).
>>>>
>>>
>>> I don't see the motivation in this example. Why wouldn't you just move
>>> the code to update `cell.heading` right after you guard that `imageName` is
>>> not nil?
>>>
>>>
>>>
>>> I already explained why. It was just a naive example. In real life
>>> methods can be a lot more complicated than my example. It’s really bad if
>>> your code will fail unless it follows the same exact order. We need to
>>> modify our code everyday, and most of the time we are working on code that
>>> is not even written by ourselves. If you scan through methods with name
>>> like updateCell, intuitively, you will think the order of the code will not
>>> matter. And it shouldn’t! It is really easy to make mistakes with guard
>>> statement because the order matters here. IMO, guard is only useful if we
>>> place it at the beginning of the function—for all or nothing.
>>>
>>> Why we chose to use brackets and indentation? Because they can warn us
>>> that the behavior of the code will change. Either the outcome will vary
>>> (if) or the code will be executed for more than one time (for). Checking an
>>> object if is nil doesn’t always belong here. Using `if let` is not being
>>> explicit. It’s boilerplate. A not-so-good fix for the side effect of
>>> optionals. Most of the time, we want the flow to be “flat”. That’s why
>>> swift supports `guard` and `object?.method`. If you think `foo(x?)` is not
>>> important, do you think `guard` and `object?.method` are also not important?
>>>
>>> I understand that Swift is designed to be explicit. I also agree with
>>> it. But I saw an unhappy trend in the mailing list: "this is not explicit
>>> enough" can be used to argue against anything. Shall we remove
>>> @autoclosure? Shall we remove trailing closures? Shall we remove
>>> `object?.method`?
>>>
>>> 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@
>>>>> 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?
>>>>>>
>>>>>
>>>>> No, it certainly doesn't! I'm saying that you haven't come up with a
>>>>> solution to a known problem with the idea.
>>>>>
>>>>>
>>>>>>
>>>>>> Assuming we have the following declaration:
>>>>>>
>>>>>> ```
>>>>>> func foo(a: Int, b: Int?, c: Int, d: Int?) -> Int
>>>>>> ```
>>>>>>
>>>>>> For this:
>>>>>>
>>>>>> ```
>>>>>> let z = foo(a: f1(), b: f2()?, c: f3(), d: f4()?) // z becomes
>>>>>> optional
>>>>>> ```
>>>>>>
>>>>>> We have a few different “possible solutions”:
>>>>>>
>>>>>> 1. Short-circuiting from left to right. This is equivalent to:
>>>>>>
>>>>>> ```
>>>>>> var z: Int? = nil
>>>>>> let a = f1()
>>>>>> guard let b = f2() else { return }
>>>>>> let c = f3()
>>>>>> guard let d = f4() else { return }
>>>>>> z = foo(a: a, b: b, c: c, d: d)
>>>>>> ```
>>>>>>
>>>>>> 2. Short-circuiting from left to right for optionals. Then evaluate
>>>>>> non-optional parameters. This is equivalent to:
>>>>>>
>>>>>> ```
>>>>>> var z: Int? = nil
>>>>>> guard let b = f2() else { return }
>>>>>> guard let d = f4() else { return }
>>>>>> let a = f1()
>>>>>> let c = f3()
>>>>>> z = foo(a: a, b: b, c: c, d: d)
>>>>>> ```
>>>>>>
>>>>>> 3. Do not short-circuiting.
>>>>>>
>>>>>> ```
>>>>>> var z: Int? = nil
>>>>>> let a = f1()
>>>>>> let optionalB = f2()
>>>>>> let c = f3()
>>>>>> let optionalD = f4()
>>>>>> guard let b = optionalB else { return }
>>>>>> guard let d = optionalD else { return }
>>>>>> z = foo(a: a, b: b, c: c, d: d)
>>>>>> ```
>>>>>>
>>>>>> Like I said before, I agree that there is no intuitive solution to
>>>>>> this problem. However, I'm still not convinced that this feature is *not
>>>>>> important*.
>>>>>>
>>>>>> Thank you for pointing out the problem to me. I didn't notice it at
>>>>>> the time I wrote my first email. I really appreciate that. However, instead
>>>>>> of saying I don't know which is the best solution so let's assume the core
>>>>>> team made the right decision, we should discuss whether 1, 2, 3 is the best
>>>>>> solution. Or you can convince me we don't *need* this feature.
>>>>>>
>>>>>
>>>>> I'm going to convince you that 1, 2, and 3 are all bad solutions.
>>>>> Thus, this feature won't fly.
>>>>> The fundamental issue is that having this sugar means that I can no
>>>>> longer reason about the order in which code is executed. An innocuous
>>>>> statement such as `print(a(), b(), c(), d())`, once you mix in your
>>>>> proposed `?` syntax with some but not all of these function calls, might
>>>>> have d() executed before a(), after a(), or not at all. This is greatly
>>>>> damaging to the goal of writing clear, understandable code.
>>>>>
>>>>>
>>>>>>
>>>>>> 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
>>>>>>>>
>>>>>>>>
>>>>
>>>>
>>>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160816/dbc21cd8/attachment.html>


More information about the swift-evolution mailing list