为不同类型的参数实现Fn Trait(调用操作符)

时间:2016-07-30 09:08:30

标签: operator-overloading rust function-call-operator

例如我有一个简单的分类器

struct Clf {
    x: f64
}

如果观察值小于x,则分类器返回0;如果大于x,则分类器返回1.

我现在想要为此分类器实现调用运算符。但是,该函数应该能够将float或vector作为参数。在向量的情况下,输出是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 ...
    ...
}

在这种情况下?

4 个答案:

答案 0 :(得分:7)

这确实是可能的,但你需要一个新的特性和大量的混乱。

如果从抽象开始

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

use VecOrScalar::*;

您想要一种使用类型转换的方法

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

因为那时你可以采取隐藏的&#34;输入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::get结果,以便再次隐藏它。

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

由于编译器可以看到所有这些malarky,它实际上完全编译成与calc_方法的直接调用基本相同的程序集。

但是,这并不是说写起来不错。像这样的重载是一种痛苦,脆弱,而且肯定是一个坏主意™。不要这样做,虽然知道你可以做到这一点很好。

答案 1 :(得分:5)

简短的回答是:你做不到。至少它不会按你想要的方式工作。我认为展示这种情况的最佳方式是走过去看看会发生什么,但一般的想法是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,直到我们实现其他特征。

现在让我们为Vec s做同样的事情:

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不支持函数重载。但我们仍然可以使用ufcs调用这些函数,其中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)。< / p>

答案 2 :(得分:2)

你不能。

首先,明确地实施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[..],)));
}

答案 3 :(得分:0)

您可以使用夜间和不稳定功能:

#![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));
}