One way to implement currying is to use impl Trait
to return a closure with one less function parameter.
fn curried<Ft>(func: F, arg: u32) -> impl Fn(&str, bool) -> String
where
F: Fn(u32, &str, bool) -> String
{
move |b: &str, c: bool| { func(arg, b, c) }
}
This could be made generic over the argument type using normal generics:
fn curried<F, A, B, C, Ret>(func: F, arg: A) -> impl Fn(B, C) -> Ret
where
F: Fn(A, B, C) -> Ret
{
move |b: B, c: C| { func(arg, b, c) }
}
From here, you could probably abstract this out into a trait which gets implemented for anything callable with 0 to N arguments (Rust doesn't let you have a variable number of generic type parameters so we kinda fudge it with macros).
Unfortunately it's not possible for a trait method to return impl Trait, so you also incur some additional runtime overhead with dynamic dispatch and heap allocations (e.g. by returning Box<dyn Fn(B, C) -> Ret>
).
Forgive me for being pedantic, but I'd like to address a couple comments you made in your initial post. They may help clear up some frustrations you've been having.
The position of the parentheses is just syntax. When you parse foo x y
and foo(x, y)
the two expressions would look identical to the compiler, so I don't think it's really a problem.
This is also not correct, Rust traits are a lot closer to Haskell type classes than Java interfaces. It's just fine for traits to have function members that don't reference self
in any way.
I've seen someone implement a brainfuck interpreter purely with traits and the type system, there's also a type-level counting system (typenum) which can be used to do math with the typesystem (e.g. say you are doing matrix math and want the dimensions to be encoded directly in the type).
I haven't really seen macros being used as a workaround for lack of functional programming tools. Rust typically uses macro_rules
macros for the same reason that you would in Lisp, to transform a tree of source code at compile time.
If the transformations are more than a simple reshuffling of tokens, procedural macros give you access to a turing complete language (Rust) to let you do arbitrary computations at compile time to generate the code you want.