尝试编写复杂谓词时出现编译器错误 - 场景 2

问题描述 投票:0回答:1

这是此处所涵盖的问题和答案的附加场景。

示例代码可在此处获得。

原始问题与此谓词相关:

let aPred = #Predicate<Event> {
            $0.dateStart != nil &&
            ($0.dateStart >= startOfDay && $0.dateStart <= endOfDay) ||
            ($0.dateEnd >= startOfDay && $0.dateEnd <= endOfDay) ||
            ($0.dateStart < startOfDay && $0.dateEnd >= startOfDay)
}

这给出了编译错误:

编译器无法以合理的方式对该表达式进行类型检查 时间;尝试将表达式分解为不同的子表达式。

可以借助此扩展来解决这个问题:(信用Mojtaba Hosseini

extension Optional: Comparable where Wrapped: Comparable {
    public static func < (lhs: Wrapped?, rhs: Wrapped?) -> Bool {
        guard let lhs, let rhs else { return false }
        return lhs < rhs
    }
}

现在的问题是,如果我向我的

@Model final class TestEvent
添加一个可选的 bool
var isActive: Bool?
,我会再次遇到相同的错误。

let aPred = #Predicate<TestEvent> {
            $0.isActive == true && //comment this out and it will compile
            $0.dateStart != nil &&
            ($0.dateStart >= startOfDay && $0.dateStart <= endOfDay) ||
            ($0.dateEnd >= startOfDay && $0.dateEnd <= endOfDay) ||
            ($0.dateStart < startOfDay && $0.dateEnd >= startOfDay)
        }

条件:

  • SwiftData(不是 CoreData)
  • CloudKit 支持(需要将所有属性保留为可选)
  • 具有 nil 值是有效的场景(不能强制展开)

理想的解决方案是将谓词分解为更小的谓词,并以类似的方式将它们组合起来,就像

CoreData using NSCompoundPredicate

中可能的那样
swift macros predicate swift-data swift-optionals
1个回答
0
投票

不要对答案的大小感到恐慌:)

如果你能够展开宏,你就能明白为什么编译器不能按时进行类型检查了!这就是宏为第 4 行实现的内容:

Foundation.Predicate<TodoModel>({
    PredicateExpressions.build_Disjunction(
        lhs: PredicateExpressions.build_Disjunction(
            lhs: PredicateExpressions.build_Conjunction(
                lhs: PredicateExpressions.build_NotEqual(
                    lhs: PredicateExpressions.build_KeyPath(
                        root: PredicateExpressions.build_Arg($0),
                        keyPath: \.dateStart
                    ),
                    rhs: PredicateExpressions.build_NilLiteral()
                ),
                rhs: PredicateExpressions.build_Conjunction(
                    lhs: PredicateExpressions.build_Comparison(
                        lhs: PredicateExpressions.build_KeyPath(
                            root: PredicateExpressions.build_Arg($0),
                            keyPath: \.dateStart
                        ),
                        rhs: PredicateExpressions.build_Arg(startOfDay),
                        op: .greaterThanOrEqual
                    ),
                    rhs: PredicateExpressions.build_Comparison(
                        lhs: PredicateExpressions.build_KeyPath(
                            root: PredicateExpressions.build_Arg($0),
                            keyPath: \.dateStart
                        ),
                        rhs: PredicateExpressions.build_Arg(endOfDay),
                        op: .lessThanOrEqual
                    )
                )
            ),
            rhs: PredicateExpressions.build_Conjunction(
                lhs: PredicateExpressions.build_Comparison(
                    lhs: PredicateExpressions.build_KeyPath(
                        root: PredicateExpressions.build_Arg($0),
                        keyPath: \.dateEnd
                    ),
                    rhs: PredicateExpressions.build_Arg(startOfDay),
                    op: .greaterThanOrEqual
                ),
                rhs: PredicateExpressions.build_Comparison(
                    lhs: PredicateExpressions.build_KeyPath(
                        root: PredicateExpressions.build_Arg($0),
                        keyPath: \.dateEnd
                    ),
                    rhs: PredicateExpressions.build_Arg(endOfDay),
                    op: .lessThanOrEqual
                )
            )
        ),
        rhs: PredicateExpressions.build_Conjunction(
            lhs: PredicateExpressions.build_Comparison(
                lhs: PredicateExpressions.build_KeyPath(
                    root: PredicateExpressions.build_Arg($0),
                    keyPath: \.dateStart
                ),
                rhs: PredicateExpressions.build_Arg(startOfDay),
                op: .lessThan
            ),
            rhs: PredicateExpressions.build_Comparison(
                lhs: PredicateExpressions.build_KeyPath(
                    root: PredicateExpressions.build_Arg($0),
                    keyPath: \.dateEnd
                ),
                rhs: PredicateExpressions.build_Arg(startOfDay),
                op: .greaterThanOrEqual
            )
        )
    )
})

虽然它是如此令人惊叹和强大,但不幸的是这种力量让苹果工程师在制作中带来了这样的混乱:)

因此,基于这些 exteam 实现,您可以实现类似于

build_Conjunction
build_Disjunction
的功能来合并 SwiftData 谓词:

(👇您只需要这个文件即可启用👇)

@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *)
/// Allows you to use an existing Predicate as a ``StandardPredicateExpression``
struct VariableWrappingExpression<T>: StandardPredicateExpression {
    let predicate: Predicate<T>
    let variable: PredicateExpressions.Variable<T>
    
    func evaluate(_ bindings: PredicateBindings) throws -> Bool {
        // resolve the variable
        let value = try variable.evaluate(bindings)
        
        // create bindings for the expression of the predicate
        let innerBindings = bindings.binding(predicate.variable, to: value)
        
        return try predicate.expression.evaluate(innerBindings)
    }
}

@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *)
extension Predicate {
    typealias Expression = any StandardPredicateExpression<Bool>
    
    /// Returns the result of combining the predicates using the given closure.
    ///
    /// - Parameters:
    ///   - predicates: an array of predicates to combine
    ///   - nextPartialResult: A closure that combines an accumulating expression and
    ///     an expression of the sequence into a new accumulating value, to be used
    ///     in the next call of the `nextPartialResult` closure or returned to
    ///     the caller.
    /// - Returns: The final accumulated expression. If the sequence has no elements,
    ///   the result is `initialResult`.
    static func combining<T>(
        _ predicates: [Predicate<T>],
        nextPartialResult: (Expression, Expression) -> Expression
    ) -> Predicate<T> {
        return Predicate<T>({ variable in
            let expressions = predicates.map({
                VariableWrappingExpression<T>(predicate: $0, variable: variable)
            })
            guard let first = expressions.first else {
                return PredicateExpressions.Value(true)
            }
            
            let closure: (any StandardPredicateExpression<Bool>, any StandardPredicateExpression<Bool>) -> any StandardPredicateExpression<Bool> = {
                nextPartialResult($0,$1)
            }
            
            return expressions.dropFirst().reduce(first, closure)
        })
    }
}

@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *)
public extension Array {
    /// Joins multiple predicates with an ``PredicateExpressions.Conjunction``
    /// - Returns: A predicate evaluating to true if **all** sub-predicates evaluate to true
    func conjunction<T>() -> Predicate<T> where Element == Predicate<T> {
        func buildConjunction(lhs: some StandardPredicateExpression<Bool>, rhs: some StandardPredicateExpression<Bool>) -> any StandardPredicateExpression<Bool> {
            PredicateExpressions.Conjunction(lhs: lhs, rhs: rhs)
        }
        
        return Predicate<T>.combining(self, nextPartialResult: {
            buildConjunction(lhs: $0, rhs: $1)
        })
    }
    
    /// Joins multiple predicates with an ``PredicateExpressions.Disjunction``
    /// - Returns: A predicate evaluating to true if **any** sub-predicate evaluates to true
    func disjunction<T>() -> Predicate<T> where Element == Predicate<T> {
        func buildConjunction(lhs: some StandardPredicateExpression<Bool>, rhs: some StandardPredicateExpression<Bool>) -> any StandardPredicateExpression<Bool> {
            PredicateExpressions.Disjunction(lhs: lhs, rhs: rhs)
        }
        
        return Predicate<T>.combining(self, nextPartialResult: {
            buildConjunction(lhs: $0, rhs: $1)
        })
    }
}

您可以将谓词分解为更小的块,例如:

let activePredicate = #Predicate<TodoModel> {
    $0.isActive == true
}

let nullCheckPredicate = #Predicate<TodoModel> {
    $0.dateStart != nil && $0.dateEnd != nil
}

let rangesPredicates =  #Predicate<TodoModel> {
    ($0.dateStart >= startOfDay && $0.dateStart <= endOfDay) ||
    ($0.dateEnd >= startOfDay && $0.dateEnd <= endOfDay) ||
    ($0.dateStart < startOfDay && $0.dateEnd >= startOfDay)
}

conjunct
disjunct
他们喜欢:

[activePredicate, nullCheckPredicate, rangesPredicates].conjunction()

[activePredicate, nullCheckPredicate, rangesPredicates].disjunction()

如上所述,要点文件可以在here找到,并且学分转到 NoahKamara、nOk 和其他开发这些强大扩展的贡献者

© www.soinside.com 2019 - 2024. All rights reserved.