Currying in Rust

Hi, I am building a collection of functional programming practices in Rust and I found currying syntax in Rust is particularly difficult to deal with at the moment.

There are ways to improve the syntax but without incorporating any macros, it would look like

fn add(x: i32) -> impl Fn(i32)-> i32 {
    return move |y| x + y;
}

I thought it would be nice to write a curried function like below

fn add(x: i32) -> fn(y: 32) -> i32 { // or omit the return type
  // Automatically resolves the scope of both x and y.
  // Also automatically move the ownership of x to `fn(y: i32)`
  return x + y;
}

inline function style

let add = | x: i32 | -> | y: i32 | { x + y }

Then you can use the curried methods like

let add_1 = add(1);
add_1(2); // 3

This is a different proposal to the previous curried function proposal by iopq (Auto-currying in Rust), which primarily proposes automatic currying instead of manual currying.

Please let me know your thought.

Note that inline can already just be this, which is surprisingly terse:

let foo = |x| move |y| x + y;
let bar = foo(3);
dbg!(bar(10));

One thing that currying for Rust will need to deal with is the FnOnce/Fn/FnMut distinction. The example given just uses Copy types, but what if I want to change it to String?

2 Likes

Note that the inline curry almost works: let add = |x| move |y| x + y; is pefectly correct. Iff x is obviously used by move, then the move annotation for the inner closure isn’t required.

That said, a proposal to add first class currying syntax would have to explain how currying benefits Rust. I personally find a more explicit curried style, with fn add(x, y) and when I want to “curry it” I do |y| add(my_x, y), to be unintrusive and very obvious as to what’s going on.

Return position impl Trait also makes the stable syntax honestly very close to the ideal, while exposing the costs quite clearly. Honestly, the only sugar I’d add is inlining the parameter names to the header to avoid separating them from their types like what a heavily curried function would look like on stable:

fn foo(a: A) -> impl FnOnce(B) -> impl FnOnce(C) -> impl FnOnce(D) -> impl FnOnce(E) -> F {
    |b| |c| |d| |e| {
        // "actual" fn body
        unimplemented!()
    }
}
1 Like

Currying is only truly useful in the presence of a standard library that is built for it, i.e., where there is a consistent choice of parameter order made in concert with “we have currying”. Unfortunately, most of Rust’s combinators are fn(Self, F) -> F(Self), rather than being swapped like in Haskell. Permuting parameters (I think Haskell has swap?) isn’t really an option, since that either incurs an extra call (if cross-translation unit) or duplicates an entire function (if LLVM inlines you), which makes binary size quite sad.

Suppose you can partially apply arbitrary parameters. If you could write

|..| f(a, _, b, c);  // You need some sigil like |..| to 
                     // indicate partial application; otherwise your
                     // grammar is sad.
// becomes
|$0| f(a, $0, b, c);

then you’re probably in better shape. However, you are not going to be terrifically happy here, since you’ve won approximately nothing in terms of readability, and probably lost a lot, since you’ve introduced a place where people will be tempted to code-golf.

6 Likes