Реализовать Fn Trait (оператор вызова) для разных типов аргументов

Например, у меня есть простой классификатор

struct Clf {
    x: f64
}

Классификатор возвращает 0, если наблюдаемое значение меньше x, и 1, если больше x.

Теперь я хочу реализовать оператор вызова для этого классификатора. Однако функция должна иметь возможность принимать в качестве аргументов либо float, либо вектор. В случае вектора на выходе получается вектор 0 или 1, который имеет тот же размер, что и входной вектор. Это должно работать так

let c = Clf { x : 0 };
let v = vec![-1, 0.5, 1];
println!("{}", c(0.5));     // prints 1
println!("{}", c(v));       // prints [0, 1, 1]

Как я могу написать

impl Fn for Clf{
    extern "rust-call" fn call ...
    ...
}

в этом случае?

3 ответа

Решение

Краткий ответ: вы не можете. По крайней мере, это не будет работать так, как вы хотите. Я думаю, что лучший способ показать это - пройтись и посмотреть, что происходит, но общая идея заключается в том, что Rust не поддерживает перегрузку функций.

Для этого примера мы будем реализовывать FnOnce, так как Fn требует FnMut что требует FnOnce, Так что, если бы мы все это отсортировали, мы могли бы сделать это для других функций.

Во-первых, это нестабильно, поэтому нам нужны некоторые флаги

#![feature(unboxed_closures, fn_traits)]

Тогда давайте сделаем impl для принятия f64:

impl FnOnce<(f64,)> for Clf {
    type Output = i32;
    extern "rust-call" fn call_once(self, args: (f64,)) -> i32 {
        if args.0 > self.x {
            1
        } else {
            0
        }
    }
}

Аргументы в пользу Fn Семейство черт поставляется через кортеж, так что это (f64,) синтаксис; это кортеж только с одним элементом.

Это все хорошо, и теперь мы можем сделать c(0.5)хотя будет потреблять c пока мы не реализуем другие черты.

Теперь давайте сделаем то же самое для Vecs:

impl FnOnce<(Vec<f64>,)> for Clf {
    type Output = Vec<i32>;
    extern "rust-call" fn call_once(self, args: (Vec<f64>,)) -> Vec<i32> {
        args.0.iter().map(|&f| if f > self.x { 1 } else { 0 }).collect()
    }
}

Теперь у нас есть проблема. Если мы попробуем c(v) или даже c(0.5) (который работал раньше), мы получаем ошибку о неизвестном типе функции. По сути, Rust не поддерживает перегрузку функций. Но мы все еще можем вызывать функции, используя UFC, где c(0.5) становится FnOnce::call_once(c, (0.5,)),


Не зная вашей более широкой картины, я бы хотел решить эту проблему, просто дав Clf две функции вроде так:

impl Clf {
    fn classify(&self, val: f64) -> u32 {
        if val > self.x { 1 } else { 0 }
    }

    fn classify_vec(&self, vals: Vec<f64>) -> Vec<u32> {
        vals.map(|v| self.classify(v)).collect()
    }
}

Тогда ваш пример использования становится

let c = Clf { x : 0 };
let v = vec![-1, 0.5, 1];
println!("{}", c.classify(0.5));     // prints 1
println!("{}", c.classify_vec(v));       // prints [0, 1, 1]

Я бы на самом деле хотел сделать вторую функцию classify_slice и возьми &[f64] если быть более общим, то вы все равно можете использовать его с vecs, ссылаясь на них: c.classify_slice(&v),

Это действительно возможно, но вам нужна новая черта и куча беспорядка.

Если вы начнете с абстракции

enum VecOrScalar<T> {
    Scalar(T),
    Vector(Vec<T>),
}

use VecOrScalar::*;

Вы хотите способ использовать преобразования типа

T      (hidden) -> VecOrScalar<T> -> T      (known)
Vec<T> (hidden) -> VecOrScalar<T> -> Vec<T> (known)

потому что тогда вы можете взять "скрытый" тип Tзаверните в VecOrScalar и извлечь реальный тип T с match,

Вы также хотите

T      (known) -> bool      = T::Output
Vec<T> (known) -> Vec<bool> = Vec<T>::Output

но без HKT это немного сложно. Вместо этого вы можете сделать

T      (known) -> VecOrScalar<T> -> T::Output
Vec<T> (known) -> VecOrScalar<T> -> Vec<T>::Output

если вы разрешите ветку, которая может паниковать.

Таким образом, черта будет

trait FromVecOrScalar<T> {
    fn put(self) -> VecOrScalar<T>;

    type Output;
    fn get(out: VecOrScalar<bool>) -> Self::Output;
}

с реализациями

impl<T> FromVecOrScalar<T> for T {
    fn put(self) -> VecOrScalar<T> {
        Scalar(self)
    }

    type Output = bool;
    fn get(out: VecOrScalar<bool>) -> Self::Output {
        match out {
            Scalar(val) => val,
            Vector(_) => panic!("Wrong output type!"),
        }
    }
}
impl<T> FromVecOrScalar<T> for Vec<T> {
    fn put(self) -> VecOrScalar<T> {
        Vector(self)
    }

    type Output = Vec<bool>;
    fn get(out: VecOrScalar<bool>) -> Self::Output {
        match out {
            Vector(val) => val,
            Scalar(_) => panic!("Wrong output type!"),
        }
    }
}

Твой класс

#[derive(Copy, Clone)]
struct Clf {
    x: f64,
}

сначала реализуем две ветви:

impl Clf {
    fn calc_scalar(self, f: f64) -> bool {
        f > self.x
    }

    fn calc_vector(self, v: Vec<f64>) -> Vec<bool> {
        v.into_iter().map(|x| self.calc_scalar(x)).collect()
    }
}

Затем он будет отправлять путем реализации FnOnce за T: FromVecOrScalar<f64>

impl<T> FnOnce<(T,)> for Clf
    where T: FromVecOrScalar<f64>
{

с типами

    type Output = T::Output;
    extern "rust-call" fn call_once(self, (arg,): (T,)) -> T::Output {

Диспетчер сначала упаковывает приватный тип, так что вы можете извлечь его с помощью enum, а потом T::gets результат, чтобы скрыть это снова.

        match arg.put() {
            Scalar(scalar) =>
                T::get(Scalar(self.calc_scalar(scalar))),
            Vector(vector) =>
                T::get(Vector(self.calc_vector(vector))),
        }
    }
}

Тогда успех:

fn main() {
    let c = Clf { x : 0.0 };
    let v = vec![-1.0, 0.5, 1.0];
    println!("{}", c(0.5f64));
    println!("{:?}", c(v));
}

Поскольку компилятор может видеть всю эту малярию, он фактически полностью компилируется в практически ту же сборку, что и прямой вызов calc_ методы.

Но это не значит, что это приятно писать. Такая перегрузка - боль, хрупкая и, безусловно, плохая идея ™. Не делай этого, хотя приятно знать, что ты можешь.

Ты не можешь

Прежде всего, реализация Fn* Семейство черт явно нестабильно и может изменяться в любое время, поэтому было бы неплохо полагаться на это.

Во-вторых, и что более важно, компилятор Rust просто не позволит вам вызвать значение, которое имеет Fn* реализации для различных типов аргументов. Он просто не может понять, чего вы хотите, потому что, как правило, этого не происходит. Единственный способ обойти это - полностью указать черту, которую вы хотели назвать, но в этот момент вы потеряли все возможные эргономические преимущества этого подхода.

Просто определите и реализуйте свою собственную черту вместо того, чтобы пытаться использовать Fn* черты. Я взял на себя смелость с вопросом, чтобы избежать / исправить сомнительные аспекты.

struct Clf {
    x: f64,
}

trait ClfExt<T: ?Sized> {
    type Result;
    fn classify(&self, arg: &T) -> Self::Result;
}

impl ClfExt<f64> for Clf {
    type Result = bool;
    fn classify(&self, arg: &f64) -> Self::Result {
        *arg > self.x
    }
}

impl ClfExt<[f64]> for Clf {
    type Result = Vec<bool>;
    fn classify(&self, arg: &[f64]) -> Self::Result {
        arg.iter()
            .map(|v| self.classify(v))
            .collect()
    }
}

fn main() {
    let c = Clf { x : 0.0 };
    let v = vec![-1.0, 0.5, 1.0];
    println!("{}", c.classify(&0.5f64));
    println!("{:?}", c.classify(&v[..]));
}

Примечание: включено для полноты картины; на самом деле не делайте этого. Мало того, что это не поддерживается, это чертовски уродливо.

#![feature(fn_traits, unboxed_closures)]

#[derive(Copy, Clone)]
struct Clf {
    x: f64,
}

impl FnOnce<(f64,)> for Clf {
    type Output = bool;
    extern "rust-call" fn call_once(self, args: (f64,)) -> Self::Output {
        args.0 > self.x
    }
}

impl<'a> FnOnce<(&'a [f64],)> for Clf {
    type Output = Vec<bool>;
    extern "rust-call" fn call_once(self, args: (&'a [f64],)) -> Self::Output {
        args.0.iter().cloned()
            .map(|v| { FnOnce::call_once(self, (v,)) })
            .collect()
    }
}

fn main() {
    let c = Clf { x : 0.0 };
    let v = vec![-1.0, 0.5, 1.0];
    println!("{}", FnOnce::call_once(c, (0.5f64,)));
    println!("{:?}", FnOnce::call_once(c, (&v[..],)));
}

Вы можете использовать ночные и нестабильные функции:

#![feature(fn_traits, unboxed_closures)]
struct Clf {
    x: f64,
}

impl FnOnce<(f64,)> for Clf {
    type Output = i32;
    extern "rust-call" fn call_once(self, args: (f64,)) -> i32 {
        if args.0 > self.x {
            1
        } else {
            0
        }
    }
}

impl FnOnce<(Vec<f64>,)> for Clf {
    type Output = Vec<i32>;
    extern "rust-call" fn call_once(self, args: (Vec<f64>,)) -> Vec<i32> {
        args.0
            .iter()
            .map(|&f| if f > self.x { 1 } else { 0 })
            .collect()
    }
}

fn main() {
    let c = Clf { x: 0.0 };
    let v = vec![-1.0, 0.5, 1.0];
    println!("{:?}", c(0.5));

    let c = Clf { x: 0.0 };
    println!("{:?}", c(v));
}
Другие вопросы по тегам