-
-
Save NoahKamara/d8660881b2ef8d6be18b8e26ed349bb7 to your computer and use it in GitHub Desktop.
import Foundation | |
@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) | |
}) | |
} | |
} |
@NoahKamara I'm really glad to hear that, and I wish you success soon! I've just published an article discussing the challenges currently faced when composing complex predicates for SwiftData. If your library is completed, I would be very happy to recommend it to everyone.
https://fatbobman.com/en/posts/how-to-dynamically-construct-complex-predicates-for-swiftdata/
@fatbobman just an fyi: the initial loading time for your website is ~25 secs I'm guessing this is due to my location (Germany)
@fatbobman I have added all the Expressions that are mention in the overview page of Predicate
https://github.com/NoahKamara/CompoundPredicate
@NoahKamara I had considered this implementation approach before, but it felt like there was too much work to be done, so I gave up. Bravo, you've done an impressive job. I will update the article later and recommend your achievements to more users.
@fatbobman thanks for the mention :) It was actually way less work than i thought. basically just call the initializer of each predicate with the properties of the self
instance. since all of them must be expressions as well i can just recursively call my implementation of replacing(variable, replacement)
and only need to add proper logic for the Variable expression
@fatbobman
I've started work on a library that with a protocol for traversing a predicate and replacing a variable with another.
I've implemented the protocol for Equal and a few other expressions and added tests showing that this works with SwiftData
This solves the unsupportedPredicate issue because there are no custom predicates.
Implementation for all expressions should be fairly easy. It is essentially a recursive call on all child predicates that either ends in a predicate that does not support conversion, a leaf (returning a copy of self), or a variable, in which case it returns the replacement variable.
I plan on further building this proof of concept into a full library