I am have created a predicate builder that takes predicates (functions with a single input and produce a boolean) and can chain them using boolean logic, so AND, OR and NOT. It is inspired by the Java Predicate
class. This is mostly to practice my TypeScript, so general feedback is of course appreciated. However, I am interested in few points:
- Naming. I don't like the
Builder
suffix here as it doesn't seem to fit. Although, I cannot exactly explain why it feels wrong. Partly, I'm used to builders producing an object as a result, not to be containers for the logic that will be later applied. This seems more of a Functor. Furthermore "predicate" is the mathematical and general term for a function of type(x: any) => boolean
, so I don't want to name the class that way. Java gets away with it because it has functional interfaces. - Repetition. When using
.and()
and.or()
the return is the same aside from the operator being used. This feels off. When I add a new method like.xor()
, then on one hand, I don't want to repeat this. On the other,.xor()
will not be implemented using a single operator. There should be a way to generalise the logic that I simply don't see. - Overloaded methods. Since you can't actually have method overloading in TypeScript/JavaScript you need to accept all possible signatures and sort out what to do in the method. Is
.extractPredicate()
a decent way to go about this in my case? Or is there something more idiomatic or clearer?
interface Predicate<T> {
(x: T) : boolean
}
class PredicateBuilder<T> {
condition: Predicate<T>;
constructor(condition: Predicate<T>) {
this.condition = condition;
}
public and(input: PredicateBuilder<T> | Predicate<T>): PredicateBuilder<T> {
const p: Predicate<T> = this.condition;
const q: Predicate<T> = this.extractCondition(input);
return new PredicateBuilder((x: T) => p(x) && q(x));
}
public or(input: PredicateBuilder<T> | Predicate<T>): PredicateBuilder<T> {
const p: Predicate<T> = this.condition;
const q: Predicate<T> = this.extractCondition(input);
return new PredicateBuilder((x: T) => p(x) || q(x));
}
public not(): PredicateBuilder<T> {
return new PredicateBuilder((x: T) => !this.condition(x));
}
public apply(x: T): boolean {
return this.condition(x);
}
private extractCondition(input: PredicateBuilder<T> | Predicate<T>): Predicate<T> {
let condition: Predicate<T>;
if ("condition" in input) {
condition = input.condition;
} else {
condition = input;
}
return condition;
}
}
extractCondition
-Thing is somewhat, meh. The simplest and cleanest here would be:return new PredicateBuilder((x: T) => p(x) || input.aply(x))
and get rid of the allowedPredicate<T>
type as parameter. I'd think that would be more consistent. As you want some, let's say static method, to construct you a PredicateBuilder object and pass on this throughout the "API" \$\endgroup\$