2016-07-30 2 views
2

Например, у меня есть простой классификаторРеализовать Fn Черту (оператор вызова) для различных типов аргументов

struct Clf { 
    x: f64 
} 

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

Теперь я хочу реализовать оператор вызова для этого классификатора. Однако функция должна иметь возможность принимать либо аргумент 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 ... 
    ... 
} 

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

ответ

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[..])); 
} 

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

#![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[..],))); 
} 
2

Короткий ответ: вы не можете. По крайней мере, это не будет работать так, как вы хотите. Я думаю, что лучший способ показать, что это пройти и посмотреть, что происходит, но общая идея заключается в том, что 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 с:

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).

4

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

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

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::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_ методов.

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

+0

Спасибо большое! – asdetrefle

Смежные вопросы