Benno Kress
Benno Kress

Reputation: 2809

Writing an enum case check to a Bool variable without Equatable conformance

I have an Enum with associated values like this one:

enum SomeState {
    case loggedOut(redirectAfterLogin: Module? = nil)
    case loggedIn
}

Now in some cases I want to compare two states exactly (like are both logged out and also is the redirect target equal) and sometimes I just want to know if they are both logged out. My question regards the second case: I want to check if the state is logged out and write that to a variable, but obviously I can't implement Equatable to just account for the general case ignoring the parameters.

One way of achieving this would be to implement a computed property isLoggedOut on the Enum itself, but since this here is just an example and my actual code is much larger, this is not an option for me.

A second way (the one I currently use) is this:

func whatever() {
    if case .loggedOut = self.state {
        self.isLoggedOut = true
    } else {
        self.isLoggedOut = false
    }
}

This works, but I would rather write something like this:

func whatever() {
    self.isLoggedOut = (case .loggedOut = self.state)
}

Am I missing something or is it really not possible to write the case comparison of the if-clause to a variable directly (or some similar one-line-solution)?

Upvotes: 1

Views: 1717

Answers (3)

user652038
user652038

Reputation:

I don't think it's possible to avoid using SomeState before .loggedOut. But even with that, this is way better than the option that's built into the language, which is in the other answers.

It's better if you can make Module and SomeState be Equatable, but not necessary.

public extension Mirror {
  /// Get the associated value from an `enum` instance.
  func getAssociatedValue<AssociatedValue>(
    _: AssociatedValue.Type = AssociatedValue.self
  ) -> AssociatedValue? {
    guard let childValue = children.first?.value
    else { return nil }

    if let associatedValue = childValue as? AssociatedValue {
      return associatedValue
    }

    let labeledAssociatedValue = Mirror(reflecting: childValue).children.first
    return labeledAssociatedValue?.value as? AssociatedValue
  }
}
/// Match `enum` cases with associated values, while disregarding the values themselves.
/// - Parameter makeCase: Looks like `Enum.case`.
public func ~= <Enum: Equatable, AssociatedValue>(
  makeCase: (AssociatedValue) -> Enum,
  instance: Enum
) -> Bool {
  Mirror(reflecting: instance).getAssociatedValue().map(makeCase)
  == instance
}

/// Match `enum` cases with associated values, while disregarding the values themselves.
/// - Parameter makeCase: Looks like `Enum.case`.
public func ~= <Enum, AssociatedValue>(
  makeCase: (AssociatedValue) -> Enum,
  instance: Enum
) -> Bool {
  let instanceMirror = Mirror(reflecting: instance)

  guard let dummyCase = instanceMirror.getAssociatedValue().map(makeCase)
  else { return false }

  return
    Mirror(reflecting: dummyCase).children.first?.label
    == instanceMirror.children.first?.label
}
struct ๐Ÿงจ {
  enum SomeState {
    struct Module { }

    case loggedOut(redirectAfterLogin: Module? = nil)
    case loggedIn
  }

  var isLoggedOut: Bool
  var state: SomeState

  mutating func whatever() {
    isLoggedOut = SomeState.loggedOut ~= state
  }
}

More example usage:

enum ๐Ÿ“ง: Equatable {
  case tuple(cat: String, hat: String)
  case labeled(cake: String)
  case noAssociatedValue
}

let tupleCase = ๐Ÿ“ง.tuple(cat: "๐Ÿฏ", hat: "๐Ÿงข")
XCTAssertTrue(๐Ÿ“ง.tuple ~= tupleCase)

XCTAssertTrue( ๐Ÿ“ง.labeled ~= ๐Ÿ“ง.labeled(cake: "๐Ÿฐ") )

let makeTupleCase = ๐Ÿ“ง.tuple
XCTAssertFalse(makeTupleCase ~= ๐Ÿ“ง.noAssociatedValue)

switch tupleCase {
case ๐Ÿ“ง.labeled: XCTFail()
case makeTupleCase: break
default: XCTFail()
}

Upvotes: 0

David Pasztor
David Pasztor

Reputation: 54706

You simply need to change whatever to be a computed property rather than a function, modify the assignments to isLoggedOut to be return statements and you've got your isLoggedOut property.

var isLoggedOut: Bool {
    if case .loggedOut = self.state {
        return true
    } else {
        return false
    }
}

Simplified example code where the type holding the state property defines the isLoggedOut property as well:

enum SomeState {
    case loggedOut
    case loggedIn
}

struct User {
    var state: SomeState

    var isLoggedOut: Bool {
        if case .loggedOut = self.state {
            return true
        } else {
            return false
        }
    }
}

Upvotes: 2

Duncan C
Duncan C

Reputation: 131398

How about this:

enum SomeState {
    case loggedOut(redirectAfterLogin: Int? = nil)
    case loggedIn

    var isLoggedOut: Bool {
        switch self {
        case .loggedOut(_):  return true
        default: return false
        }
    }
}

var aState: SomeState = .loggedOut()

if aState.isLoggedOut {
    print("logged out")
} else {
    print("not logged out")
}

(I switched the associated value redirectAfterLogin to a simple scalar Int so it would compile for those of us who don't have the definition of your Module type. you'd need to switch it back.)

The trick here is the computed property isLoggedOut, which uses a switch statement that ignores the associated value for the loggedOut case.

Upvotes: 1

Related Questions