2
\$\begingroup\$

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;
  }
}

Playground Link including a small set of unit tests

\$\endgroup\$
1
  • 1
    \$\begingroup\$ In general, I like it. The Builder naming bugs me a bit, but it's not my point here. The 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 allowed Predicate<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\$
    – r3dst0rm
    Commented May 15, 2020 at 11:59

1 Answer 1

3
\$\begingroup\$

As pointed out as a comment, I was unhappy about the extractCondition function. This function handles, variables which do conform your Predicate<T> type. Which is fine, but I think this doesn't belong here. Because a Predicate should have an accept or, if we talk about the Java implementation, test function. This is not given when we can pass on any random function which do return a boolean. But does not conform our design of a Predicate.

Next naming, the PredicateBuilder seems okay-ish, but it feels wrong. As it isn't a builder in the classical sense. Furthermore we have to work around functional interfaces, and this is I think the main pain point. To achieve this I would propose to rename the Predicate<T> interface, as it should be describing what a Predicate holds (a function which takes a parameter and returns a boolean). Therefore the name Predicate is available again and should be used instead of PredicateBuilder.

The repetition regarding and, or, etc... feels also some kind off. But I think that's because you always do extractCondition and other stuff (I've eliminated this in my suggestion, as the Java Predicate implementation does basically the same). Plus I've create a static method Predicate#of(...) to easier "get" a new Predicate. This reduces the mess a bit, but changing the operands is also still a bit clumsy, in my opinion.

Oh and btw. I've felt free to use arrow functions :-)

interface InternalPredicate<T> {
  (x: T) : boolean
}

class Predicate<T> {
  constructor(private condition: InternalPredicate<T>) {}

  public static of = <T>(condition: InternalPredicate<T>) => {
    return new Predicate(condition);
  }

  public and = (input: Predicate<T>): Predicate<T> =>
    Predicate.of((x: T) => this.apply(x) && input.apply(x));

  public or = (input: Predicate<T>): Predicate<T> =>
    Predicate.of((x: T) => this.apply(x) || input.apply(x));

  public not = (): Predicate<T> =>
    Predicate.of((x: T) => !this.apply(x));

  public apply = (x: T): boolean => this.condition(x);
}

Those are my two cents to the Predicate implementation, feel free to discuss them with me.

Alternative solution to eliminate the InternalPredicate and to support functions which are booleans:

type PredicateType<T> = (x: T) => boolean;

class Predicate<T> {
        constructor(private condition: PredicateType<T>) {}

        private static isInstance = <T>(input: Predicate<T> | PredicateType<T>): Predicate<T> => (input instanceof Predicate) ? input : Predicate.of(input);

        public static of = <T>(condition: PredicateType<T>) => new Predicate(condition);

        public and = (input: Predicate<T> | PredicateType<T>): Predicate<T> =>
          Predicate.of((x: T) => this.apply(x) && Predicate.isInstance(input).apply(x));

        public or = (input: Predicate<T> | PredicateType<T>): Predicate<T> =>
          Predicate.of((x: T) => this.apply(x) || Predicate.isInstance(input).apply(x));

        public not = (): Predicate<T> =>
          Predicate.of((x: T) => !this.apply(x));

        public apply = (x: T): boolean => this.condition(x);
}

It comes pretty close the the extractCondition approach, but the main difference here is, that a new instance is created if the input is a raw function. This adds up to be more robust to changes and being less error prone. Another advantage is, we don't care about how the predicate internally works, we're just in charge of returning a predicate based on the input. This clears up the use case of the function a bit more.

EDIT: As discussed in the comments, it is nicer to merge the private isInstance function into the static of function.

\$\endgroup\$
6
  • 1
    \$\begingroup\$ Good points all around. The of static was something I was considering but wasn't sure about. You've convinced me it's a good idea. I'm not exactly sold on the InternalPredicate name but...the problem is that there are two things here - a predicate function and this predicate class. Both being a predicate in concept but can't use the same name because it'd be confusing. Internal, on the other hand sounds like you shouldn't be using it. I'm leaning towards having a PredicateFn or PredicateFunction instead. It's more accurate. \$\endgroup\$
    – VLAZ
    Commented May 15, 2020 at 13:27
  • 1
    \$\begingroup\$ At any rate, it's important for me to be able to handle both a function an an object being passed in. It's part of why I did this - I wanted to find a good way to handle similar situations. So, I'd want to be able to call isOne.or(x => x === "two") as well as isOne.or(isTwo) (asuming isX are class instances). So, either join together existing predicates as well as create new ones without having to do isOne.or(Predicate.of(x => x === "two")) \$\endgroup\$
    – VLAZ
    Commented May 15, 2020 at 13:27
  • \$\begingroup\$ Regarding the PredicateFn naming, yes, InternalPredicate may sound off as well. But would it be an option to convert the interface to a type? Like type PredicateType<T> = (x: T) => boolean naming it could be: PredicateFunction, TypePredicate or what ever you desire to fit the most. Alright, I thought you would say, that handling any functions is a must. To comply with this, how about a function, which checks if the input is either of type PredicateFunction or an instance of Predicate itself. If the later, just return as is otherwise use the of function? \$\endgroup\$
    – r3dst0rm
    Commented May 15, 2020 at 13:44
  • 1
    \$\begingroup\$ Yes, that sounds better. It's an improved version of extractCondition, which I also dislike, by the way. So, it could be inverted to just produce an instance any time. So something like a input => typeof input === "function" ? Predicate.of(input) : input. (or reverse the check and see if it's an instance). Thinking further, this can be folded into of itself so it either calls the constructor or returns the input to ensure smoother interface. Would something like this make sense? (shortened URL to TS playground) \$\endgroup\$
    – VLAZ
    Commented May 15, 2020 at 14:17
  • 1
    \$\begingroup\$ I'd wait until tomorrow to accept to not discourage any other feedback. Given that it took 10 days to get any feedback, I'm not exactly hopeful but still. \$\endgroup\$
    – VLAZ
    Commented May 15, 2020 at 15:24

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.