Conditional Types

I would like to do something like this, guaranteeing that only the types I specify are allowed in the array rather than making it Any.

let strarr:[String | [String]] = ["Hello", "bye", "str1", ["str2", "str3"]]

Creating an enum for the types is possible but a bit clumsy because now I have to wrap all my values.

indirect enum Item {
 case value(String)
 case container([Item])
}

let strarr2:[Item] = [.value("Hello"), .value("bye"), .value("str1"), .container([.value("str2"), .value("str3")])] 

Is there a way to do this more cleanly?

One alternative would be using a protocol:

protocol Item {
  // Any useful functionality common between the two.
}
extension String: Item {}
extension Array: Item where Element: Item {}

let strarr2: [Item] = ["Hello", "bye", "str1", ["str2", "str3"]] 

It’s more succinct, but it can’t be checked for exhaustively. Additional conformances can also be added behind your back if the protocol is public. So which strategy is actually better depends on the target use case.

1 Like

Ah cool, thanks, as you point out, things could change for public protocols, but this is nice in that I don't have to wrap everything. Would be nice to be able to do something like my first example some day.

I think you can combine enums and protocol to have the best of the two worlds:

indirect enum Item {
 case value(String)
 case container([Item])
}

protocol ItemRepresentable {
    var asItem: Item { get }
}

extension String: ItemRepresentable {
    var asItem: Item { .value(self) }
}

extension Array<Item>: ItemRepresentable {
    var asItem: Item { .container(self) }
}

let items: [ItemRepresentable] = ["Hello", "bye", "str1", ["str2", "str3"]] 
for x in items {
  switch x.asItem {
  case let .value(v):
      print(“value: \(v)”)
  case let .container(c):
      print(“container: \(c)”)
  }
}
1 Like

Ah nice, that is just what I need.