最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

swift - Deepest nested associated value (extracted recursively) - Stack Overflow

programmeradmin4浏览0评论
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
  • If this can be answered with basic swift without any library then what exactly is the question? To be able to recursively extract the value from any enum that has associated values? – Joakim Danielson Commented 2 days ago
  • What if there is a case foo(SomeType, SomethingElse)? Are you assuming that each case will have at most one associated value? – Sweeper Commented 2 days ago
  • Nice question, @Sweeper. If I correctly understand the library, in a case of multiple associated values the function will return a tuple. It will not pass "as? T" and thus return nil. Which totally suits me. Your assumption about "at most 1 associated value" is also ok though. – Roman Commented yesterday
Add a comment  | 

1 Answer 1

Reset to default 1

I 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"
发布评论

评论列表(0)

  1. 暂无评论