Why doesn't swift allow for let-based decisions in ternaries? - swift

It's possible to do the following in Swift:
let params: String
if let aString = someOptionalString {
params = "?someparam=\(aString)"
} else {
params = ""
}
However it would be much more concise if I could write it like:
let params = let aString = someOptionalString ? "?someparam=\(aString)" : ""
Or
let params = case let aString = someOptionalString ? "?someparam=\(aString)" : ""
However this doesn't compile in any way I could think about. Is this possible? If not, why not? And is there a way to suggest it to be implemented or can I only try to first add this myself to the Swift project and then propose the change to the community?

Because mapping an optional is a much more sensible choice:
let params = someOptionalString.map{ "?someparam\($0)" } ?? ""

To answer your question as given, conditional let only applies in an if statement or similar branching construct, everywhere else it doesn't have a value.
Normally when you have a pattern like:
if let x = y {
doSomething(x)
}
...what you're doing is declaring a new namespace context inheriting from the current one where x is defined; whether the code enters that context depends on whether the assigning expression evaluates to nil. Outside the block, x is not defined and it's an error to refer to it. If you like, think of it as a closure, something that might look like:
callIfNotNil(y, {x in doSomething(x)})
When you do a let otherwise, you are defining it in the current namespace, which means it can't not be defined on the other side of the ternary operator, so the best the compiler could give you is String! as a type, which would defer the nil check to runtime, largely defeating the point of using it.
In principle ternary could apply the same behaviour by defining an implicit block in the middle of the expression, but that's a recipe for confusion on the part of the programmer.
As for why let x = true; let y = x ? 1 : 2 is valid but let y = let x = true ? 1 : 2 isn't, there are some trivial precedence problems there, and with the let keyword being a compile-time feature not a runtime one it would be misleading to allow it mid-expression.

Related

Binary operator '-' cannot be applied to two 'Double?' operands

var result = (dimensione-base)/2
dimensione & base are two "Double?" number, how can i solve this?
i want to calculate this, but he always gives me this error:"Binary operator - cannot be applied to two "double?" operands.
Double? is an optional type. That means a variable of this type can be nil. So before making an operation with them, you need to make sure these variables actually hold a value :
if let dimensione = dimensione,
let base = base
{
var result = (dimensione-base)/2
// Use result here
}else{
// Do something here when dimensione or base is nil.
}
You could also prefer to assign them a default value when they are nil (but in this case it seems less secure) :
var result = ((dimensione ?? 0)-(base ?? 0))/2
Here, if nil, 0 will be used instead.

Why outside of block swift can't see value assigned to a uninitialized variable in the block?

What is the mechanism of declaring w/o value in Swift5 ? Does the first assign become the real declaration ?
And, should we avoid to declare without value in Swift?
var v:String;
if true {
v = "Hello"
print(v) // print "Hello" when without the print below
}
print(v) // variable 'v' used before being initialized
var v:String="";
if true {
v = "Hello"
print(v) // print "Hello"
}
print(v) // print "Hello"
Well, the message is not very helpful, and that's the problem. This pattern (which I call computed initialization) is perfectly legal and useful and — amazingly — you can even use let instead of var. But you must initialize the uninitialized variable by all possible paths before you use it. So you have:
var v:String
if true {
v = "Hello"
}
print(v) // error
But hold my beer and watch this:
var v:String
if true {
v = "Hello"
} else {
v = "Goodbye"
}
print(v) // fine!
Or even:
let v:String
if true {
v = "Hello"
} else {
v = "Goodbye"
}
print(v) // fine!
Amazing, eh?
Now, you might say: OK, but true will always be true so it's silly to make me fulfill the "all paths" rule. Too bad! The compiler insists anyway, and then lets you off later with a warning that the else won't be executed. But a warning lets you compile; an error doesn't. The truth is that your example is very artificial. But this is a real-life possibility:
let v:String
if self.someBoolProperty {
v = "Hello"
} else {
v = "Goodbye"
}
print(v) // fine!
Not only is this sort of thing legal, it is actually the pattern that Apple recommends under certain slightly tricky circumstances. For instance, it is used in Apple's own example code showing how to use the Swift 5 Result struct:
let result: Result<Int, EntropyError>
if count < AsyncRandomGenerator.entropyLimit {
// Produce numbers until reaching the entropy limit.
result = .success(Int.random(in: 1...100))
} else {
// Supply a failure reason when the caller hits the limit.
result = .failure(.entropyDepleted)
}
So this is because swift compiles your code and notices that your value var v:String; is undeclared before being used which makes it an "optional" value. Even though you are assigning it within the if statement, if you were to get rid of the true value it is possible that the if statement would never run therefore no value will ever be stored in v, thus it would be used before "assigned".
So to answer your question if you want your value to be an optional and possible empty value declare v as the following var v:String? if you would like it to be a non-optional value with a value always stored within v you should declare it as the following var v = "". Swift will interpret this declaration as a String.
To answer your second question, defining without values in swift is 100% allowed, it really just depends on how you want to handle your code. I use optional values in my code all the time, but I don't necessarily like optionals so i usually like to declare my values such as var v = "" that way if the value is empty my UI or whatever else i'm manipulating won't break. But if i need to ensure a value is present i will have to make my value optional so i can use an if statement to check whether it's a valid value or not.
Shorter version of what I'm trying to say is, you are receiving the compiler warning because you are declaring v as a non-optional value rather than an optional value.

Array of String printing Optional, why?

I am playing with Arrays in playground and I am bit confused. Here is code:
var players = ["tob", "cindy", "mindy"] //["tob", "cindy", "mindy"]
print(players.isEmpty) // False
var currentPlayer = players.first // "tob"
print(currentPlayer) // "Optional("tob")\n"
Why does it says "Optional"?
I found explanation: "The property first actually returns an optional, because if the array were empty, first would return nil."
But it is not empty. .isEmpty //false, So I am not understanding this.
Thanks for help in advance.
The correct way to think of Optional is that this may or may not have a value. What is the first element of an empty list? There is no such thing. It is not a value. We call that lack of a value nil or .None.
In Swift a variable must have a specific type. So your example:
let currentPlayer = players.first
What is the type of currentPlayer? It may be a String, or it may be nothing at all. It is a "maybe string" and in Swift that's called an Optional<String>. Whether players has elements or doesn't have elements doesn't change the type of currentPlayer.
If you want to do something if-and-only-if the variable has a value, then there are many ways. The simplest is if-let.
let players = ["tob", "cindy", "mindy"] //["tob", "cindy", "mindy"]
print(players.isEmpty) // False
if let currentPlayer = players.first {
print(currentPlayer)
}
This will print tob as you're expecting.
Another very common approach is the guard let
let players = ["tob", "cindy", "mindy"] //["tob", "cindy", "mindy"]
guard let currentPlayer = players.first else { return }
print(currentPlayer)
This lets you avoid nesting the rest of your function inside of curly braces, but otherwise is the same approach.
It is possible to convert an Optional into its underlying type using !, but this is very dangerous and should be avoided except where absolutely necessary. Tools like if-let and guard-let (and also Optional.map) are almost always preferred.
But the key here is to understand that all Swift variables have a single type, and sometimes that type is "maybe it has a value, maybe it doesn't."
If we look at the description of first, we will see that it always returns optional type:
public var first: Self.Generator.Element? { get }

"if let" statement executed despite value being nil

I have an "if let" statement that is being executed, despite the "let" part being nil.
if let leftInc : Double? = self.analysis.inputs[self.btnLeftIncisor.dictionaryKey!]! {
println(leftInc)
let valueString : String = formatter.stringFromNumber(NSNumber(double: leftInc!))!
self.leftIncisorTextField?.text = valueString
self.btnLeftIncisor.associatedLabel?.text = valueString
}
// self.analysis.inputs is a Dictionary<String, Double?>
The inputs dictionary holds information entered by the user - either a number, or nil if they haven't entered anything in the matching field yet.
Under the previous version of Swift, the code was written as this:
if let leftInc : Double? = self.analysis.inputs[self.btnLeftIncisor.dictionaryKey!]?? {
and worked correctly.
I saw a similar question here, but in that instance the problem seemed to be the result of using Any?, which is not the case here.
Swift 2.2
In your if let you define another optional, that's why nil is a legitimate case. if let is intended mainly to extract (maybe) non optional value from an optional.
You might try:
if let leftInc : Double = self.analysis.inputs[self.btnLeftIncisor.dictionaryKey!].flatMap ({$0}) {
// leftInc is not an optional in this scope
...
}
Anyway I'd consider to not do it as a one liner but take advantage of guard case. Just in order to enhance readability. And avoid bang operator (!).
The if-let is for unwrapping optionals. You are allowing nil values by setting the type to an optional Double.
The if statement should be:
if let leftInc = self.analysis.inputs[self.btnLeftIncisor.dictionaryKey!] as? Double{
...
}
This will attempt to get an object out of inputs, if that fails it returns nil and skips it. If it does return something it will attempt to convert it to a Double. If that fails it skips the if statement as well.
if inputs is a dictionary like [Something:Double] then you don't need the last as? Double as indexing the dictionary will return a Double?
I recommend reading the swift book on optional chaining.
You could break it down further -
if let optionalDouble = self.analysis.inputs[self.btnLeftIncisor.dictionaryKey!], leftInc = optionalDouble {
....
}
as your dictionary has optional values - this way of writing it might make it clearer what's going on
if let k = dict["someKey"]{}, dict["someKey"] will be an object of type Any
this can bypass a nill
So do a typecast to get it correct like if let k = dict["someKey"] as! String {}

Why there is a ? mark in if-let statement to see an optional has a value

I am reading the following book, on Page #32 there is a code snippet. December 2014: First Edition.
Swift Development with CocoaJonathon Manning, Paris Buttfield-Addison,
and Tim Nugent
I know we can use ? to make a vairbale optional and ! to unwrap a optional vairbale in Swift
var str: String? = "string"
if let theStr = str? {
println("\(theStr)")
} else {
println("nil")
}
Why do we need ? in this line if let theStr = str? It seems working just fine with out it and making no difference.
You don't need it, and shouldn't have it. The optional binding evaluates the optional. If it's nil, it stops. If it's not nil, it assigns the value to your required variable and then executes the code inside the braces of the if statement.
EDIT:
The language has changed slightly since it was first given out in beta form. My guess is that the ? used to be required.
Some of the sample projects I've used from Github fail to compile and I've had to edit them to get them to work. this might be an example where the syntax has changed slightly.
The current version of Swift does not require it, as it is redundant since your variable is already an optional.
Whatever you put in the if let statement does have to be an optional though or you will receive an error
Bound Value in a conditional binding must be of Optional type
Furthermore, if you are casting to a type, you do need to use as? to cast to an optional type.
var str2: Any = ["some", "example"]
if let theStr = str2 as? [String] {
println("\(theStr)")
} else {
println("nil")
}