7

I have this function that works:

fn compose<A, B, C>(f: impl Fn(A) -> B, g: impl Fn(B) -> C) -> impl Fn(A) -> C {
    move |x: A| g(f(x))
}

I'd like to use currying in this situation, so that I can do:

/* THIS */
compose(f)(g)(x)

/* Instead of this */
compose(f, g)(x)

Is it even reasonable to think about making a macro such that this is possible?

compose(f)(g)(h)···(x)

I've managed to get something that's kinda similar to what I want:

fn compose<A, B, C, F: 'static, G: 'static>(f: F) -> impl Fn(G) -> Box<dyn Fn(A) -> C>
where
    F: Fn(A) -> B + Copy,
    G: Fn(B) -> C + Copy,
{
    move |g: G| Box::new(move |x: A| g(f(x)))
}

let inc = |x| x + 1;
let mult2 = |x| x * 2;

let inc_compose = compose(inc);

println!("{}", inc_compose(mult2)(3)); // 8

Now there's a new problem: when creating a higher-order function based on my compose function, I need to give the function a type dependent on another function:

let inc   = |x: usize| x + 1;
let mult2 = |x: usize| x * 2;
let mult3 = |x: usize| x * 3;

let inc_compose = compose(inc);

println!("{}", inc_compose(mult2)(3)); // 8

println!("{}", inc_compose(mult3)(3)); // ERROR: [rustc E0308] [E] mismatched types

Is there a way to avoid this error?

None of these similar posts answer my question:

The main reason is that I want currying to get the ability to do point-free programming.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • 1
    https://docs.rs/partial_application/0.2.1/partial_application/ there is a crate for partial application macro, however there really isn't any language support for this. – Ivan C Apr 04 '21 at 18:57
  • You may be interested in [`pipeline`](https://github.com/johannhof/pipeline.rs), a popular crate that provides a `pipe!()` macro. – Ibraheem Ahmed Apr 04 '21 at 18:58
  • The question is not about whether it's possible to do composition with more than 2 parameters, that is fairly trivial, the question is whether it's possible to do it with currying. – Carlo Augusto Bagnoli Gomez Apr 04 '21 at 19:03
  • I'll also mention this comment thread on currying using return-position impl Trait: https://internals.rust-lang.org/t/currying-in-rust/10326/2. However, nested impl Trait isn't available, so something like this doesn't work (yet): https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=4e072abecddec65b3196420887d1a347 – ddulaney Apr 04 '21 at 19:03
  • @ddulaney, So, does that mean it's impossible to do it? I mean, there's other similar implementations (not on the same problem but similar), that use Box to solve the dyn Fn() problem. – Carlo Augusto Bagnoli Gomez Apr 04 '21 at 19:07
  • 1
    It could be possible when Fn traits become stable, by writing a proc-macro that would transform a function into a struct inplementing Fn, while also generating a curry() method for it, so you could then use it as either foo(a,b,c) or foo.curry()(a)(b)(c). – Ivan C Apr 04 '21 at 19:15
  • Are the Rust devs actually working on this? 'cause that would be amazing! – Carlo Augusto Bagnoli Gomez Apr 04 '21 at 19:17
  • It looks like your question might be answered by the answers of [How do I emulate Lisp (apply) or (curry) in Rust?](https://stackoverflow.com/q/9271924/155423); [How to implement a multiple level currying function in Rust?](https://stackoverflow.com/q/64005006/155423); [How to invoke a multi-argument function without creating a closure?](https://stackoverflow.com/q/53806635/155423). If not, please **[edit]** your question to explain the differences. Otherwise, we can mark this question as already answered. – Shepmaster Apr 05 '21 at 15:03
  • @Shepmaster, No, none of them are answering the question, and the main reason is ̣that I want currying ─ as the title suggests ─ to get the ability to do point-free programming. – Carlo Augusto Bagnoli Gomez Apr 05 '21 at 19:12
  • Since my implementation of the `compose` function has the function dependency issue, it doesn't work to make higher-order functions. – Carlo Augusto Bagnoli Gomez Apr 05 '21 at 19:15
  • @CarloAugustoBagnoliGomez can you suggest how I could improve my comment, specifically this part: *If not, please edit your question to explain the differences* so that it's less confusing for people? – Shepmaster Apr 05 '21 at 19:17
  • When it comes down to stack overflow, I'm a big noob (you can tell bc of my reputation), so I'm still getting used to the "how to make a question" part. Either way, i guess it'd be better if you linked to a meta-post or something similar that explained how to differenciate your questions from others. (I would honestly have never guessed that linking other posts to explain what's different is the standard way of doing it). – Carlo Augusto Bagnoli Gomez Apr 05 '21 at 19:24

1 Answers1

5

In Rust, currying is hard. The strict type system, lifetimes, and nested impl traits not existing all conspire to ruin your day when you try. However, it is possible to create a macro to curry functions, and I did so for code golf at one point. The ungolfed version is here:

macro_rules! curry{
    (
        $f:ident
        $(
            ($args:ident : $types:ty)
        )*
    ) => {
        $(move |$args: $types|)*
        $f(
            $($args),*
        )
    }
}

This matches on expressions of the form name(arg1, type1)(arg2, type2)...(argN, typeN) and returns a chain of move closures leading to the function call. Most of the time, however, you can just use _ for the type and let inference figure it out. Using it with compose is simple:

let inc = |x| x as u32 + 1;
let double = |x| x * 2;
let curried = curry!(compose(f: _)(g: _));
assert_eq!(curried(inc)(double)(7), 16);

The code generated by the macro, in this case, looks like this:

move |f: _| move |g: _| compose(f, g)

It simply takes the supplied name and type and pastes it into the closure heads, so you can use it to force arguments for generic functions into concrete types. Playground

Aiden4
  • 2,504
  • 1
  • 7
  • 24
  • I'm tempted to mark this as the answer, just because of the simplicity and the extensibilty of it, although it still has the same "type hijacking/dependence" issue. If nothing better comes out, I'll mark it. Thank you, you've got my upvote! – Carlo Augusto Bagnoli Gomez Apr 05 '21 at 18:56
  • @CarloAugustoBagnoliGomez I don't know what you mean by "type hijacking/dependence" issue, with this macro you should be able to curry any safe function you can call. If you elaborate further, I may be able to help better. – Aiden4 Apr 05 '21 at 19:32
  • So, when creating a higher-order function from the `compose` function, in the sense that, we could then pass any other function to act after the first, it only works when only one function is passed to it. That might sound verbose, so here's a [playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=781aa2e3dd978142331706d63008dcc7) to explain it better. – Carlo Augusto Bagnoli Gomez Apr 05 '21 at 19:45
  • I call it hijacking because Rust's compiler gives the argument of the higher order function a type based on the first use of the function, hence it cannot be generalised. – Carlo Augusto Bagnoli Gomez Apr 05 '21 at 19:47
  • The only way to make a higher order function then would be to generalize this. – Carlo Augusto Bagnoli Gomez Apr 05 '21 at 19:51
  • @CarloAugustoBagnoliGomez You can do that- you just need to specify a type, rather than just letting the compiler figure it out. Function pointers probably work the best in your situation although a borrowed trait object would probably work as well. [Playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=9ddb8ca663cb077ee17861069da385e3) – Aiden4 Apr 06 '21 at 00:23
  • Ok, but, then we'd need to define curried everytime that we wanted to create a specific function, right? Can we use a generic or something? – Carlo Augusto Bagnoli Gomez Apr 06 '21 at 00:41
  • Eureka! I finally figured it out. I'll mark your answer as correct. – Carlo Augusto Bagnoli Gomez Apr 06 '21 at 00:58
  • Here's the [solution](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=ebcd2e26ee51d640789daa985f99a894), please edit your answer to provide a link to it or make it better and provide a link for others. – Carlo Augusto Bagnoli Gomez Apr 06 '21 at 01:03
  • @CarloAugustoBagnoliGomez By doing that you are losing some generality in your `compose` function. If you want to use trait objects, you should do so in the invocation of `curry!` rather than in the function itself. As for function pointers, they `impl` the function traits and any closure that doesn't capture an environment, as well as functions, freely coerce into them. Function pointers also have less overhead and are easier to work with then trait objects. Also, you only need to invoke `curry` once per function- the closure returned should be `Copy`-able. – Aiden4 Apr 06 '21 at 01:23