I'm trying to replicate some behaviour that typescript has:
interface SomeInterface {
someProperty: string
}
interface a extends SomeInterface {
}
interface b extends SomeInterface {
}
type SwiftEnumEquivalent = a|b
function takesSomeInterface(x: SwiftEnumEquivalent){
console.log(x.someProperty);
}
The takeaway here is the type I've defined as 'SwiftEnumEquivalent' is similar to an enum with an associated value. Now both interfaces 'a' and 'b' inherits from 'SomeInterface' which has a property 'someProperty'. When using 'SwiftEnumEquivalent' I can use the 'someProperty' as it infers that any of the types in the union have this property.
This isn't directly possible in swift. I understand you can have computed properties on enums where you can switch on all possible values of the enum and present it manually, but this is more work than what I have to do in typescript. E.g.:
protocol SomeProtocol {
var someProperty: String {get}
}
enum MyEnum: SomeProtocol {
case a(SomeProtocol)
case b(SomeProtocol)
var someProperty {
switch self {
... etc here
}
}
}
Some I'm wondering what the technical reason that the same thing in can't be achieved in swift. I understand they're both 'sum types' but thats as far as my knowledge goes.
1 Like
Reminds me of a case I ran into where every case of DecodingError
has a Context
associated, and I wanted a general purpose way of accessing that, so I ended up with this:
extension DecodingError
{
var context: Context
{
switch self {
case .dataCorrupted(let context),
.keyNotFound(_, let context),
.typeMismatch(_, let context),
.valueNotFound(_, let context):
return context
@unknown default:
return Context(codingPath: [], debugDescription: "")
}
}
}
I think that's similar to what you were talking about. What do you think would be a better solution for this?
If you don't need/want MyEnum
to conform you can also use @dynamicMemberLookup
like this if you just need properties not functions:
protocol SomeProtocol {
var someProperty: String {get}
}
@dynamicMemberLookup
enum MyEnum {
case a(SomeProtocol)
case b(SomeProtocol)
public subscript<T>(dynamicMember member: KeyPath<SomeProtocol, T>) -> T {
switch self {
case let .a(someProtocol),
let .b(someProtocol):
return someProtocol[keyPath: member]
}
}
}
If you do need MyEnum
to conform I would do as @David_Catmull is suggesting have a private context property that returns the SomeProtocol
value and your conformances to the protocol accesses the context and maps to its implementation for the protocol like this:
enum MyEnum: SomeProtocol {
case a(SomeProtocol)
case b(SomeProtocol)
private var context: SomeProtocol {
switch self {
case let .a(context),
let .b(context):
return context
}
}
var someProperty: String { context.someProperty }
}