2016-07-30 13 views
2

Zum Beispiel habe ich einen einfachen KlassifiziererImplementieren Fn Trait (Call Operator) für verschiedene Arten von Argumenten

struct Clf { 
    x: f64 
} 

Der Klassifikator liefert 0, wenn der beobachtete Wert kleiner als x und 1, wenn größer als x haben.

Ich möchte jetzt den Aufrufoperator für diesen Klassifizierer implementieren. Die Funktion sollte jedoch in der Lage sein, entweder einen Float oder einen Vektor als Argumente zu verwenden. Im Fall eines Vektors ist die Ausgabe ein Vektor von 0 oder 1, der die gleiche Größe wie der Eingabevektor hat. Es sollte

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] 

wie diese Arbeit Wie kann ich die

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

in diesem Fall schreiben?

Antwort

2

Sie können nicht.

Zuallererst ist die Implementierung der Fn* Familie von Eigenschaften explizit instabil und unterliegt Änderungen zu jeder Zeit, so dass es eine schlechte Idee wäre, davon abhängig zu sein.

Zweitens, und noch wichtiger, der Rust-Compiler nur wird nicht lassen Sie einen Wert aufrufen, der Fn* Implementierungen für verschiedene Argumenttypen hat. Es kann einfach nicht funktionieren, was Sie wollen, da es normalerweise nicht möglich ist. Der einzige Weg, um das Merkmal, das Sie anrufen möchten, vollständig zu spezifizieren, aber an diesem Punkt haben Sie keinen möglichen ergonomischen Vorteil dieses Ansatzes verloren.

Definieren und implementieren Sie Ihr eigenes Merkmal, anstatt zu versuchen, die Fn* Merkmale zu verwenden. Ich habe mir mit der Frage einige Freiheiten genommen, um fragwürdige Aspekte zu vermeiden.

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

Hinweis: Aus Gründen der Vollständigkeit; tun dies eigentlich nicht. Nicht nur ist es nicht unterstützt, es ist verdammt hässlich.

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

Die kurze Antwort ist: Sie können nicht. Zumindest wird es nicht so funktionieren, wie du es willst. Ich denke, der beste Weg, das zu zeigen, besteht darin, durchzulaufen und zu sehen, was passiert, aber die allgemeine Idee ist, dass Rust keine Funktionsüberlastung unterstützt.

Für dieses Beispiel werden wir FnOnce umsetzen, weil FnFnMut die FnOnce erfordert erfordert. Wenn wir also alles sortiert bekommen, könnten wir es für die anderen Funktionsmerkmale tun.

Erstens ist dies instabil, daher müssen wir einige Feature-Flags

#![feature(unboxed_closures, fn_traits)] 

Dann lassen Sie uns das impl tun, um eine für die Aufnahme 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 
     } 
    } 
} 

Die Argumente für die Fn Familie von Merkmalen sind über ein Tupel geliefert, so ist das die (f64,) Syntax; es ist ein Tupel mit nur einem Element.

Das ist alles gut und gut, und wir können jetzt c(0.5) tun, obwohl es c verbrauchen wird, bis wir die anderen Merkmale implementieren.

Lassen Sie uns jetzt für Vec s dasselbe tun:

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

Jetzt haben wir ein Problem. Wenn wir versuchen c(v) oder sogar c(0.5) (die vorher funktionierte), erhalten wir eine Fehlermeldung über den Typ der Funktion, die nicht bekannt ist. Grundsätzlich unterstützt Rust keine Funktionsüberlastung. Aber wir können die Funktionen immer noch mit ufcs aufrufen, wobei c(0.5)FnOnce::call_once(c, (0.5,)) wird.


Nicht Ihr größeres Bild zu wissen, ich würde das einfach lösen will, indem wie so Clf zwei Funktionen geben:

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

Ihre Verwendung Beispiel

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] 

Dann wird würde ich tatsächlich möchte die zweite Funktion classify_slice machen und &[f64] nehmen, um ein bisschen allgemeiner zu sein, dann könntest du es immer noch mit vecs verwenden, indem du sie aufrufst : c.classify_slice(&v).

4

Dies ist in der Tat möglich, aber Sie brauchen eine neue Eigenschaft und eine Tonne Chaos.

Wenn Sie mit der Abstraktion beginnen

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

use VecOrScalar::*; 

Sie eine Art und Weise wollen die Art Transformationen verwenden

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

denn dann können Sie eine „versteckte“ Art nehmen T, wickeln Sie es in ein VecOrScalar und extrahiere den echten Typ T mit einem match.

Sie wollen auch

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

aber ohne HKT das ist ein bisschen schwierig. Stattdessen können Sie tun

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

wenn Sie für eine Branche, die in Panik geraten können erlauben.

wird das Merkmal

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

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

mit Implementierungen

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!"), 
     } 
    } 
} 

sein damit Ihre Klasse

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

zunächst die beiden b implementieren Ranches:

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

Dann wird es durch FnOnce für die Umsetzung Versand T: FromVecOrScalar<f64>

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

mit Typ

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

Die Versand ersten Boxen des privaten Typ, so dass Sie es mit dem Extrakt können enum und dann T::get s das Ergebnis, um es wieder zu verstecken.

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

Dann Erfolg:

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

Da der Compiler durch alle diese malarky sehen können, es kompiliert tatsächlich vollständig auf die im Grunde die gleiche Anordnung als direkter Aufruf an die calc_ Methoden entfernt.

Aber das ist nicht zu sagen, es ist schön zu schreiben. So zu überladen ist ein Schmerz, zerbrechlich und ganz sicher A Bad Idea ™. Tun Sie es nicht, obwohl es gut ist zu wissen, dass Sie es können.

+0

Vielen Dank Kumpel! – asdetrefle

Verwandte Themen