10

我有一个简单的分类器:

struct Clf {
    x: f64,
}

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

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

Fn在这种情况下如何编写实现?

impl Fn for Clf {
    extern "rust-call" fn call(/*...*/) {
        // ...
    }
}
4

4 回答 4

12

简短的回答是:你不能。至少它不会按照你想要的方式工作。我认为展示这一点的最好方法是走过去看看会发生什么,但总体思路是 Rust 不支持函数重载。

对于这个例子,我们将实现FnOnce,因为Fnrequires FnMutwhich requires 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
        }
    }
}

trait 系列的参数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()
    }
}

Rust 1.33 nightly之前,你不能直接调用c(v)或什至c(0.5)(之前工作过);我们会得到一个关于函数类型未知的错误。基本上,这些版本的 Rust 不支持函数重载。但是我们仍然可以使用完全限定的语法调用函数,其中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.into_iter().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]使其更通用,然后您仍然可以Vec通过引用它们来将其与 s 一起使用:c.classify_slice(&v).

于 2016-07-30T09:53:28.197 回答
11

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

如果你从抽象开始

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,将其包装在 a 中并使用 aVecOrScalar提取真实类型。Tmatch

你也想要

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

但如果没有更高种类的类型,这有点棘手。相反,你可以做

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

如果你允许一个可以恐慌的分支。

因此,该特征将是

trait FromVecOrScalar<T> {
    type Output;

    fn put(self) -> VecOrScalar<T>;

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

有实现

impl<T> FromVecOrScalar<T> for T {
    type Output = bool;

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

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

    fn put(self) -> VecOrScalar<T> {
        Vector(self)
    }

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

然后它将通过实现FnOnceforT: 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_方法基本相同的程序集。

这并不是说写作很好。像这样的超载是一种痛苦、脆弱,而且肯定是 A Bad Idea™。不要这样做,尽管知道你可以做到这一点很好。

于 2016-07-30T12:17:09.123 回答
5

你不能(但要读到答案结束)。

首先,Fn*明确地实现特征家族是不稳定的,并且随时可能发生变化,因此依赖它是一个坏主意。

其次,更重要的是,Rust 1.33 nightly之前的 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[..]));
}

如何使用Fn*特征

为了完整起见,我将其包括在内;实际上不要这样做。 它不仅不受支持,而且非常丑陋。

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

    // Before 1.33 nightly
    println!("{}", FnOnce::call_once(c, (0.5f64,)));
    println!("{:?}", FnOnce::call_once(c, (&v[..],)));

    // After
    println!("{}", c(0.5f64));
    println!("{:?}", c(&v[..]));
}
于 2016-07-30T09:43:58.293 回答
-2

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

#![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));
}
于 2019-07-03T22:50:46.843 回答