[Pitch] Swift Predicates

Hey @ser-0xff, thanks for the additional explanation! The last approach you have there would work as you mentioned, but as you add more properties it would become much more complex. If you need to dynamically create a predicate while analyzing what should go into the predicate, you can do so by manually constructing the expression tree. Unfortunately, since this is a more advanced use case you wouldn't be able to use the macro to help here, but you could write something along the lines of the following:

func makePredicate(level: Int?, name: String?) -> Predicate<Monster> {
    func buildConjunction(lhs: some StandardPredicateExpression<Bool>, rhs: some StandardPredicateExpression<Bool>) -> any StandardPredicateExpression<Bool> {
        PredicateExpressions.Conjunction(lhs: lhs, rhs: rhs)
    }
    
    return Predicate<Monster>({ input in
        var conditions: [any StandardPredicateExpression<Bool>] = []
        if let level {
            conditions.append(
                PredicateExpressions.Equal(
                    lhs: PredicateExpressions.KeyPath(root: input, keyPath: \Monster.level),
                    rhs: PredicateExpressions.Value(level)
                )
            )
        }
        if let name {
            conditions.append(
                PredicateExpressions.Equal(
                    lhs: PredicateExpressions.KeyPath(root: input, keyPath: \Monster.name),
                    rhs: PredicateExpressions.Value(name)
                )
            )
        }
        guard let first = conditions.first else {
            return PredicateExpressions.Value(true)
        }
        
        let closure: (any StandardPredicateExpression<Bool>, any StandardPredicateExpression<Bool>) -> any StandardPredicateExpression<Bool> = {
            buildConjunction(lhs: $0, rhs: $1)
        }
        return conditions.dropFirst().reduce(first, closure)
    })
}

In short, we create a list of the conditions that need to be met and build up the list based on which parameters are specified to the makePredicate function. We can then reduce this array into a single tree of conjunctions to ensure that all of the conditions are met. There are a few small hoops to jump through here in order to satisfy the type-checker with the use of generics such as the closure and separate buildConjunction function, but this allows you to just append to conditions for each new property rather than needing to work with a combinatorial explosion of conditions using the macro.

4 Likes