Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add map combinator to map input values to a predicate #142

Open
holly-hacker opened this issue Apr 28, 2023 · 2 comments
Open

Add map combinator to map input values to a predicate #142

holly-hacker opened this issue Apr 28, 2023 · 2 comments

Comments

@holly-hacker
Copy link

I am working on a todo app and I use predicates-rs to filter tasks before displaying them to the user. My Task struct contains a id: TaskId field and I want to use predicate::in_hash to filter out tasks that have an id that is within a certain list.

I currently have code that looks roughly like this:

// base predicate that lets everything through
let predicate = predicate::always().boxed(); // BoxPredicate<Task>

if filter_unactionable {
    let tasks_with_uncompleted_dependencies: Vec<TaskId> = task_database.iter_tasks().filter(|| ...).map(|t| t.id).collect();
    let has_uncompleted_dependencies = predicate::in_hash(tasks_with_uncompleted_dependencies); // HashableInPredicate<TaskId>
    predicate = predicate.and(has_uncompleted_dependencies.not()).boxed(); // error, differing types!
}

// other filters here

// `predicate` is returned and later used to filter which tasks to display

This code does not work because my in_hash predicate expects a value of type TaskId while my main predicate expects Task.

To solve this, I'd like a way to "map" values before a predicate evaluates them. This could look like this (API is just a suggestion):

let predicate = predicate::always().boxed(); // BoxPredicate<Task>

if filter_unactionable {
    let tasks_with_uncompleted_dependencies: Vec<TaskId> = task_database.iter_tasks().filter(|| ...).map(|t| t.id).collect();
    let has_uncompleted_dependencies = predicate::in_hash(tasks_with_uncompleted_dependencies); // HashableInPredicate<TaskId>

    // map from Predicate<Task> to PredicateTaskId
    let has_uncompleted_dependencies_mapped = has_uncompleted_dependencies.map(|task: &Task| task.task_id);

    predicate = predicate.and(has_uncompleted_dependencies_mapped.not()).boxed();
}
@epage
Copy link
Contributor

epage commented May 1, 2023

Would you be up for prototyping this within your project, creating extension traits to provide the syntax you want?

This will

  • Unblock you
  • Allow experimentation and iteration before being pulled into predicates
  • Help clarify what your request is by having it in a real-worl application

@holly-hacker
Copy link
Author

I've already created my own predicate in my own project here: holly-hacker/td@4f71543#diff-ea57b46e48fccc26585162b2fa81cc63b45aa766280a22afa43c881a356b23cdR197

Usage looks roughly like this

let tasks_with_uncompleted_dependencies = self.database.get_all_tasks()
    .filter(|t| ...)
    .map(|t| t.id().clone())
    .collect::<HashSet<_>>();

let has_uncompleted_dependencies = predicate::in_hash(tasks_with_uncompleted_dependencies);

let has_uncompleted_dependencies = MapPredicate::new(has_uncompleted_dependencies, |task: &Task| task.id());

predicate = predicate.and(has_uncompleted_dependencies.not()).boxed();

I opted to not write an extension trait for my current solution, but an ideal api would look something like this instead:

let has_uncompleted_dependencies = has_uncompleted_dependencies.map(|task: &Task| task.id()));

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants