[swift-evolution] [Idea] Use optionals for non-optional parameters
Xiaodi Wu
xiaodi.wu at gmail.com
Tue Aug 16 10:31:50 CDT 2016
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 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?
>>>>>
>>>>
>>>> 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/02062aff/attachment.html>
More information about the swift-evolution
mailing list