Filters everywhere

Ten days ago I published another crate on crates.io - the ‘filters’ crate.

Its source code was partially extracted from imag. In this article I want to show you what you can do with it as well as list some things that should be improved.

How to Filter<N>

First of all, Filter is a generic trait. It can be implemented for a wide range of things, of course, but is implemented for all T: Fn(&I) -> bool. It requires one function: Filter::filter(&N) -> bool to be implemented. This function should hold the predicate definition. So you can do something like this:

use filters::filter::Filter;
struct EqTo {
    pub i: usize,
}

impl Filter<usize> for EqTo {
    fn filter(&self, n: &usize) -> bool {
        self.i == *n
    }
}

fn filter_with_eqto() {
    let eq = EqTo { i: 0 };
    assert_eq!(eq.filter(&0),  true);
    assert_eq!(eq.filter(&1),  false);
    assert_eq!(eq.filter(&17), false);
    assert_eq!(eq.filter(&42), false);
}

in your codebase. Of course, that is a lot of boilerplate code for simply filtering something for beeing equal to a number, right? That’s why we implemented it for all T: Fn(&I) -> bool - you can boil down the upper example to this:

fn filter_with_eqto() {
    let eq = |&a: &usize| *a == 0;
    assert_eq!(eq.filter(&0),  true);
    assert_eq!(eq.filter(&1),  false);
    assert_eq!(eq.filter(&17), false);
    assert_eq!(eq.filter(&42), false);
}

And/or/not now?

This does not yield any code abstraction improvements yet. But the Filter trait has also has some more functions which are already implemented for you: and, or, not and so on.

This is where the other types from the crate come into play: There are types available which implement logical operations using the Filter trait. And the functions from the trait itself use them so they are convenient:

fn filter_inside_range() {
    let zero = |&a: &usize| *a > 0;
    let hund = |&a: &usize| *a < 100;

    let f = zero.and(hund);

    assert_eq!(f.filter(&0),  false);
    assert_eq!(f.filter(&1),  true);
    assert_eq!(f.filter(&17), true);
    assert_eq!(f.filter(&100), false);
}

And of course you can chain them:

fn filter_inside_range() {
    let zero = |&a: &usize| *a > 0;
    let hund = |&a: &usize| *a < 100;

    let f = zero.and(hund.not()).not().or(true);

    assert_eq!(f.filter(&0),  true);
    assert_eq!(f.filter(&1),  true);
    assert_eq!(f.filter(&17), true);
    assert_eq!(f.filter(&100), true);
}

As shown above, we also implement Into<Bool> for bool so you can easily provide default values for your filters.

What this crate is for

This crate was designed for imag, but should prove helpful elsewhere, too. It was written to be able to construct complex filters based on user input. For example if your application has a commandline interface which allows the user to specify predicates and logical operators, you can use this crate to build these filters and connect them.

That is also exactly the usecase for imag, by the way.

Issues

There are two remaining things I want to implement in the codebase. The crate is usable as of the time of writing, but I also want to do the following things:

  • Implement BitAnd, BitOr, … etc for all filters. With this, one could do (written in short now) (|a| a > 1) & (|b| b < 5) - using bitwise operators to make these things even nicer.
  • Provide a possibility to use a Filter as function, so one can do myvec.iter().filter(foo) instead of myvec.iter().filter(|x| foo.filter(x))

These issues are posted in the github repository of the crate and of course you are welcome to help out.