Skip to content

Instantly share code, notes, and snippets.

@fatbobman
Forked from NoahKamara/CompoundPredicate.swift
Created February 5, 2024 12:46
Show Gist options
  • Save fatbobman/b59d67a0038cae748982f73316fa9464 to your computer and use it in GitHub Desktop.
Save fatbobman/b59d67a0038cae748982f73316fa9464 to your computer and use it in GitHub Desktop.
Combining New Swift Predicates
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)
})
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment