I see a pattern of myself writing code like this now: ``` func foo(x: Int?) -> Int? { guard x = x else { return nil } /* normal function */ } ```
This is even worse than the x? syntax. But sometimes I have no other choice. Most of my functions are without side effects and… I don’t want to write if let x = x every time I call this function. > On Aug 16, 2016, at 12:56 PM, Justin Jia <justin.jia.develo...@gmail.com> > wrote: > >> >> On Aug 16, 2016, at 1:51 AM, Xiaodi Wu <xiaodi...@gmail.com >> <mailto:xiaodi...@gmail.com>> wrote: >> >> On Mon, Aug 15, 2016 at 12:31 PM, Justin Jia <justin.jia.develo...@gmail.com >> <mailto:justin.jia.develo...@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...@gmail.com >>> <mailto:xiaodi...@gmail.com>> wrote: >>> >>> On Mon, Aug 15, 2016 at 12:07 PM, Xiaodi Wu <xiaodi...@gmail.com >>> <mailto:xiaodi...@gmail.com>> wrote: >>> On Mon, Aug 15, 2016 at 11:43 AM, Justin Jia >>> <justin.jia.develo...@gmail.com <mailto:justin.jia.develo...@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...@gmail.com >>>> <mailto:xiaodi...@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.develo...@gmail.com >>>> <mailto:justin.jia.develo...@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...@gmail.com >>>> <mailto:xiaodi...@gmail.com>> wrote: >>>> >>>>> On Mon, Aug 15, 2016 at 3:55 AM, Xiaodi Wu <xiaodi...@gmail.com >>>>> <mailto:xiaodi...@gmail.com>> wrote: >>>>> On Mon, Aug 15, 2016 at 3:25 AM, Justin Jia via swift-evolution >>>>> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote: >>>>> >>>>>> On Aug 15, 2016, at 4:09 PM, Charlie Monroe <char...@charliemonroe.net >>>>>> <mailto:char...@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@swift.org <mailto:swift-evolution@swift.org> >>>>> https://lists.swift.org/mailman/listinfo/swift-evolution >>>>> <https://lists.swift.org/mailman/listinfo/swift-evolution>
_______________________________________________ swift-evolution mailing list swift-evolution@swift.org https://lists.swift.org/mailman/listinfo/swift-evolution