例如我有一个简单的分类器
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 ...
...
}
在这种情况下?
答案 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));
}