Skip to content

Instantly share code, notes, and snippets.

@NoahKamara
Last active May 8, 2024 03:50
Show Gist options
  • Save NoahKamara/d8660881b2ef8d6be18b8e26ed349bb7 to your computer and use it in GitHub Desktop.
Save NoahKamara/d8660881b2ef8d6be18b8e26ed349bb7 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)
})
}
}
@NoahKamara
Copy link
Author

@fatbobman I have added all the Expressions that are mention in the overview page of Predicate
https://github.com/NoahKamara/CompoundPredicate

@fatbobman
Copy link

@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.

@NoahKamara
Copy link
Author

@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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment