1

I am asking for user input (this works) and trying to output different results depending on if the input was nil, an empty string, or a non-empty string using a switch clause (doesn't work).

The first attempt got me an error because I'm trying to compare an optional string with a non-optional string:

import Foundation

print("Hi, please enter a text:")

let userInput = readLine(stripNewline: true)

switch userInput {
    case nil, "": // Error: (!) Expression pattern of type ‘String’ cannot match values of type ‘String?’
        print("You didn’t enter anything.")
    default:
        print("You entered: \(userInput)")
}

Fair enough, so I create a optional empty string to compare to:

import Foundation

print("Hi, please enter a text:")

let userInput = readLine(stripNewline: true)
let emptyString: String? = "" // The new optional String

switch userInput {
    case nil, emptyString: // Error: (!) Expression pattern of type ‘String?’ cannot match values of type ‘String?’
        print("You didn’t enter anything.")
    default:
        print("You entered: \(userInput)")
}

So that gives me an error saying I cannot compare a ‘String?’ to a ‘String?’.

Why is that? Are they somehow still not the same type?

PS. I have the feeling that I'm missing something fundamental here, such as what Troy pointed out about optionals not being "the same type as the corresponding non-otpional type but with the additional possibility of not having a value" (not an exact quote, see his UPDATE at the end of the question: What does an exclamation mark mean in the Swift language?). But I'm stuck connecting the final dots why this isn't ok.

Community
  • 1
  • 1
Elias Mossholm
  • 150
  • 1
  • 11

4 Answers4

5

Replace

case `nil`, "":

with

case nil, .Some(""):

Note that optional is an enum with two possible values: .None and .Some(value).

As for your second example with String?, note that the matching in case is performed using ~= operator which is not defined for optionals.

If you define:

@warn_unused_result
public func ~=<T: Equatable>(lhs: T?, rhs: T?) -> Bool {
    return lhs == rhs
}

both your examples will start to work (not recommended).

Sulthan
  • 128,090
  • 22
  • 218
  • 270
  • Yes. `~=` is defined for `Equatable` operands, but optionals are not equatable even if the underlying wrapped type is. – `~=` is also defined for _OptionalNilComparisonType operands, that's why ([as I assume](http://stackoverflow.com/a/33209762/1187415)) the pattern `case nil:` works. – Martin R Jul 08 '16 at 14:54
  • You can also shorten the pattern `.Some("")` to `""?`. – Martin R Jul 08 '16 at 14:56
  • 1
    Totally agree, though I would suggest that this is likely a bug in the Swift compiler and may be worth a radar. The fact that pattern matching against `.Some(string)` works but not `optionalString` feels like an unintentional inconsistency. (Especially since Swift doesn't really ascribe to the "pattern matching as deconstruction" paradigm like Scala or the like.) – Rob Napier Jul 08 '16 at 14:56
  • @MartinR Exactly. I came to the same conclusion. I am not sure whether it's a bug or not. There might be a case I am missing. – Sulthan Jul 08 '16 at 14:58
3

Did you try to get your userInput text as a String:

let response = userInput ?? ""
switch response {
    case "": 
        print("You didn’t enter anything.")
    default:
        print("You entered: \(response)")
}

Then you would be sure it is the same type String.

I hope it works

Robin Delaporte
  • 575
  • 5
  • 15
  • I’m not allowed to compare `response` with `nil`, probably because `respones` is no longer an optional. I'm given the error `Expression pattern of type ‘_’ cannot match values of type ‘String’` – Elias Mossholm Jul 08 '16 at 14:57
  • Yes indeed, sorry, you can just compare it with a String expression. Then even if your value is nil in the first place, it will be converted to an empty string. So your printed message will appear – Robin Delaporte Jul 08 '16 at 15:00
  • However, removing `nil` from the equation should work (I’m just not sure how to provoke a `nil` event when running the program), see http://stackoverflow.com/questions/27439723/swift-optional-variable-assignment-with-default-value-double-question-marks. Thanks for pointing me in the right direction! – Elias Mossholm Jul 08 '16 at 15:03
  • 1
    @EliasMossholm: readLine return nil on a end-of-file condition. You can trigger that by pressing Control-D in a Terminal or the debugger console. – Martin R Jul 08 '16 at 15:14
1

Just as a supplement to the existing good answers: Instead of testing against nil and the empty string you can go the other way around and test against a non-nil string (with the input? pattern) and add a where constraint:

switch userInput {
case let input? where !input.isEmpty:
    print("You entered", input)
default:
    print("You did not enter anything")
}

Equivalently:

if case let input? = userInput where !input.isEmpty {
    print("You entered", input)
} else {
    print("You did not enter anything")
}

However: readLine() returns nil only on an end-of-file condition, which means that no data can be read anymore from standard input. Therefore it might make more sense to terminate the program in that case:

guard let userInput = readLine(stripNewline: true) else {
    fatalError("Unexpected end-of-file")
}
// `userInput` is a (non-optional) String now

if userInput.isEmpty {
    print("You did not enter anything")
} else {
    print("You entered", userInput)
}
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • When would an end-of-file condition happen in real life under these circumstances? (So as to understand why terminating the program is the better option.) – Elias Mossholm Jul 09 '16 at 08:50
  • @EliasMossholm: For example, if your program reads the input from a file (`myProg < inputData`). As soon as the end of the file is reached, readLine() returns nil. Therefore, if the input file does not contain enough data for your program, there is no way to continue. – The same happens when you call the program "directly" and enter Control-D. That will close the standard input of the program, and all following readLine() calls return nil. – Martin R Jul 09 '16 at 08:59
0

see the self-explanatory example ...

let txtArr: [String?] = [nil, "", "some text"]

for txt in txtArr {
    switch txt {
    case nil:
        print("txt is nil")
    case let .Some(t) where t.isEmpty:
        print("txt is empty string")
    default:
        print("txt is \(txt)")
    }
}

the code prints

txt is nil
txt is empty string
txt is Optional("some text")
user3441734
  • 16,722
  • 2
  • 40
  • 59