import CasePaths
extension CasePathable where AllCasePaths: CasePathReflectable<Self> {
func associatedValue<T: CasePathable>() -> T? where T.AllCasePaths: CasePathReflectable<T> {
let caseKeyPath = Self.allCasePaths[self]
return self[case: caseKeyPath] as? T
}
}
Note: strictly speaking, familiarity with the PointFree's swift-case-paths library I'm using here is not required. Fundamentally, my question pertains solely to Swift itself.
So, given the correct function above, the goal is to apply it recursively, in order to retrieve the deepest nested value.
But how to handle the types here? Because each associated value could be a different type. The problem is that the associatedValue()
function returns a generic T
, which must be specified. But for a general solution, we can't know each T
in advance.
I'll give some concrete example for more context:
@CasePathable
enum A {
case a(B)
case smthA
}
@CasePathable
enum B {
case b(C)
case smthB
}
@CasePathable
enum C {
case c
case smthC
}
let aValue: A = .a(.b(.c))
let bAssValue: B = aValue.associatedValue()! // .b(.c))
let cAssValue: C = bAssValue.associatedValue()! // .c
import CasePaths
extension CasePathable where AllCasePaths: CasePathReflectable<Self> {
func associatedValue<T: CasePathable>() -> T? where T.AllCasePaths: CasePathReflectable<T> {
let caseKeyPath = Self.allCasePaths[self]
return self[case: caseKeyPath] as? T
}
}
Note: strictly speaking, familiarity with the PointFree's swift-case-paths library I'm using here is not required. Fundamentally, my question pertains solely to Swift itself.
So, given the correct function above, the goal is to apply it recursively, in order to retrieve the deepest nested value.
But how to handle the types here? Because each associated value could be a different type. The problem is that the associatedValue()
function returns a generic T
, which must be specified. But for a general solution, we can't know each T
in advance.
I'll give some concrete example for more context:
@CasePathable
enum A {
case a(B)
case smthA
}
@CasePathable
enum B {
case b(C)
case smthB
}
@CasePathable
enum C {
case c
case smthC
}
let aValue: A = .a(.b(.c))
let bAssValue: B = aValue.associatedValue()! // .b(.c))
let cAssValue: C = bAssValue.associatedValue()! // .c
Share
Improve this question
edited 2 days ago
David Pasztor
54.7k9 gold badges96 silver badges127 bronze badges
asked 2 days ago
RomanRoman
1,5001 gold badge15 silver badges28 bronze badges
3
|
1 Answer
Reset to default 1I will assume that each enum case will have at most 1 associated value.
Since you don't know the intermediate types, you need to work with existential types.
any CasePathable
cannot capture the constraint of where AllCasePaths: CasePathReflectable<Self>
, so you need to introduce an extra protocol to represent such a type.
protocol ReflectableCasePathable: CasePathable where AllCasePaths: CasePathReflectable<Self> {}
All your enums should conform to ReflectableCasePathable
.
You also need a version of associatedValue
that returns an any ReflectableCasePathable
.
extension ReflectableCasePathable {
func associatedValueExistential() -> (any ReflectableCasePathable)? {
let caseKeyPath = Self.allCasePaths[self]
return self[case: caseKeyPath] as? (any ReflectableCasePathable)
}
// the generic version can reuse the existential version
func associatedValue<T: ReflectableCasePathable>() -> T? {
associatedValueExistential() as? T
}
}
Note that associatedValue
doesn't need to depend on the AllCasePaths: CasePathReflectable<Self>
constraint. It can instead depend on the CasePathIterable
protocol instead, where you can find the correct case path by iteration.
extension CasePathIterable {
func associatedValueExistential() -> (any CasePathIterable)? {
for path in Self.allCasePaths {
if let value = self[case: path],
let iterableValue = value as? (any CasePathIterable) {
return iterableValue
}
}
return nil
}
}
Then you don't have to introduce the new protocol, but I digress.
In any case, you can just call this in a loop.
func recursivelyUnwrap(_ start: any ReflectableCasePathable) -> any ReflectableCasePathable {
var r = start
while let x = r.associatedValueExistential() {
r = x
}
return r
}
let aValue: A = .a(.b(.c))
print(recursivelyUnwrap(aValue)) // prints "c"
Also consider this alternative design, which allows you to extract the non-enum associated value at the "bottommost" level.
extension ReflectableCasePathable {
func associatedValueExistential() -> Any {
let caseKeyPath = Self.allCasePaths[self]
return self[case: caseKeyPath]! // AFAIK this will never be nil because caseKeyPath = Self.allCasePaths[self]
}
}
func recursivelyUnwrap(_ start: any ReflectableCasePathable) -> Any {
var r = start
while true {
let associated = r.associatedValueExistential()
if associated is Void { // this means the enum has no associated value
return r
}
if let nextLevel = associated as? (any ReflectableCasePathable) {
r = nextLevel
} else {
return associated
}
}
}
This allows you to do things like this:
let a: A = .a(.b(.smthC))
let b: A = .a(.b(.c("Hello"))) // suppose C.c has a string associated value
print(recursivelyUnwrap(a)) // C.smthC
print(recursivelyUnwrap(b)) // "Hello"
case foo(SomeType, SomethingElse)
? Are you assuming that each case will have at most one associated value? – Sweeper Commented 2 days ago