How to do Higher Order Composition in Rust? - rust

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:
How do I emulate Lisp (apply) or (curry) in Rust?
How to implement a multiple level currying function in Rust?
How to invoke a multi-argument function without creating a closure?
The main reason is that I want currying to get the ability to do point-free programming.

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

Related

return closures but cannot infer type

When learning rust closures,I try Like Java return "A Function"
fn equal_5<T>() -> T
where T: Fn(u32) -> bool {
let x:u32 = 5;
|z| z == x
}
But when i use it
let compare = equal_5();
println!("eq {}", compare(6));
Build error
11 | let compare = equal_5();
| ------- consider giving `compare` a type
12 | println!("eq {}", compare(6));
| ^^^^^^^^^^ cannot infer type
|
= note: type must be known at this point
See: https://doc.rust-lang.org/stable/rust-by-example/trait/impl_trait.html
Currently T simply describes a type, which in this case implements the Fn trait. In other words, T isn't a concrete type. In fact with closures, it's impossible to declare a concrete type because each closure has it's own unique type (even if two closures are exactly the same they have different types.)
To get around directly declaring the type of the closure (which is impossible) we can use the impl keyword. What the impl keyword does is convert our description of a type (trait bounds) into an invisible concrete type which fits those bounds.
So this works:
fn equal_5() -> impl Fn(u32) -> bool {
let x:u32 = 5;
move |z| z == x
}
let compare = equal_5();
println!("eq {}", compare(6));
One thing to note is we can also do this dynamically. Using boxes and the dyn trait. So this also works, however incurs the associated costs with dynamic resolution.
fn equal_5() -> Box<dyn Fn(u32) -> bool> {
let x:u32 = 5;
Box::new(move |z| z == x)
}
let compare = equal_5();
println!("eq {}", compare(6));
The compiler seems to be complaining that it's expecting a type parameter but finds a closure instead. It knows the type, and doesn't need a type parameter, but also the size of the closure object isn't fixed, so you can either use impl or a Box. The closure will also need to use move in order to move the data stored in x into the closure itself, or else it wont be accessible after equal_5() returns, and you'll get a compiler error that x doesn't live long enough.
fn equal_5() -> impl Fn(u32) -> bool {
let x:u32 = 5;
move |z| z == x
}
or
fn equal_5() -> Box<Fn(u32) -> bool> {
let x:u32 = 5;
Box::new(move |z| z == x)
}

How to deref tuple of references

use std::collections::BTreeMap;
fn main() {
let mut m = BTreeMap::new();
m.insert(1, 1);
let (a, b) = *m.iter().next().unwrap();
}
I am trying to get de-refed version of (key, value) pair returned from the btreemap. But using dereference operator directly upon the returned (& ,&) pair is not working.
Is there a way to do this so that the type of a, b are just u64, not &u64?
A & before a variable in a pattern (used by let) will dereference it.
let (&a, &b) = m.iter().next().unwrap();

How lifetime parameters and borrowing interacts in function signatures

Lets say I have a function with the following signature in Rust:
fn f<'a>(x: &'a i32) -> &'a i32;
Lets say I do the following then:
let x = 0;
let y = f(&x);
In that case, the Rust borrow checker considers y to borrow x. Why? What is the reason on a deeper level than "because you used the same lifetime parameter in the parameter type and the return type".
The function signature
fn f<'a>(x: &'a i32) -> &'a i32;
means that the value returned by f is a reference to what x parameter refers to, hence it can't outlive it. For example, this won't work:
// Compile error
let y = {
let x = 0;
f(&x)
// x is dropped here
};
// Here y still "exists", but x doesn't (y outlives x)
To your specific question:
Lets say I do the following then:
let x = 0;
let y = f(&x);
In that case, the Rust borrow checker considers y to borrow x. Why?
The answer is because the function signature of f tells it so. To give you an example, suppose that we change the signature to this:
fn f<'a, 'b>(x: &'a i32, z: &'b i32) -> &'a i32;
Then we call f like this:
let x = 0;
let z = 1;
let y = f(&x, &z);
In the code above, y borrows x, but not z. It's because the return value of f has 'a lifetime that is the same as x's lifetime.
Using the syntax from the Rustonomicon, we can elaborate the second snippet
let x: i32 = 0;
'a: { // definition of a lifetime (not real syntax)
let y: &'a i32 = f::<'a>(&'a x) // &'a x is also not real, though interestingly rustc recognizes it
}
The lifetime 'a is introduced by the compiler because you wrote a & and it needs to know how long that borrow will last (note that you cannot manually specify lifetimes for borrows). The type of the function also means that y has 'a in its type. The compiler needs to figure out where 'a starts and ends. The rules are that 'a must start right before you perform the borrow, that x cannot be moved during 'a (since it's borrowed), and that y cannot be used after 'a (because its type says so). If the compiler can pick start and end points for 'a so that the rules hold, then it compiles. If it can't, there's an error. There is no direct relationship between x and y; it goes through the lifetime 'a that they both interact with. E.g. you can't move x and then read *y because 'a must end before you move x and after you read *y, but such a time does not exist. TL;DR think about borrow checking as lifetime inference.

Modify and return closure

I'm trying to make a function to do the following:
accept a closure f of the form fn(T) -> T
return a closure of the form fn(T, bool) -> T, that conditionally performs f depending on the bool parameter.
I come from a haskell-ish background, and in Haskell this would be something like this:
conditionally :: (a -> a) -> a -> Bool -> a
conditionally f x True = f x
conditionally f x False = x
Translating that to something more rust-like:
conditionally :: ((t) -> t) -> ((t, Bool) -> t)
conditionally f = \(x, b) -> if b then (f x) else (x)
I tried the following in rust:
fn conditionally<T>(f: &'static (dyn Fn(T) -> T + 'static)) -> Box<dyn Fn(T, bool) -> T> {
Box::new(&|x, b| if b { f(x) } else { x } )
}
and was helpfully told to use the move keyword to ensure the closure takes ownership of f. However, the following still doesn't work:
fn conditional<T>(f: &'static (dyn Fn(T) -> T + 'static)) -> Box<dyn Fn(T, bool) -> T> {
Box::new(&move|x, b| if b { f(x) } else { x } )
}
I'm getting the following error (this also appeared prior to adding move):
error[E0515]: cannot return value referencing temporary value
--> src/main.rs:216:5
|
216 | Box::new(&move|x, b| if b { f(x) } else { x } )
| ^^^^^^^^^^-----------------------------------^^
| | |
| | temporary value created here
| returns a value referencing data owned by the current function
I'm thinking that the 'data owned by the current function' is either the closure I've defined, or the f that I've moved, but I can't get my head around how it all fits together.
As a smoke check, I made sure I'm able to box simpler closures that I define in the function body, and the following compiles:
fn conditional_increment() -> Box<dyn Fn(i32, bool) -> i32> {
Box::new(&|x, b| if b { x + 1 } else { x } )
}
What am I missing here? Is this possible in rust?
I'm also wondering if there's a more specific name for what I'm trying to do than simply higher-order functions, as I'm struggling to find resources on this kind of problem.
Update: I've realised that "currying in rust" would have been a good term to search for. While this isn't an example of currying, it would use the same language features, and would have led me to the answer given by vallentin.
You're attempting to return a boxed reference to a closure defined within the conditional function. Which you can't do, given that the closure only lives for the duration of the call. Instead you can return the closure itself, and in short just have to remove the &, i.e. turn &move |x, b| ... into move |x, b| ....
fn conditional<T>(f: &'static (dyn Fn(T) -> T + 'static)) -> Box<dyn Fn(T, bool) -> T> {
Box::new(move |x, b| if b { f(x) } else { x })
}
However, the more idiomatic way to write what you're attempting, which is to use generics and a type parameter for the closure. Check out:
Rust By Example - (Closures) As input parameters
Rust By Example - (Closures) As output parameters.
In short, you could rewrite it into this:
fn conditional<F, T>(f: F) -> Box<dyn Fn(T, bool) -> T>
where
F: Fn(T) -> T + 'static,
T: 'static,
{
Box::new(move |x, b| if b { f(x) } else { x })
}
You can actually also do without the box, and use the impl Trait syntax.
fn conditional<F, T>(f: F) -> impl Fn(T, bool) -> T
where
F: Fn(T) -> T,
{
move |x, b| if b { f(x) } else { x }
}
You can also use the impl Trait syntax for parameters, which the links shows, but personally I find it to noisy when dealing with closuring.
Using it the boils down to something as simple as this:
let f = conditional(move |x| x * 2);
println!("{}", f(2, false)); // Prints `2`
println!("{}", f(2, true)); // Prints `4`

Error message with unboxed closures

This small FizzBuzz program using unboxed closures gives a rather mysterious error message.
fn fizzbuzz<F: Fn(i64) -> bool>(n: i64, f: F, fs: &str, b: F, bs: &str) {
for i in range(1i64, n+1) {
match (f(i), b(i)) {
(true, true) => println!("{:3}: {}{}", i, fs, bs),
(true, _) => println!("{:3}: {}", i, fs),
(_, true) => println!("{:3}: {}", i, bs),
_ => (),
}
}
}
fn main() {
fizzbuzz(30,
|&: i: i64| { i % 3 == 0 }, "fizz",
|&: j: i64| { j % 5 == 0 }, "buzz");
}
Error message:
<anon>:15:14: 15:40 error: mismatched types: expected `closure[<anon>:14:14: 14:40]`, found `closure[<anon>:15:14: 15:40]` (expected closure, found a different closure)
<anon>:15 |&: j: i64| { j % 5 == 0 }, "buzz");
^~~~~~~~~~~~~~~~~~~~~~~~~~
error: aborting due to previous error
Could someone describe the error please? Thanks.
This piece of code demonstrates the essence of the problem:
fn show_both<S: Show>(x: S, y: S) {
println!("{} {}", x, y);
}
You can only call it with both arguments of the same type, i.e. this is allowed:
let x: i32 = 10;
let y: i32 = 20;
show_both(x, y);
But this is not:
let x: i32 = 10;
let y: f64 = 20.0;
show_both(x, y);
This is fairly natural: you specified that both of the parameters must be of the same type, even though this type can be arbitrary as long as it implements Show.
You have essentially the same thing in your code:
fn fizzbuzz<F: Fn(i64) -> bool>(n: i64, f: F, fs: &str, b: F, bs: &str)
You declared that both f and b must have the same type. However, for each unboxed closure the compiler generates a different type - this is quite natural as well, given that different closures can capture different variables.
You need to specify different type parameters in order to be able to pass different closures:
fn fizzbuzz<F1, F2>(n: i64, f: F1, fs: &str, b: F2, bs: &str)
where F1: Fn(i64) -> bool,
F2: Fn(i64) -> bool
Each unboxed closure definition creates a completely different type. This makes each of the closures defined in main a different type. Your fizzbuz function, on the other hand, requires each of the closures passed to it to be the same type, F. If you change fizzbuzz's signature to:
fn fizzbuzz<F: Fn(i64) -> bool, G: Fn(i64) -> bool>(n: i64, f: F, fs: &str, b: G, bs: &str)
your code will typecheck.
Basically, the <F: Fn(i64) -> bool> syntax doesn't create a wildcard for types that implement the trait parameter (Fn(i64) -> bool), but declares a single type which must satisfy the trait parameter and be the same type everywhere it is used. Unboxed closure definitions must be different types both because the may wrap different environments but also because they dispatch to different functions (that is, each has a different body). As a result, fizzbuzz needs two different type parameters to accommodate the two closure types.

Resources