Passing a R^n -> R function as an argument


#1

I would like to pass a Vector -> f64 function to some function go, but I run into troubles.

fn go<D, S, F>(f: F)
    where D: Dim, 
          S: Storage<f64, D>, 
          F: Fn(&Vector<f64, D, S>) -> f64   {
    let n = 4;
    let m = DMatrix::<f64>::new_random(n,n);

    println!("{:8.4}", f(&m.column(0)));
}

fn norm_square<D: Dim, S: Storage<f64, D>>(x: &Vector<f64, D, S>) -> f64 {
    x.iter().fold(0., |b, xi| b + xi.powi(2))
}

go(norm_square);

Which raises this error:

error[E0283]: type annotations required: cannot resolve `_: nalgebra::base::dimension::Dim`
|     go(norm_square);
|     ^^
note: required by `go`
| /     fn go<D, S, F>(f: F)
| |         where D: Dim,
| |               S: Storage<f64, D>,
| |               F: Fn(&Vector<f64, D, S>) -> f64   {
...
| |         println!("{:8.4}", f(&m.column(0)));
| |     }
| |_____^

error[E0308]: mismatched types
|         println!("{:8.4}", f(&m.column(0)));
|                              ^^^^^^^^^^^^ expected type parameter, found struct `nalgebra::base::dimension::Dynamic`
|
   = note: expected type `&nalgebra::base::matrix::Matrix<f64, D, nalgebra::base::dimension::U1, S>`
              found type `&nalgebra::base::matrix::Matrix<f64, nalgebra::base::dimension::Dynamic, nalgebra::base::dimension::U1, nalgebra::base::matrix_slice::SliceStorage<'_, f64, nalgebra::base::dimension::Dynamic, nalgebra::base::dimension::U1, nalgebra::base::dimension::U1, nalgebra::base::dimension::Dynamic>>`

I tried specifying parameters for go but not in a successful way. I’m not even quite sure it could actually work, because of the dynamic dimension. My vectors will all be of the same dimension so I guess it might be possible specify that n as a type parameter.

I noted that the following works

fn apply<D, S, F>(x: &Vector<f64, D, S>, f: F) -> f64 
    where D: Dim, 
          S: Storage<f64, D>, 
          F: Fn(&Vector<f64, D, S>) -> f64    
{
    f(x)
}

let n = 4;
let m = DMatrix::<f64>::new_random(n,n);
println!("{}", apply(&m.column(0), norm_square));

I suppose it is because it is able to unify the D for its parameters since they are both specified by the type?


#2

Hi!

Yes, the first thing you tried cannot work because it cannot unify D with Dynamic as you guessed. That’s because the following:

fn go<D, S, F>(f: F)
    where D: Dim, 
          S: Storage<f64, D>, 
          F: Fn(&Vector<f64, D, S>) -> f64   {

means that f is defined only for the one type bound to D when calling go (and from the point of view of the content of go, this type is opaque). It does not mean that f can accept any D. Ideally, you would like something like (following a syntax inspired from higher kinded lifetimes):

fn go<F>(f: F)
    where 
          F: for<D: Dim, S: Storage<f64, D>> Fn(&Vector<f64, D, S>) -> f64   {

But that is not valid rust. Higher kinded type parameters are not possible in rust yet.
Note however that there might be a workaround for what you are trying to do. You could define a trait VectorFn:

trait VectorFn {
  fn apply<D: Dim, S: Storage<f64, D>>(&Vector<f64, D, S>) -> f64;
}

Then you can write go as: fn go(f: impl VectorFn). This won’t allow you to pass a closure though since you won’t be able to impl VectorFn for a closure (again because of lack of higher kinded type parameters) . Only a struct that implements VectorFn will work.


#3

Thanks. Is that what you were suggesting?

trait VectorFn {
    fn apply<D: Dim, S: Storage<f64, D>>(&self, x: &V<f64, D, S>) -> f64;
}

struct NormSquare;

impl VectorFn for NormSquare {
    fn apply<D: Dim, S: Storage<f64, D>>(&self, x: &V<f64, D, S>) -> f64 {
        norm_square(x)
    }
}

fn go<F: VectorFn>(f: F) {
    let n = 4;
    let m = DM::<f64>::new_random(n,n);

    println!("{:8.4}", f.apply(&m.column(0)));
}

go(NormSquare);

I imagine some macro could make it look nicer.


#4

Yes, exactly! And yeah, I guess a macro could help here.