Implement function composition via operator overload

Hi,

I'd like to implement the composition of lambdas (closures) so that (f >> g)(x) would means f(g(x)) and I could also use let fg = f >> g, and of course compose more than two functions, f>>g>>h>> etc.

In Haskell, it's dead easy : fg = (f . g)

So in Rust I want to implement Shr for closures. So I started by trying to implement Shr for a struct with a closure, but it fails : https://gist.github.com/yann-ledu/81e7fc522fad4560ccbc#file-compose-rs.

  1. Can someone tell me how to implement that through struct ?
  2. Is there a way to avoid a struct and directly deal with the closure generic type ?
  3. Is there a way to allow function composition via such overloading, and thus generalize the approach to a chain of stuff with the right chaining of domain and co-domains ?

Thanks,

Yann

2 Likes

I'm having a similar problem. I'm a complete newbie to Rust, but have some experience with Haskell, and a lot of experience with C#, Typescript and C++.

I hacked something together, it seems to work, but I hardly understand any of it, I merely am a slave of the Rust compiler that is telling me what to do :wink:

I didn't get it working without boxing though... I guess I would have to add a type parameter to the Fun struct, but so far, I failed doing so.

#![allow(unused)]
#![feature(unboxed_closures, fn_traits)]

use std::ops::Shr;
use std::ops::Fn;

pub struct Fun<T, R> {
    f: Box<dyn Fn(T) -> R>,
}

impl<Args, R> FnOnce<(Args,)> for Fun<Args, R> {
    type Output = R;
    extern "rust-call" fn call_once(self, args: (Args,)) -> R {
        self.f.call_once(args)
    }
}

impl<Args, R> FnMut<(Args,)> for Fun<Args, R> {
    extern "rust-call" fn call_mut(&mut self, args: (Args,)) -> R {
        self.f.call_mut(args)
    }
}

impl<Args, R> Fn<(Args,)> for Fun<Args, R> {
    extern "rust-call" fn call(&self, args: (Args,)) -> R {
        self.f.call(args)
    }
}


pub fn fun<T, R>(f: impl Fn(T) -> R + 'static) -> Fun<T, R> {
    Fun { f: Box::new(f) }
}

impl<T1: 'static, T2: 'static, R: 'static> Shr<Fun<T2, R>> for Fun<T1, T2> {
    type Output = Fun<T1, R>;

    fn shr(self, rhs: Fun<T2, R>) -> Self::Output {
        let f1 = self.f;
        let f2 = rhs.f;
        let f = move |x: T1| f2(f1(x));
        Fun { f: Box::new(f) }
    }
}


fn add(increment: i32) -> Fun<i32, i32> {
    fun(move |x| x + increment)
}

fn mul(factor: i32) -> Fun<i32, i32> {
    fun(move |x| x * factor)
}


fn main() {
    let add_mul = add(1) >> mul(5);
    let mut base = 10;
    let result = add_mul(base);
    println!("{}", result);
    base = 100;
    let result = add_mul(base);
    println!("{}", result);

}

Got something working without boxes, but it's not very user friendly

Don't revive old threads please.

2 Likes