Over on Swift-Users, Dan T asks:
Currently I do stuff like this:
let dobString: String if let dob = dob { dobString = serverDateFormatter.stringFromDate(dob) } else { dobString = "" }Is there a better, more idiomatic, way to do this sort of thing?
I’m going to assume that serverDateFormatter
is an instance of NSDateFormatter
. If so, what Dan’s going for might be better expressed like this :
let dobString: String = { guard let dob = dob else { return "" } return serverDateFormatter.string(from: dob) }()
I think it’s a lot cleaner, and it only mentions dobString
once. (If he’s not using NSDateFormatter
and needs a second level of unwrapping, the guard let statement just needs a second clause that performs a conditional binding on the results of stringFromDate
.)
Update: Tim Vermeulen has a much nicer one-line solution.
let dobString = dob.flatMap(serverDateFormatter.stringFromDate) ?? ""
I’m pretty sure that this can use map
as well as flatMap
, which saves you four more characters.
Got a better solution? Toss it into the comments, tweet it, or hop onto the Swift-Users email list.
12 Comments
In this particular case, DateFormatter inherits from Formatter, which has the optional taking and returning string(for:). So you can write:
let dobString = serverDateFormatter.string(for: dob) ?? “”
In general I like this style, for any situation where it makes sense to provide an optional taking and returning version of a method.
Oh nice. I didn’t know about that!
let dobString = dob.flatMap(serverDateFormatter.stringFromDate) ?? “”
How about a more functional style?
let dobString = serverDateFormatter.string(from: dob ?? “”)
Nice tweak!
This one relies on an internal implementation detail of the formatter class, instead of being explicit about the default value, which would be less “clean”.
let dobString = serverDateFormatter.stringFromDate(dob) ?? “”
How about this one?
let dobString = dob.map {
serverDateFormatter.string(from: $0)
} ?? “”
I still like Tim’s better, but:
dobString = (dob != nil) ? serverDateFormatter.stringFromDate(dob!) : “”
How exactly is the one line “nicer”? It will be much harder to maintain for the original author and others who have to read it later.
Don’t turn Swift into Perl, thank you.
I agree that the “functional” versions presented are not so appealing (although Tim Vermeulen’s, in Erica’s update is not bad).
Unfortunately the truly “more functional” version is not possible in Swift. And sadly this seems to be by design (I don’t have a reference handy but there was a design document that stated that Expression Oriented programming was not something they were going to support).
In a truly functional language everything is an expression. A hypothetical Functional version of Swift would let you do this:
let dobString = if let dob = dob { serverDateFormatter.stringFromDate(dob) } else { “” }
You might argue that the version using map is shorter. But I’d argue that it’s hard to beat the combination of clarity and succinctness above.
You might also argue that the mapping approach is more functional because it involves lifting – but that’s just a tool you can use when appropriate. It’s fine here but I think the direct expression is still more… direct.
And it scales much more nicely to switches too – you can switch over an enum and each case evaluates to something – and the whole expression can be assigned to a variable with no mutability required (you can also use the closure approach for that case, and in fact is what I usually do there, but it’s a workaround for the language limitation).
I think just `dob.map` is more appropriate than `flatMap`.
One question I would have is: how is this value going to be used? Is it for setting the text on a label? If that’s the case I might not even use the nil coalescing operator (`??`) at all. for example:
myLabel.text = dob.map(dateFormatter.stringFromDate)
So, in this example, since `text` is optional in the first place, there may really no reason for me to make sure I’m not dealing with nil.
I think one also needs to be careful of abusing the nil coalescing operator—in the proposed flatMap example, even though personally I understand it exactly, it’s not exactly what I would describe as “clean code.” Even though more verbose an `if let` may be more appropriate. `guard` can be nice too, although I think I’ve seen a lot of people going overboard with guard when an if-let might actually be cleaner.