Recursive Swift Enum with Associated Values

Hello

I want to develop a recursive Either type as a Swift enum (at least a partially working version ;)), and so have been playing around with generics (trying to work with the type system).

Here is what I have so far:

enum NumericEitherEnum<Left, Right> {
  case left(Left)
  case right(Right)

  var left: Left? {
    switch self {
      case .left(let leftie) where leftie is NumericEitherEnum<Int, Float>:
        return (leftie as? NumericEitherEnum<Int, Float>)?.left // This won't work (obviously)
      case .left(let left):
        return left
      case .right(_):
        return nil
    }
  }

  var right: Right? {
    switch self {
      case .left(_):
        return nil
      case .right(let right) where right is NumericEitherEnum:
        return (right as? NumericEitherEnum)?.right // This assumes the generic parameters of Left, Right in the current enum so won't work.
      case .right(let right):
        return right
    }
  }
}

I get cryptic diagnostic error messages in Xcode, which I haven't been able to get around:

  1. 'Replace (leftie as? NumericEitherEnum<Int, Float>)? with NumericEitherEnum<Int, Float>'
  2. 'Enum case 'left' cannot be used as an instance member'

What I am trying to achieve:

print(NumericEitherEnum<NumericEitherEnum<Int, Float>, NumericEitherEnum<Int, Float>>.left(.left(3)).left ?? "failed :(") // Parses the innermost value 3

The fix-it doesn't fix the error or advise how to actually address the underlying cause. I think this is probably an edge case for the compiler to parse (maybe even bug) ;). I also realise that in fact it doesn't make sense logically to return an Int for a Left? type but is there any way I can express this within the type system (I tried associated types but I still don't know how to make this dynamic).

I am not sure if this is actually possible so please explain in simple terms (if possible) how I might resolve this issue in a better way (preferably using an enum, but using a different struct could be good if enums are impossible or impractical in this instance).

Thanks for the help!

Ok, decided to break the problem down to use functions instead. The limitations with my approaches so far seems that it can't parse nested values more than 2 layers deep and can't handle Nested enums with two Left types being the same. At least this approach partially works, although I am not sure if it is the best way to solve this problem.

extension NumericEitherEnum {

  public func `as`(_ type: Left.Type) -> Left? {
    guard case let .left(left) = self else { return nil }
    return left
  }

  public func `as`(_ type: Right.Type) -> Right? {
    guard case let .right(right) = self else { return nil }
    return right
  }

  public func `as`<L,R>(_ type: L.Type) -> L? where Left == NumericEitherEnum<L,R> {
    guard case let .left(left) = self else { return nil }
    return left.as(type)
  }

  public func `as`<L,R>(_ type: R.Type) -> R? where Left == NumericEitherEnum<L,R> {
    guard case let .left(left) = self else { return nil }
    return left.as(type)
  }

  public func `as`<L,R>(_ type: L.Type) -> L? where Right == NumericEitherEnum<L,R> {
    guard case let .right(right) = self else { return nil }
    return right.as(type)
  }

  public func `as`<L,R>(_ type: R.Type) -> R? where Right == NumericEitherEnum<L,R> {
    guard case let .right(right) = self else { return nil }
    return right.as(type)
  }
}
print(NumericEitherEnum<NumericEitherEnum<Int, Float>, NumericEitherEnum<Double, Float>>.left(.left(3)).as(Int.self) ?? "failed :(")

I am open to your feedback on this issue! Thanks again

I'm assuming Left and Right won't change even in nested Either, is that correct? If so, an alternative approach may be the following:

enum RecursiveEither<Left, Right> {
  case left(ValueOrRecursiveEither<Left, Left, Right>)
  case right(ValueOrRecursiveEither<Right, Left, Right>)
  
  var left: Left? {
    switch self {
    case .left(.value(let left)):
      return left
    case .left(.either(let left)):
      return left.left
    case .right:
      return nil
    }
  }
}

enum ValueOrRecursiveEither<Value, Left, Right> {
  case value(Value)
  indirect case either(RecursiveEither<Left, Right>)
}
let e = RecursiveEither<Int, Int>.left(.value(4))
let f = RecursiveEither<Int, Int>.left(.either(.left(.value(5))))
let g = RecursiveEither<Int, Int>.left(.either(.right(.value(8))))
let h = RecursiveEither<Int, Int>.right(.value(7))

print(e.left)  // prints Optional(4)
print(f.left)  // prints Optional(5)
print(g.left)  // prints nil
print(h.left)  // prints nil

Essentially you want .left to contain either a value of type Left or a RecursiveEither<Left, Right>. You can express that with a separate enum (which I called ValueOrRecursiveEither).

1 Like

A better alternative, which removes .either and lets you place directly .left, .right or .value:

enum RecursiveEither<Left, Right> {
  case left(ValueOrLeftOrRight<Left, Left, Right>)
  case right(ValueOrLeftOrRight<Right, Left, Right>)
  
  var left: Left? {
    guard case .left(let content) = self else { return nil }
    return content.left
  }
}

enum ValueOrLeftOrRight<Value, Left, Right> {
  case value(Value)
  indirect case left(ValueOrLeftOrRight<Left, Left, Right>)
  indirect case right(ValueOrLeftOrRight<Right, Left, Right>)
  
  var left: Left? {
    switch self {
    case .value(let left): return left as? Left
    case .left(let content): return content.left
    default: return nil
    }
  }
}
// previously
let e = RecursiveEither<Int, Int>.left(.either(.left(.either(.left(.either(.left(.value(3))))))))
// now
let e = RecursiveEither<Int, Int>.left(.left(.left(.left(.value(3)))))