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 <[email protected]> wrote:
> I will reply both of your email in this simple email. > > > On Aug 16, 2016, at 10:26 PM, Xiaodi Wu <[email protected]> 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 <[email protected]> > wrote: > >> On Aug 16, 2016, at 1:51 AM, Xiaodi Wu <[email protected]> wrote: >> >> On Mon, Aug 15, 2016 at 12:31 PM, Justin Jia < >> [email protected]> 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 <[email protected]> wrote: >>> >>> On Mon, Aug 15, 2016 at 12:07 PM, Xiaodi Wu <[email protected]> wrote: >>> >>>> On Mon, Aug 15, 2016 at 11:43 AM, Justin Jia < >>>> [email protected]> 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 <[email protected]> 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 < >>>>> [email protected]> 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 <[email protected]> wrote: >>>>>> >>>>>> On Mon, Aug 15, 2016 at 3:55 AM, Xiaodi Wu <[email protected]> >>>>>> wrote: >>>>>> >>>>>>> On Mon, Aug 15, 2016 at 3:25 AM, Justin Jia via swift-evolution < >>>>>>> [email protected]> wrote: >>>>>>> >>>>>>>> >>>>>>>> On Aug 15, 2016, at 4:09 PM, Charlie Monroe < >>>>>>>> [email protected]> 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 >>>>>>>> [email protected] >>>>>>>> https://lists.swift.org/mailman/listinfo/swift-evolution >>>>>>> >>>>>>> >>> >>> >>
_______________________________________________ swift-evolution mailing list [email protected] https://lists.swift.org/mailman/listinfo/swift-evolution
