guard vs control-flow-aware typing?

I like the new guard syntax, but it surprised me. I was expecting some control-flow aware typing (or whatever it's called). I'm curious... do you think these are just two ways to do the same thing, or does the guard syntax offer some advantage?


// foo: Foo?
guard let foo = foo else { throw Error.NoFoo }
// Now, foo: Foo
foo.doStuff()

vs...

// foo: Foo?
if foo == nil { throw Error.NoFoo }
// Now, foo: Foo
foo.doStuff()


Rob

FWIW, with this:


struct a {
  let w: Int? = 1
  func b (z: Int?) {
  let x: Int? = 5
  guard let w = w else { return } // #1
  guard let x = x else { return } // #2
  let y = x
  guard let y = y else { return } // #3
  guard let z = z else { return } // #4
  print ("\(w)\(x)\(y)\(z)")
  do {
  let x = 1 // #5
  print ("\(x)")
  }
  }
}


I get compiler errors on #2 and #3. All of the others are accepted.

Since we now agree on the terminology, let me ask once again. Which option do you prefer:


  1. Allow if statements to create an implicit scope after their block is done, and still disallow normal let statements create implicit scopes, or
  2. Allow if statements to create an implicit scope after their block is done, and also allow normal let statements create implicit scopes, or
  3. Disallow everyone from creating implicit scopes.


Clearly you don't prefer 3, because that is what you are arguing against. Would you choose 2, and thereby allowing code like this:

let x : String?
let x = x!


In that case, why? And if not, why?


If you allow the implicit scopes, are other variables allowed to be shadowed from what looks like the same scope?


And perhaps most importantly, how do you communicate these rules to the programmers?


These are questions that are easy to ignore when you are suggesting changes to a language, but if you are designing one you have to think of the consequences. If you have a serious suggestion for a change to a language, of course you don't have to think of the consquences, you can make the suggestion anyway, but if you do, the suggestion is more likely to be good.

I'm not sure I understand the question completely. There are 4 different statements we could be talking about here:


1. if … { … }

2. if let x = … { … }

3. guard … else { … }

4. guard let x = … else { … }


In all 4 cases, there's a new scope inside the braces, which follows the compiler rules for new scopes inside braces. That's not what we're talking about.


In cases 1 and 3, there's no other scoping consideration, and so those cases are not what we're talking about either.


In cases 2 and 4, there's a scope-related side effect, but it's — intentionally — asymmetric. For 'if let', a new uwrapped x is defined inside the braces. For 'guard let', a new uwrapped x is defined for the rest of the current scope, and there's no new definition of x inside the braces. (I checked: if you try to refer to x inside the braces, you get a warning that it's not the x you think it is.)


Case 2 behaves as expected now, because it's always "just" shadowing, as we normally understand it, so it doesn't cause an error when x is a local variable.


Case 3 is weird, because one of three different things can happen (according to the tests I posted earlier):


a. If the original x is a property, the guard's new definition of x "shadows" the property in the rest of the scope


b. If x is a parameter, the new x "redefines" the parameter in the same scope, without an error (surprisingly)


c. If x is a local variable, the new x "redefines" the local variable, and thus produces the error


What I want is for 'guard let' not to produce a redefinition error, ever, so that it's usable in all the same cases as 'if let'. If I've done my accounting properly, that means everything is working reasonably (by my standards), except that case 3c should not produce an error.

"There has been some discussion in the old forums of having the compiler track nil-ness in the code so that it can eliminate unnecessary unwrapping without a special construct."


That'd probably be the nicest solution. I've only done a wee-bit of Swift. In ObjC..you usually don't have messages to nil propagate too deep...but thinking about peppering my code with all these

? ! if let guard...


Kind of makes me cringe a bit. Is it at all possible that this being over-thought?

As far as I can see, "guard let" works in the same places as "let" does. The "let" below is not redefining the argument y, it is shadowing it, because y is from an outer scope (outside the braces).


func foofunc(y: String?) {
    let y = 1
    print(y)
}


Think of "guard let x = y" as the same thing as "let x = y!" but with explicit error handling instead of a crash.

So, my question still stands, would you allow this code, i.e. should a let statement be able to create an implicit scope:

let x : String?
let x = x!
func foofunc(y: String?) {
    let y = 1
    print(y)
}


I would call the above a bug. Is there a rationale for allowing it?


>> would you allow this code,


let x : String?
let x = x!


No, I don't see any value in allowing that, at least not for the kinds of reasons that inspired 'if let' (which I regard as a different statement, with its own syntax and semantics).


To make that explict: I think 'if let' was invented to "declare" a section of source code where unwrapping had already happened. The fuss about adding 'guard let' is to get the same effect without needing a new explicit scope and its accompanying indentation. Therefore:


>> As far as I can see, "guard let" works in the same places as "let" does.


What I expect/wish is that 'guard let' works in the same places as 'if let' does.

I think I see your issue now. I definitely think you're wanting to use the guard statement in a way contrary to how it was intended... I think typically you'd use 'guard' at the very top of a function to early exit if the incoming parameters are other than expected. For use within the function, I think the idea is that you would use 'if let' like before. So to refactor your code above:


struct a {
    let w: Int? = 1
   
    func b (z: Int?) {
        guard let w = w else { return }
        guard let z = z else { return }

        let x: Int? = 5
        if let x = x {
            let y = x
            print ("\(w)\(x)\(y)\(z)")
            do {
                let x = 1 /
                print ("\(x)")
            }
        }
    }
}


I suppose it's not what you want, but I personally think this is fairly clear. The 'guard let' and 'if let' phrases each serve a distinct purpose: guards tell you that the function can exit early and under what conditions, 'if let' is a flow-control. Using a 'guard' deep within a function doesn't make a whole lot of sense, IMHO (tho I realize it's just an opinion).

Well, no, that's not the real issue.


The underlying issue is a programming pattern that doesn't have an official name (AFAIK), but that I call "mainline coding", where in any given method the normal path of execution is a series of unindented statements. Everything that is indented is an exceptional (error, optional, alternate) path of execution. This tends to make methods very easy to read, and somewhat more bug free, because you don't need to deduce the actual path of execution in two dimensions.


For example, here's how you would have to write some real code in Swift 1.0:


var error

if let data = NSData.readFromFile (…, &error) {

if let dictionary = NSJSONSerialization.JSONObjectWithData (data, 0, &error) as? NSDictionary {

return dictionary

}

else {

return somethingElse ()

}

}

else {

return somethingElse2 ()

}


Here's how it improved in Swift 2:


guard let data = try NSData.readFromFile (…) else { return somethingElse () }

guard let dictionary = try NSJSONSerialization.JSONObjectWithData (data, 0) as? NSDictionary else { return somethingElse2 () }


return dictionary


Suddenly it's obvious that this method is intended to return a dictionary. In the original, you have to hunt around for that information, and it's not even a complicated example.


Note that there's no presumption that this code is at the beginning of method. It might actually be towards the end. 'guard' is not limited to "early" exits, if that means exits at the top of a method.


P.S. I cheated a little bit with the comparative formatting, to make the difference more emphatic. In practice, the difference tends to be as dramatic as I've shown it here.

guard vs control-flow-aware typing?
 
 
Q