To your question about the type-casting, you can definitely simplify it:
case .getName:
return .success("Johnny Appleseed" as! T)
This is ok if asking for the wrong type should be considered a programming error (and so should crash), and the results will never come from external sources. If the data could ever come from an external source, then you should never crash in response to it being wrong.
In that case, we should model that kind of error:
enum FetchError : Error {
case unknownKey
case invalidType
}
Then you can have a syntax very close to what you like by adding a function to do the (possibly failing) type conversion:
func fetchValue<Value>(_ key:FetchKey) -> Result<Value, FetchError> {
func checkType(_ value: Any) -> Result<Value, FetchError> {
guard let value = value as? Value else { return .failure(.invalidType) }
return .success(value)
}
switch key {
case .getName: return checkType("Johnny Appleseed")
case .getWidth: return checkType(25.0)
@unknown default: return .failure(.unknownKey)
}
}
That said, I'd do it this way to avoid the ugliness of required type annotations:
func fetch<Value>(_: Value.Type, forKey key: FetchKey) -> Result<Value, FetchError> { ... }
let r = fetch(String.self, forKey: .getName)
This follows the pattern of Codable.
Here's the whole solution together in one place in a few different ways:
Returning Result
enum FetchError : Error {
case unknownKey
case invalidType
}
enum FetchKey {
case width
case name
}
func fetch<Value>(_: Value.Type, forKey key: FetchKey) -> Result<Value, FetchError> {
func checkType(_ value: Any) -> Result<Value, FetchError> {
guard let value = value as? Value else { return .failure(.invalidType) }
return .success(value)
}
switch key {
case .name: return checkType("Johnny Appleseed")
case .width: return checkType(25.0)
@unknown default: return .failure(.unknownKey)
}
}
With throws
I think this gets a little nicer if you throw rather than wrapping things into Result. It means you can more easily lift checkType into one place and gets very close to the syntax you said you wanted.
func fetch<Value>(_: Value.Type, forKey key: FetchKey) throws -> Value {
func checkType(value: () throws -> Any) throws -> Value {
guard let value = try value() as? Value else { throw FetchError.invalidType }
return value
}
return try checkType {
switch key {
case .name: return "Johnny Appleseed"
case .width: return 25.0
@unknown default: throw FetchError.unknownKey
}
}
}
With Optionals
This gets a little simpler with Optional if you don't really care about the errors.
func fetch<Value>(_: Value.Type, forKey key: FetchKey) -> Value? {
func _fetch() -> Any? {
switch key {
case .name: return "Johnny Appleseed"
case .width: return 25.0
@unknown default: return nil
}
}
return _fetch() as? Value
}