How do you properly use a generic type in Swift's new Result type? - swift

I'm trying to use Swift's new Result type in such a way that the type of the .success associated value is a generic. The rather contrived sample code below works, but is there a way to simplify the type casting so that the compiler can infer the correct type for T?
enum FetchError : Error {
case unknownKey
}
enum FetchKey {
case getWidth
case getName
}
func fetchValue<T>(_ key:FetchKey) -> Result<T, FetchError> {
switch key {
case .getName:
// Ideally I would like to just use: return .success("Johnny Appleseed")
return Result<String, FetchError>.success("Johnny Appleseed") as! Result<T, FetchError>
case .getWidth:
return Result<Double, FetchError>.success(25.0) as! Result<T, FetchError>
#unknown default:
return .failure(.unknownKey)
}
}
// This explicit type declaration is also required.
let r:Result<String, FetchError> = fetchValue(.getName)
print(r)

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
}

The function you are trying to create has a dynamically typed generic. This isn't possible as the Swift compiler needs to know the types for each method/variable at compile time.
Suppose you have
func whatToFetch() -> FetchKey {
// Randomly returns one of the FetchKey cases
}
let r = fetchValue(whatToFetch())
There is no way for the compiler to know the type of r before the call is made. Your example code works because your force-casts are perfectly lined up. But in a correct implementation you should not have to specify what type T is inside your generic function
In your case, it seems generics are not really needed and you could do away with protocols:
enum FetchKey {
case someStringValueKey
case someDoubleValueKey
case someModelValueKey
}
protocol FetchResult {}
extension String: FetchResult {}
extension Double: FetchResult {}
extension SomeModel: FetchResult {}
func fetch(_ key: FetchKey) -> Result<FetchResult,Error> {
switch key {
case .someStringValueKey:
return .success("")
case .someDoubleValueKey:
return .success(1.0)
case .someModelValueKey:
return .success(SomeModel())
}
}
let wtf = whatToFetch()
let r = fetch(wtf)
switch r {
case .success(let value):
switch wtf {
case .someStringValueKey:
guard let stringValue = value as? String else { return }
case .someDoubleValueKey:
guard let doubleValue = value as? Double else { return }
case .someModelValueKey:
guard let someModelValue = value as? SomeModel else { return }
}
case .failure(let error):
print("Better do more than just print on production code")
}

Trying to use an unqualified generic fetchValue<T> to do what you want probably isn’t going to work. The reason is that in a generic function, the T is specified by the caller, not the function. You’re essentially saying, “Ask fetchValue for any T you want, and it will give you a result.”
There’s missing link in the way you’ve set up your types. The red flag that’s your primary clue to this is the use of as!. That’s a sign that you are making assumptions about type relationships that you’re not telling the compiler about.
What does that mean? Well, note that with your code, let r: Result<URLConnection, FetchError> = fetchValue(.getName) also compiles — but then crashes at runtime!
One solution is to have an umbrella type that gathers all the possible result types, as Emil’s solution does. In this approach, you erase the type of the result, and ask callers to dynamically extract it. Callers have to deal with the possibility that the result might be of any type, not necessarily the one they expected. This is a common pattern when dealing with dynamically structured data such as JSON documents.
A second approach is to do what Rob suggests: let the caller specify the type, but translate an incorrect type into an error.
A third approach to solving this is to find a way to associate keys with result types in the type system, i.e. to tell the compiler getName → String, getWidth → Double. Unfortunately, AFAIK, there’s no way to do that with individual enum cases, so you’ll need to encode the keys as something other than an enum.
Here’s one way to do it:
enum FetchError : Error {
case unknownKey
}
protocol FetchKey {
associatedtype ValueType
func get() -> ValueType
}
struct GetNameKey: FetchKey {
func get() -> String { // This line establishes the getName → String mapping
return "Johnny Appleseed"
}
}
struct GetWidthKey: FetchKey {
func get() -> Double { // This line establishes the getWidth → Double mapping
return 25.0
}
}
// This function signature means: “If you give fetchValue a key, it
// will give you a result _with the appropriate type_ for that key”
//
func fetchValue<K: FetchKey>(_ key: K) -> Result<K.ValueType, FetchError> {
// The return type here is Result<K.ValueType, FetchError>, but
// now Swift has enough into to infer it!
return Result.success(key.get())
}
// This now works without type inference:
let r0 = fetchValue(GetNameKey())
print(r0)
// And this now (correctly) fails to compile:
// let r1: Result<Double, FetchError> = fetchValue(GetNameKey())
Which approach should you use?
Does each key always return a single, consistent type that you know beforehand (e.g. name is always a string)?
Yes: Use my approach above
No: If a fetch succeeds, but the type that came back isn’t the type the caller asked for, how do you want it reported?
As a Result.failure, and the caller can’t see the value: Use Rob’s approach
As a Result.success, and the caller has to figure out what type the value is: Use Emil’s approach

Related

How can I implement a custom error throwing syntax in swift?

I want to implement the following:
throwingFunction()??.doStuff()
/* if throwingFunction throws an error:
print the error
else
returns an object with the doStuff() Method
*/
throwingFunction()??
/*
if an error is thrown,
prints the error.
else
execute the function without errors.
*/
I'm not sure where to look in the source code for examples on how do, try, catch were implemented. The Swift error docs explain how to use error handle methods that are already implemented. To be clear, I want to implement custom error handling with the above syntax.
Something like:
precedencegroup Chaining {
associativity: left
}
infix operator ?? : Chaining
extension Result {
// ERROR: Unary operator implementation must have a 'prefix' or 'postfix' modifier
static func ??(value: Result<Success, Failure>) -> Success? {
switch value {
case .success(let win):
return win
case .failure(let fail):
print(fail.localizedDescription)
return nil
}
}
}
You can define a postfix operator which takes a throwing closure as (left) operand. ?? is already defined as an infix operator, therefore you have to choose a different name:
postfix operator <?>
postfix func <?><T>(expression: () throws -> T) -> T? {
do {
return try expression()
} catch {
print(error)
return nil
}
}
Now you can call
let result = throwingFunc<?>
or chain it with
let result = (throwingFunc<?>)?.doStuff()
Previous answer:
?? is already defined as an infix operator. For a postfix operator you have to choose a different name, for example:
postfix operator <?>
extension Result {
static postfix func <?>(value: Result) -> Success? {
switch value {
case .success(let win):
return win
case .failure(let fail):
print(fail.localizedDescription)
return nil
}
}
}
Now you can call
let res = Result(catching: throwingFunc)<?>
or chain it with
let res = (Result(catching: throwingFunc)<?>)?.doStuff()
There is little chance that you can do this, without actually forking apple/swift and creating your own version of the compiler... Here are my attempts:
First, I noticed that the second part of the desired result, ?.doStuff() looks exactly like a optional chaining expression. I thought I could make a postfix ? operator that returned an optional. But it turns out, I can't declare an ? operator at all:
postfix operator ? // error
So I used a visually similar character - ‽ - instead. The type of a throwing function is () throws -> Void and I used #autoclosure so that the {} can be omitted:
typealias ThrowingFunction<T> = () throws -> T
postfix operator ‽
postfix func ‽<T>(lhs: #autoclosure ThrowingFunction<T>) -> T? {
switch Result(catching: lhs) {
case .failure(let error):
print(error.localizedDescription)
return nil
case .success(let t):
return t
}
}
// Usage:
func f() throws -> Int {
throw URLError(URLError.badURL)
}
// You have to use it like this :(
(try f()‽)
(try f()‽)?.description
The try could be omitted if the function you are calling takes no arguments:
f‽
(f‽)?.description
To make functions of other arity work without try, you need to create an implementation of ‽ for each arity, which sucks.
But the brackets must be there because of how Swift parses operators :(
Then I tried to make the approach you attempted, with key paths:
func ??<T, U>(lhs: #autoclosure ThrowingFunction<T>, rhs: KeyPath<T, U>) -> U? {
switch Result(catching: lhs) {
case .failure(let error):
print(error.localizedDescription)
return nil
case .success(let t):
return t[keyPath: rhs]
}
}
func f() throws -> Int {
throw URLError(URLError.badServerResponse)
}
This seems to be even worse, because you gotta use it like this:
try f() ?? \.description
You can't omit the try,
f ?? \.description // type inferencer freaks out, thinks T is ThrowingFunction<Int>
nor reduce the spaces on either side of ?? (See here for why):
try f()??\.description
Plus there is this backlash that is an integral part of the keypath syntax, and you can only use it for key paths, not methods. :(
Summary
You can't do this because:
You can't overload ?
You can't put a ? right after anything because it will be parsed as optional chaining
You must write try, unless you cater for every arity.
postfix operator *
#discardableResult
postfix func *<Preferred>(expression: ErrorAlt<Preferred>) -> Preferred? {
switch expression {
case .preferred(let pref):
return pref
case .error(let err):
print(err.localizedDescription)
return nil
case .initializersWereNil:
print("initializersWereNil")
return nil
}
}
Here is an example usage.
enum TestMeError: Error {
case first
}
extension Int {
func printWin() {
print("we did it!")
}
}
func testMe() -> ErrorAlt<Int> {
if true {
return .error(TestMeError.first)
} else {
return .preferred(40)
}
}
// USAGE
testMe()*

Swift `Failure` keyword meaning in a Swift Combine chain error

Let's say that I've this simple chain that from an HTTP request creates a publisher for <T, APIManagerError>
func run<T:Decodable>(request:URLRequest)->AnyPublisher<T, APIManagerError>{
return URLSession.shared.dataTaskPublisher(for: request)
.map{$0.data}
.decode(type: T.self, decoder: JSONDecoder())
.eraseToAnyPublisher()// it should run mapError before this point
}
This code produces this error since I'm returning Error instead of APIManagerError.
Cannot convert return expression of type
'AnyPublisher<T, Publishers.Decode<Upstream, Output, Coder>.Failure>'
(aka 'AnyPublisher<T, Error>')
to return type 'AnyPublisher<T, RestManagerError>'
I know that to fix the issue I need to add a mapError after .decode.
.mapError{error in
APIManagerError.error("Decode Fail")
}
but I can't really understand what is reported with the error message before the "aka" part that is quite clear instead
How do you read the error Publishers.Decode<Upstream, Output, Coder>.Failure? specifically what does it mean the .Failure part? where can I find the Failure in the Swift Doc?
Since we are talking about two different kinds of errors here lets denote a Compilation Error as CE and the Failure of a Publisher (conforming to Swift.Error) as PF (Publisher Failure).
Your question is about the interpretation of the CE message.
Cannot convert return expression of type
'AnyPublisher<T, Publishers.Decode<Upstream, Output, Coder>.Failure>'
Writes out the resulting returned type of your implementation of func run - without the mapError call. The compiler acknowledges your call to eraseToAnyPublisher() in the end of the function, and also your generic Output of type T. So that covers the Cannot convert return expression of type 'AnyPublisher<T,. As to Publishers.Decode<Upstream, Output, Coder>.Failure>' outputs the derived type of the Failure. This is somewhat a symbolic breakdown of the derived Failure type. Your upstream Publisher is initially of type URLSession.DataTaskPublisher, as a result of your URLSession.shared.dataTaskPublisher call, which you then transform with every Combine operator you call: map and then decode. Resulting in the publisher Publishers.Decode. And the Failure type of cannot be properly "desymbolised" (I lack the proper compiler knowledge to use the correct terminology).
Which Xcode version do you use? The New Diagnostic Architecture might be able to show a better error message. This is in fact the reason why I later in my response use .assertMapError(is: DecodingError.self)
Your code in mapError does the job, but it completely tossed away information about the actual error. So I would not do that. At least print (log) the error. But still I would do something like:
Declare custom Error type
Intuitively we have at least two different kinds of errors, either networking or decoding. But potentially more...
public enum HTTPError: Swift.Error {
indirect case networkingError(NetworkingError)
indirect case decodingError(DecodingError)
}
public extension HTTPError {
enum NetworkingError: Swift.Error {
case urlError(URLError)
case invalidServerResponse(URLResponse)
case invalidServerStatusCode(Int)
}
}
You might need to tell combine that the error type is indeed DecodingError, thus I've declared some fatalError macros useful for this information. It is somewhat simular to Combine's setFailureType (but which only works when the upstream publisher has Failure type Never, thus we cannot use it here).
castOrKill
func typeErasureExpected<T>(
instance incorrectTypeOfThisInstance: Any,
toBe expectedType: T.Type,
_ file: String = #file,
_ line: Int = #line
) -> Never {
let incorrectTypeString = String(describing: Mirror(reflecting: incorrectTypeOfThisInstance).subjectType)
fatalError(
"Incorrect implementation: Expected variable '\(incorrectTypeOfThisInstance)' (type: '\(incorrectTypeString)') to be of type `\(expectedType)`",
file, line
)
}
func castOrKill<T>(
instance anyInstance: Any,
toType: T.Type,
_ file: String = #file,
_ line: Int = #line
) -> T {
guard let instance = anyInstance as? T else {
typeErasureExpected(instance: anyInstance, toBe: T.self, file, line)
}
return instance
}
And then create a convenience method on Publisher, similar to setFailureType:
extension Publisher {
func assertMapError<NewFailure>(is newFailureType: NewFailure.Type) -> AnyPublisher<Output, NewFailure> where NewFailure: Swift.Error {
return self.mapError { castOrKill(instance: $0, toType: NewFailure.self) }.eraseToAnyPublisher()
}
}
Usage:
I took the liberty of catching some more errors in your examples. Asserting e.g. that the server responds with a non failure HTTP status code etc.
func run<Model>(request: URLRequest) -> AnyPublisher<Model, HTTPError> where Model: Decodable {
URLSession.shared
.dataTaskPublisher(for: request)
.mapError { HTTPError.NetworkingError.urlError($0) }
.tryMap { data, response -> Data in
guard let httpResponse = response as? HTTPURLResponse else {
throw HTTPError.NetworkingError.invalidServerResponse(response)
}
guard case 200...299 = httpResponse.statusCode else {
throw HTTPError.NetworkingError.invalidServerStatusCode(httpResponse.statusCode)
}
return data
}
.decode(type: Model.self, decoder: JSONDecoder())
// It's unfortunate that Combine does not pick up that failure type is `DecodingError`
// thus we have to manually tell the Publisher this.
.assertMapError(is: DecodingError.self)
.mapError { HTTPError.decodingError($0) }
.eraseToAnyPublisher()
}
Bonus - equality check for HTTPError
It is indeed very advantageous if our error types are Equatable, it makes writing unit tests so much easier. Either we go the Equatable route, or we can do some reflection magic. I will present both solutions, but the Equatable solution is more robust for sure.
Equatable
In order to make HTTPError conform to Equatable we only need to manually make DecodingError equatable. I've done this with this code:
extension DecodingError: Equatable {
public static func == (lhs: DecodingError, rhs: DecodingError) -> Bool {
switch (lhs, rhs) {
/// `typeMismatch` is an indication that a value of the given type could not
/// be decoded because it did not match the type of what was found in the
/// encoded payload. As associated values, this case contains the attempted
/// type and context for debugging.
case (
.typeMismatch(let lhsType, let lhsContext),
.typeMismatch(let rhsType, let rhsContext)):
return lhsType == rhsType && lhsContext == rhsContext
/// `valueNotFound` is an indication that a non-optional value of the given
/// type was expected, but a null value was found. As associated values,
/// this case contains the attempted type and context for debugging.
case (
.valueNotFound(let lhsType, let lhsContext),
.valueNotFound(let rhsType, let rhsContext)):
return lhsType == rhsType && lhsContext == rhsContext
/// `keyNotFound` is an indication that a keyed decoding container was asked
/// for an entry for the given key, but did not contain one. As associated values,
/// this case contains the attempted key and context for debugging.
case (
.keyNotFound(let lhsKey, let lhsContext),
.keyNotFound(let rhsKey, let rhsContext)):
return lhsKey.stringValue == rhsKey.stringValue && lhsContext == rhsContext
/// `dataCorrupted` is an indication that the data is corrupted or otherwise
/// invalid. As an associated value, this case contains the context for debugging.
case (
.dataCorrupted(let lhsContext),
.dataCorrupted(let rhsContext)):
return lhsContext == rhsContext
default: return false
}
}
}
extension DecodingError.Context: Equatable {
public static func == (lhs: DecodingError.Context, rhs: DecodingError.Context) -> Bool {
return lhs.debugDescription == rhs.debugDescription
}
}
Which, as you can see, also has to make DecodingError.Context Equatable.
Then you can declare these XCTest helpers:
func XCTAssertThrowsSpecificError<ReturnValue, ExpectedError>(
file: StaticString = #file,
line: UInt = #line,
_ codeThatThrows: #autoclosure () throws -> ReturnValue,
_ error: ExpectedError,
_ message: String = ""
) where ExpectedError: Swift.Error & Equatable {
XCTAssertThrowsError(try codeThatThrows(), message, file: file, line: line) { someError in
guard let expectedErrorType = someError as? ExpectedError else {
XCTFail("Expected code to throw error of type: <\(ExpectedError.self)>, but got error: <\(someError)>, of type: <\(type(of: someError))>")
return
}
XCTAssertEqual(expectedErrorType, error, line: line)
}
}
func XCTAssertThrowsSpecificError<ExpectedError>(
_ codeThatThrows: #autoclosure () throws -> Void,
_ error: ExpectedError,
_ message: String = ""
) where ExpectedError: Swift.Error & Equatable {
XCTAssertThrowsError(try codeThatThrows(), message) { someError in
guard let expectedErrorType = someError as? ExpectedError else {
XCTFail("Expected code to throw error of type: <\(ExpectedError.self)>, but got error: <\(someError)>, of type: <\(type(of: someError))>")
return
}
XCTAssertEqual(expectedErrorType, error)
}
}
func XCTAssertThrowsSpecificErrorType<Error>(
_ codeThatThrows: #autoclosure () throws -> Void,
_ errorType: Error.Type,
_ message: String = ""
) where Error: Swift.Error & Equatable {
XCTAssertThrowsError(try codeThatThrows(), message) { someError in
XCTAssertTrue(someError is Error, "Expected code to throw error of type: <\(Error.self)>, but got error: <\(someError)>, of type: <\(type(of: someError))>")
}
}
Reflection magic
Or you can take a look at my Gist here which does not make use of Equatable at all, but can "compare" any enums errors which are not conforming to Equatable.
Usage
Together with CombineExpectation you can now write unit tests of your Combine code and compare errors more easily!
I've easily found the answer actually but I keep the question open in case someone else needed it.
The Failure keyword in this case is just the associatedtype that comes with the Publisher protocol.
In this case I'm just getting the Failure type for the Publishers.Decode, that by default is just Error.

What is the proper use of guard when testing for the existence of a property

In my code I am being passed a dictionary. That dictionary can contain an error string. Like this
func handleSomething(_ json: [String: Any]) -> (Bool, String?) {
if let error = json["error"] as? String {
print("I have an error: \(error)")
return (false, error)
}
...
return (true, nil)
}
I would think this would be a good place to use guard, however the code is not as concise
func handleSomething(_ json: [String: Any]) -> Bool {
let error = json["error"] as? String
guard error == nil else {
print("I have an error: \(error!)")
return (false, error)
}
...
return (true, nil)
}
Is there a better way to do this so the assignment and check can be done in one line? If not is this the correct use of guard?
Update: Added return values to better illustrate the real intention of the question.
Generally you should strongly avoid passing around [String: Any]. You should parse this down to a stronger data type earlier, and you should separate out error cases there, often with an enum:
struct ServerData {
// good data; no errors
}
enum ServerResponse {
case success(ServerData)
case error(String) // Return this if error is set
}
With this, our functions would just take ServerData mostly, so errors have already been dealt with. Whenever possible, rather than coming up with clever workarounds to problems, make the whole problem go away.
That said, there's still the parser itself to deal with and this situation can come up both there and even when you have stronger types. In many cases you wind up with this guard/log pattern very often, and it's definitely worth avoiding that. So pull out a function:
func noErrorOrLog(_ json: [String: Any]) -> Bool {
if let error = json["error"] as? String {
print("I have an error: \(error)")
return false
}
return true
}
Now actual handling functions can use guard and avoid duplicating the logging:
func handleSomething(_ json: [String: Any]) -> Bool {
guard noErrorOrLog(json) else {
return false
}
// ...
}
In some cases this doesn't make sense (if you're doing it exactly once perhaps), in which case if-let is the right tool. Creating confusing code to create a guard is counterproductive. But in many cases you can avoid this problem entirely (by filtering errors earlier with strong types), or avoid duplication by moving to a function.

Swift 2 value extraction from Enum

Prior to Swift 2, I would often use enums with associated values and add functions to extract specific values, like so:
public enum Maybe <T> {
case Unknown
case Known(T)
public var value: T? {
switch self {
case .Unknown: return nil
case .Known(let value): return value
}
}
}
This would allow me to do something like this:
let maybe = .Known("Value")
let val = maybe.value ?? "Another Value"
I would like to get rid of these convenience functions and rely on Swift 2's new syntax. This is possible doing something like:
let val: String
if case .Known(let value) = maybe {
val = value
} else {
val = "Another Value"
}
But I can't figure out how to condense this back into a single line using the ?? operator or even ternary operator.
Is this even possible or am I stuck with defining "extraction" optionals on the enum?
Update (clarification)
The Maybe enum is just an example, but the solution would need to work on Enums that have multiple associated values... like an Either:
public enum Either<L, R> {
case Left(Box<L>)
case Right(Box<R>)
public func left() -> L?{
switch self {
case let Left(value):
return value.value
default:
return nil
}
}
public func right() -> R?{
switch self {
case let Right(value):
return value.value
default:
return nil
}
}
}
The syntax I'm looking for would be something like:
let val = (case .Known(let value) = maybe) ?? "AnotherValue"
What I want to do is easily extract an associated value for a specific case, else provide a default.
For Either it might be something like:
let val = (case .Left(let value) = either) ?? "AnotherValue"
Make sense?
The syntax you want isn't possible in Swift today (and feels unlikely for Swift tomorrow, but I often am surprised). The best available solutions are extraction functions like left() -> L? and right() -> R?. case is not a generic value-returning function that you can extend. See Rob Rix's Either for some of the best current thinking on this problem.
A key choice in Swift is that there are many statements that are not expressions. switch is one of them. Until Swift makes things like switch and if be expressions, it will be very hard to build this kind of syntax IMO.
Just define ?? for it:
func ??<T>(lhs: Maybe<T>, #autoclosure defaultValue: () throws -> T) rethrows -> T {
switch lhs {
case .Unknown: return try defaultValue()
case .Known(let value): return value
}
}
let maybe = Maybe.Known("Value")
let val = maybe ?? "Another Value"
Doing it this way gives us some nice features in Swift2. For instance, we can lazily evaluate the rhs, and we can handle throwing in the rhs:
func computeIt() -> String {
print("LAZY!")
return "Computed"
}
maybe ?? computeIt() // Does not print "LAZY!"
Maybe.Unknown ?? computeIt() // Does print "LAZY!"
enum Error: ErrorType {
case Failure
}
func fail() throws -> String {
throw Error.Failure
}
try maybe ?? fail() // Doesn't throw
do {
try Maybe.Unknown ?? fail() // throws
} catch {
print("THROW")
}

Can Swift enums have multiple raw values?

I want to associate two raw values to an enum instance (imagine an enum representing error types, I want Error.Teapot to have an Int type property code with value 418, and a String property set to I'm a teapot.)
Note the difference between raw values and associated values here—I want all Teapot instances to have a code of 418, I don't want a unique associated value for each Teapot instance.
Is there a better way than adding computed properties to the enum that switched on self to look up the appropriate value?
You have a couple options. But neither of them involve raw values. Raw values are just not the right tool for the task.
Option 1 (so-so): Associated Values
I personally highly recommend against there being more than one associated value per enum case. Associated values should be dead obvious (since they don't have arguments/names), and having more than one heavily muddies the water.
That said, it's something the language lets you do. This allows you to have each case defined differently as well, if that was something you needed. Example:
enum ErrorType {
case teapot(String, Int)
case skillet(UInt, [CGFloat])
}
Option 2 (better): Tuples! And computed properties!
Tuples are a great feature of Swift because they give you the power of creating ad-hoc types. That means you can define it in-line. Sweet!
If each of your error types are going to have a code and a description, then you could have a computed info property (hopefully with a better name?). See below:
enum ErrorType {
case teapot
case skillet
var info: (code: Int, description: String) {
switch self {
case .teapot:
return (418, "Hear me shout!")
case .skillet:
return (326, "I'm big and heavy.")
}
}
}
Calling this would be much easier because you could use tasty, tasty dot syntax:
let errorCode = myErrorType.info.code
No, an enum cannot have multiple raw values - it has to be a single value, implementing the Equatable protocol, and be literal-convertible as described in the documentation.
I think the best approach in your case is to use the error code as raw value, and a property backed by a prepopulated static dictionary with the error code as key and the text as value.
I created a way of simulating this (No different than what Marcos Crispino suggested on his answer). Far from a perfect solution but allows us to avoid those nasty switch cases for every different property we want to get.
The trick is to use a struct as the "properties/data" holder and using it as a RawValue in the enum itself.
It has a bit of duplication but it's serving me well so far. Every time you want to add a new enum case, the compiler will remind you to fill in the extra case in the rawValue getter, which should remind you to update the init? which would remind you to create the new static property on the struct.
Gist
Code to the Gist:
enum VehicleType : RawRepresentable {
struct Vehicle : Equatable {
let name: String
let wheels: Int
static func ==(l: Vehicle, r: Vehicle) -> Bool {
return l.name == r.name && l.wheels == r.wheels
}
static var bike: Vehicle {
return Vehicle(name: "Bicycle", wheels: 2)
}
static var car: Vehicle {
return Vehicle(name: "Automobile", wheels: 4)
}
static var bus: Vehicle {
return Vehicle(name: "Autobus", wheels: 8)
}
}
typealias RawValue = Vehicle
case car
case bus
case bike
var rawValue: RawValue {
switch self {
case .car:
return Vehicle.car
case .bike:
return Vehicle.bike
case .bus:
return Vehicle.bus
}
}
init?(rawValue: RawValue) {
switch rawValue {
case Vehicle.bike:
self = .bike
case Vehicle.car:
self = .car
case Vehicle.bus:
self = .bus
default: return nil
}
}
}
VehicleType.bike.rawValue.name
VehicleType.bike.rawValue.wheels
VehicleType.car.rawValue.wheels
VehicleType(rawValue: .bike)?.rawValue.name => "Bicycle"
VehicleType(rawValue: .bike)?.rawValue.wheels => 2
VehicleType(rawValue: .car)?.rawValue.name => "Automobile"
VehicleType(rawValue: .car)?.rawValue.wheels => 4
VehicleType(rawValue: .bus)?.rawValue.name => "Autobus"
VehicleType(rawValue: .bus)?.rawValue.wheels => 8
No, you cannot have multiple raw values associated with an enum.
In your case, you could have the raw value to be equal to the code, and have an associated value with the description. But I think the computed properties approach is the best option here.
One workaround if you wanted to have many static properties for a YourError could be to import a property list; you could set the root object to a dictionary, with your enum raw value as the key for each object, allowing you to easily retrieve static structured data for the object.
This has an example of importing and using a plist: http://www.spritekitlessons.com/parsing-a-property-list-using-swift/
That might be overkill for simply an error description, for which you could just use a hardcoded static function with a switch statement for your enum values, that returns the error string you need. Simply place the static function in the same .swift file as your enum.
For instance,
static func codeForError(error : YourErrorType) -> Int {
switch(error) {
case .Teapot:
return "I'm a Teapot"
case .Teacup:
return "I'm a Teacup"
...
default:
return "Unknown Teaware Error"
}
}
This has the benefit (compared to the .plist solution) of better accomodating localization. However, a .plist could just contain a key used for retrieving the proper localization, instead of a error string, for this purpose.
For beginning, assuming you want to store a code and a message, you can use a struct for RawValue
struct ErrorInfo {
let code: Int
let message: String
}
Next step is to define the enum as being RawRepresentable, and use ErrorInfo as the raw value:
enum MyError: RawRepresentable {
typealias RawValue = ErrorInfo
case teapot
What remains is to map between instances of MyError and ErrorInfo:
static private let mappings: [(ErrorInfo, MyError)] = [
(ErrorInfo(code: 418, message: "I'm a teapot"), .teapot)
]
With the above, let's build the full definition of the enum:
enum MyError: RawRepresentable {
static private let mappings: [(ErrorInfo, MyError)] = [
(ErrorInfo(code: 418, message: "I'm a teapot"), .teapot)
]
case teapot
init?(rawValue: ErrorInfo) {
guard let match = MyError.mappings.first(where: { $0.0.code == rawValue.code && $0.0.message == rawValue.message}) else {
return nil
}
self = match.1
}
var rawValue: ErrorInfo {
return MyError.mappings.first(where: { $0.1 == self })!.0
}
}
Some notes:
you could use only the error code for matching, however this might result in inconsistent raw values if the messages differ
the amount of boilerplate code required to have raw values of some custom type might not outcome the benefits of using associated values.
Possible work around may to associate custom functions with enum
enum ToolbarType : String{
case Case = "Case", View="View", Information="Information"
static let allValues = [Case, View, Information]
func ordinal() -> Int{
return ToolbarType.allValues.index(of: self)!
}
}
Can be used as
for item in ToolbarType.allValues {
print("\(item.rawValue): \(item.ordinal())")
}
Output
Case: 0
View: 1
Information: 2
Possibly you can have additional functions to associate enum type to different values
This doesn't particularly answer your question, which was asking to find a better way than switching through self to look up the appropriate value but this answer may still be useful for someone looking in the future that needs a simple way to get a string from an enum which is defined as an integer type.
enum Error: UInt {
case Teapot = 418
case Kettle = 419
static func errorMessage(code: UInt) -> String {
guard let error = Error(rawValue: code) else {
return "Unknown Error Code"
}
switch error {
case .Teapot:
return "I'm a teapot!"
case .Kettle:
return "I'm a kettle!"
}
}
}
This way, we can get the errorMessage two ways:
With an integer (eg. that was returned as an error code from a server)
With an enum value (the rawValue we define for the enum)
Option 1:
let option1 = Error.errorMessage(code: 418)
print(option1) //prints "I'm a teapot!"
Option 2:
let option2 = Error.errorMessage(code: Error.Teapot.rawValue)
print(option2) //prints "I'm a teapot!"
In modern versions of Swift it's possible to get the string value of an enum case label, even without that enum being declared with a : String rawValue.
How to get the name of enumeration value in Swift?
So there is no longer a need to define and maintain a convenience function that switches on each case to return a string literal. In addition, this works automatically for any enum, even if no raw-value type is specified.
This, at least, allows you to have "multiple raw values" by having both a real : Int rawValue as well as the string used as the case label.
I think it just tricky, and I have create my own idea like below:
enum Gender:NSNumber
{
case male = 1
case female = 0
init?(strValue: String?) {
switch strValue {
case Message.male.value:
self = .male
case Message.female.value:
self = .female
default: return nil
}
}
var strValue: String {
switch self {
case .male:
return Message.male.value
case .female:
return Message.female.value
}
}
}
First of all, enums should only have one raw value. However if you want to have something that can use multiple raw values... there is a way to 'hack' this, but you have to make it codable and hashable yourself, implement custom init's etc.
enum MyCustomEnum: Codable, Hashable {
// duplicate every case with associated value of Codable.Type
case myFirstCase, _myFirstCase(Codable.Type)
case mySecondCase, _mySecondCase(Codable.Type)
case myThirdCase, _myThirdCase(Codable.Type)
case unknown(Any), _unknown(Codable.Type, Any) // handles unknown values
// define an allCases value to determine the only values your app 'sees'.
static var allCases: [Self] {
return [
.myFirstCase,
.mySecondCase,
.myThirdCase
// unknown(String) // you can add unknown as well, but this is too mask any unknown values.
]
}
static func == (lhs: MyCustomEnum, rhs: MyCustomEnum) -> Bool {
return lhs.stringValue == rhs.stringValue // can be either one of your custom raw values.
}
// add this per raw value. In this case one for Int and one for String
init(rawValue: Int) {
guard let value = Self.allCases.first(where:{ $0.intValue == rawValue }) else {
self = ._unknown(Int.self, rawValue)
return
}
switch value {
case .myFirstCase: self = ._myFirstCase(Int.self)
case .mySecondCase: self = ._mySecondCase(Int.self)
case .myThirdCase: self = ._myThirdCase(Int.self)
default: self = ._unknown(Int.self, rawValue)
}
}
init(rawValue: String) {
guard let value = Self.allCases.first(where:{ $0.stringValue == rawValue }) else {
self = ._unknown(String.self, rawValue)
return
}
switch value {
case .myFirstCase: self = ._myFirstCase(String.self)
case .mySecondCase: self = ._mySecondCase(String.self)
case .myThirdCase: self = ._myThirdCase(String.self)
default: self = ._unknown(Int.self, rawValue)
}
}
// add this per raw value. In this case one for Int and one for String
var intValue: Int {
switch self {
case .myFirstCase, ._myFirstCase(_): return 1
case .mySecondCase, ._mySecondCase(_): return 2
case .myThirdCase, ._myThirdCase(_): return 3
case .unknown(let value), ._unknown(_, let value): return value as? Int ?? -1 // you can also choose to let intValue return optional Int.
}
}
var stringValue: String {
switch self {
case .myFirstCase, ._myFirstCase(_): return "my first case"
case .mySecondCase, ._mySecondCase(_): return "my second case"
case .myThirdCase, ._myThirdCase(_): return "my third case"
case .unknown(let value), ._unknown(_, let value): return value as? String ?? "not a String" // you can also choose to let stringValue return optional String.
}
}
// determine the codable type using Mirror
private func getCodableType() -> Codable.Type? {
let mirrorOfModuleType = Mirror.init(reflecting: self)
guard let childOfModuleType = mirrorOfModuleType.children.first else { // no children, means no associated values.
return nil
}
let value = childOfModuleType.value // can be either Codable.Type, String or (Codable.Type & String)
if let rawValue = value as? Codable.Type {
return rawValue
} else {
guard let rawValue = value as? (Codable.Type, String) else {
// unknown(String), we don't know the rawValue as given, but try in this part of the code to guess what type fits best.
if self.stringValue != "\(self.intValue)" { // e.g. "1" might match 1 but "1.0" and 1 don't match
return String.self
} else {
return Int.self // return either a default value, or nil. It's your choice.
}
}
return rawValue.0
}
}
// confine to hashable using getCodableType
func hash(into hasher: inout Hasher) {
if self.getCodableType() is String.Type {
hasher.combine(self.stringValue)
} else { // if you don't call hasher.combine at all, you can expect strange issues. If you do not know the type, choose one that is most common.
hasher.combine(self.intValue)
}
}
// confine to Decodable
init(from decoder: Decoder) throws {
if let rawValue = try? Int.init(from: decoder) {
self.init(rawValue: rawValue)
} else if let rawValue = try? String.init(from: decoder) {
self.init(rawValue: rawValue)
} else {
throw DecodingError.valueNotFound(Self.self, DecodingError.Context(codingPath: [], debugDescription: "no matching value was found"))
}
}
// confine to Encodable using getCodableType
func encode(to encoder: Encoder) throws {
let rawValue = self.getCodableType()
if rawValue is String.Type {
try self.stringValue.encode(to: encoder)
} else if rawValue is Int.Type {
try self.intValue.encode(to: encoder)
} else {
// getCodableType returns nil if it does not know what value it is. (e.g. myFirstCase without associated value) If you want to support this as well, you can encode using one of your rawValues to the encoder.
throw EncodingError.invalidValue(Self.self, EncodingError.Context.init(codingPath: [], debugDescription: "this enum does not have a correct value", underlyingError: nil))
}
}
}
this code is scalable to any number of raw value as long as they are Codable