I need the user of my library crate be able to specify which activation function a struct Node
will use, but also provide a default. It is also needed that the user-specified function be passed through a building factory. My current solution is abbreviated here:
lib.rs:
pub struct Configuration {
activation_function: Box<dyn Fn(f64) -> f64>,
}
impl Configuration {
pub fn new_default() -> Configuration {
Configuration {
activation_function: Box::new(|x| 1.0 / (1.0 f64::exp(x))),
}
}
pub fn activation_function(mut self, val: Box<dyn Fn(f64) -> f64>) -> Configuration {
self.activation_function = val;
self
}
}
pub struct Node {
pub net: f64,
pub activation: f64,
}
impl Node {
pub fn activate(&mut self, config: &Configuration) {
self.activation = (*config.activation_function)(self.net);
}
}
main.rs:
use sandbox::{Configuration, Node};
fn main() {
let config = Configuration::new_default()
.activation_function(Box::new(|x| f64::max(0.2 * x, x)));
let mut node = Node { net: 1.23, activation: 0.0};
node.activate(&config);
println!("{}", node.activation);
}
My problem with this aproach is that the activation function cannot be inlined. As it may be the one of the most frequently called function in the project, speed is of the essence. In profiling this method was significantly slower than using an inline-able function.
I wonder whether the Configuration
and Node
structs can be made generic over a Fn(f64) -> f64
, or alternatively a fn(f64) -> f64
, so that the compiler is able to inline this function whenever it is needed within Node
.
I tried using a generic const, but apparently those can only be of primitive type. I tried also using the following struct definition, but couldn't get the default_new
function to work.
pub struct Configuration<F: Fn(f64) -> f64> {
activation_function: F,
}
Is it even possible in Rust to have a generic parameter of type Fn
or fn
so that the function is inlined?
CodePudding user response:
pub struct Configuration<F: Fn(f64) -> f64> {
activation_function: F,
}
This is on the right track. The gimmick is that new_default
needs to know what type its function is now. Since you know the function in advance, fn(f64) -> f64
(the type of function pointers which don't have closures) is a great candidate.
impl<F: Fn(f64) -> f64> Configuration<F> {
pub fn activation_function<G: Fn(f64) -> f64>(self, val: G) -> Configuration<G> {
Configuration { activation_function: val }
}
}
impl Configuration<fn(f64) -> f64> {
pub fn new_default() -> Configuration<fn(f64) -> f64> {
Configuration {
activation_function: |x| 1.0 / (1.0 f64::exp(x)),
}
}
}
Note that we can only convert the closure to fn(f64) -> f64
since it doesn't capture anything. Otherwise, we'd need to do something more complicated.
Also note that, in order for activation_function
(which appears to be an implementation of the builder pattern) to be useful, we now need it to be able to return a Configuration<G>
where G
is potentially different than F
. Otherwise, you'd always be limited to fn(f64) -> f64
and wouldn't be able to use closures or other function-like objects.
CodePudding user response:
Return Configuration<impl Fn(f64) -> f64>
from your new_default()
:
pub struct Configuration<F> {
activation_function: F,
}
impl Configuration<fn(f64) -> f64 /* this is just a marker type */> {
pub fn new_default() -> Configuration<impl Fn(f64) -> f64> {
Configuration {
activation_function: |x| 1.0 / (1.0 f64::exp(x)),
}
}
}
impl<F> Configuration<F> {
pub fn activation_function<G: Fn(f64) -> f64>(self, val: G) -> Configuration<G> {
Configuration { activation_function: val }
}
}