У меня есть предикатная черта. Он принимает некоторый тип T
и возвращает для него логическое значение.
trait Predicate<T> {
fn evaluate(&self, t: &T) -> bool;
}
У меня также есть оценщик, который оценивает один и тот же предикат для каждого заданного T
.
trait Evaluator<T> {
fn evaluate(&self, is: &[T]) -> Vec<bool>;
}
Evaluator
реализован для Predicate
. Он оценивает Predicate
для каждого T
.
impl<T, P: Predicate<T>> Evaluator<T> for P {
fn evaluate(&self, is: &[T]) -> Vec<bool> {
is.iter().map(|i| self.evaluate(i)).collect()
}
}
У меня также реализован Predicate<T>
для замыканий типа Fn(&T) -> bool
, что позволяет мне предоставлять такое замыкание всякий раз, когда я хочу Evaluator
, вместо того, чтобы создавать структуру и реализовывать для нее признак Evaluator
каждый раз, когда я хочу применить какую-то другую логику.
Теперь я могу создать замыкание и передать его всему, что требует Evaluator
.
let p = |i: &i32| *i > 0;
wants_evaluator(&p);
До сих пор этот API был довольно кратким, но недавно у меня возникла необходимость параллельно оценивать предикаты для каждого решения. Сама логика не является проблемой для вискозы — par_iter()
делает именно то, что мне нужно. Проблема в том, что я не хочу терять удобное автоматическое преобразование замыкания в оценщик, поскольку я во многом от него завишу. В идеале я хочу добиться чего-то вроде этого:
let p = |i: &i32| *i > 0;
let p_par = p.into_par(); // will be evaluated in parallel
wants_evaluator(&p_par);
Параллельная оценка требуется не так часто, потому что для большинства простых предикатов распараллеливание только снижает производительность, однако когда оно мне нужно, оно мне нужно.
Я пытался использовать структуру-обертку ParPredicate
struct ParPredicate<T, P: Predicate<T>>(P, PhantomData<T>);
impl<T, P> Evaluator<T> for ParPredicate<T, P>
where
T: Sync,
P: Predicate<T> + Sync,
{
fn evaluate(&self, is: &[T]) -> Vec<bool> {
is.par_iter().map(|i| self.0.evaluate(i)).collect()
}
}
Однако Rust жалуется, что эта реализация конфликтует с impl<T, P: Predicate<T>> Evaluator<T> for P { ...
conflicting implementations of trait `Evaluator<_>` for type `ParPredicate<_, _>`
downstream crates may implement trait `Predicate<_>` for type `ParPredicate<_, _>`
Если я использую какой-то конкретный тип вместо T
в Predicate
, то эта ошибка не появляется. Это также портит весь смысл Predicate
, поскольку он должен быть общим.
Я также попытался использовать какой-то шаблон состояния типа с Predicate
:
trait ExecutionStrategy {}
enum Sequential {}
impl ExecutionStrategy for Sequential {}
enum Parallel {}
impl ExecutionStrategy for Parallel {}
trait Predicate<ExecutionStrategy, T> {
fn evaluate(&self, i: &T) -> bool;
}
trait Evaluator<ExecutionStrategy, T> {
fn evaluate(&self, is: &[T]) -> Vec<bool>;
}
impl<T, P> Evaluator<Sequential, T> for P
where
P: Predicate<Sequential, T>,
{
fn evaluate(&self, is: &[T]) -> Vec<bool> {
is.iter().map(|i| self.evaluate(i)).collect()
}
}
impl<T, P> Evaluator<Parallel, T> for P
where
T: Sync,
P: Predicate<Parallel, T> + Sync,
{
fn evaluate(&self, is: &[T]) -> Vec<bool> {
is.par_iter().map(|i| self.evaluate(i)).collect()
}
}
Проблема здесь в том, что мне нужно каким-то образом преобразовать Predicate<Sequential, T>
в Predicate<Parallel, T>
, и я понятия не имею, как это сделать, хотя по сути у них один и тот же контакт.
trait IntoPar<T> {
fn into_par(self) -> impl Predicate<Parallel, T>;
}
impl<T, P: Predicate<Sequential, T>> IntoPar<T> for P {
fn into_par(self) -> impl Predicate<Parallel, T> {
// now what?
}
}
Все, что я хочу, это прикрепить к моему Predicate
какой-то маркер, который позволил бы Evaluator
реализовать другую логику для Predicate
на основе его маркера. Вся эта информация собирается во время компиляции, и я не понимаю, почему я теоретически не мог этого добиться. Но как мне это сделать, сохранив плавное преобразование замыкания в Evaluator
?
Вы на правильном пути. Чтобы решить проблему преобразования в Parallel
, вам нужно избавиться от ExecutionStrategy
в Predicate
(но оставить его в Evaluator
):
trait Predicate<T> {
fn evaluate(&self, i: &T) -> bool;
}
trait Evaluator<ExecutionStrategy, T> {
fn evaluate(&self, is: &[T]) -> Vec<bool>;
}
impl<T, P> Evaluator<Sequential, T> for P
where
P: Predicate<T>,
{
fn evaluate(&self, is: &[T]) -> Vec<bool> {
is.iter().map(|i| self.evaluate(i)).collect()
}
}
impl<T, P> Evaluator<Parallel, T> for P
where
T: Sync,
P: Predicate<T> + Sync,
{
fn evaluate(&self, is: &[T]) -> Vec<bool> {
is.par_iter().map(|i| self.evaluate(i)).collect()
}
}
fn wants_evaluator<S, E: Evaluator<S, i32>>(evaluator: E) { ... }
Однако это будет иметь нежелательные последствия: все существующие вызовы wants_evaluator()
перестанут работать с «необходимой аннотацией типа».
Это можно решить с помощью типа маркера:
enum Sequential {}
enum Parallel {}
trait Predicate<T> {
fn evaluate(&self, i: &T) -> bool;
}
trait Evaluator<ExecutionStrategy, T> {
fn evaluate(&self, is: &[T]) -> Vec<bool>;
}
pub struct ParallelPredicate<T, P>(P, PhantomData<T>);
impl<T, P> Evaluator<Parallel, T> for ParallelPredicate<T, P>
where
T: Sync,
P: Predicate<T> + Sync,
{
fn evaluate(&self, is: &[T]) -> Vec<bool> {
is.par_iter().map(|i| self.0.evaluate(i)).collect()
}
}
fn into_par<T, P: Predicate<T>>(predicate: P) -> ParallelPredicate<T, P> {
ParallelPredicate(predicate, PhantomData)
}
impl<T, P> Evaluator<Sequential, T> for P
where
P: Predicate<T>,
{
fn evaluate(&self, is: &[T]) -> Vec<bool> {
is.iter().map(|i| self.evaluate(i)).collect()
}
}
fn wants_evaluator<S, E: Evaluator<S, i32>>(evaluator: E) {
evaluator.evaluate(&[1, 2, 3]);
}
fn main() {
wants_evaluator(|v: &i32| true);
wants_evaluator(into_par(|v: &i32| true));
}
Это действительно работает. Не могли бы вы объяснить, почему ваше решение работает, а мое нет? Как это вообще работает? Например, что такое ExecutionStrategy
в этом контексте? Это какой-то шаблон типа состояния типа для структур?
Если у вас есть решение, отличное от существующих ответов, опубликуйте сообщение с ответом. Решения не принадлежат вопросу.