fix_fn/lib.rs
1//! This library enables the creation of recursive closures by providing a
2//! single macro [`fix_fn`]. The functionality is similar to the
3//! [Y combinator](https://en.wikipedia.org/wiki/Fixed-point_combinator#Fixed-point_combinators_in_lambda_calculus).
4//! Recursive closures can have arbitrary amounts of parameters and can capture
5//! variables.
6//!
7//! ```
8//! use fix_fn::fix_fn;
9//!
10//! let fib = fix_fn!(|fib, i: u32| -> u32 {
11//! if i <= 1 {
12//! i
13//! } else {
14//! // fib will call the closure recursively
15//! fib(i - 1) + fib(i - 2)
16//! }
17//! });
18//!
19//! assert_eq!(fib(7), 13);
20//! ```
21//!
22//! The generated code is not completely abstraction free as it uses one dyn trait
23//! (without any boxing) to overcome rust's recursive type limitations.
24//! In most cases, however, the optimizer should be able to eliminate any dynamic dispatch.
25//!
26//! Unfortunately, mutable recursive closures are not supported.
27
28
29/// Takes a closure definition where the first parameter will be a [`Fn`] to the closure itself.
30/// Returns a recursive closure with the same signature, except the first parameter will be
31/// eliminated.
32///
33/// The passed closure needs to have at least one parameter. This
34/// first parameter can be used to call the closure itself, achieving recursion.
35/// It must not be annotated with a type.
36///
37/// Additional parameters will be parameters of the resulting closure.
38/// All additional parameters must be annotated with types.
39///
40/// The closure definition needs to have a result-type annotation.
41///
42/// `move` can be used and has the [usual semantic](https://doc.rust-lang.org/1.18.0/book/first-edition/closures.html#move-closures).
43///
44/// # Example
45///
46/// ```
47/// use fix_fn::fix_fn;
48///
49/// let fib = fix_fn!(|fib, i: u32| -> u32 {
50/// if i <= 1 {
51/// i
52/// } else {
53/// // fib will call the closure recursively
54/// fib(i - 1) + fib(i - 2)
55/// }
56/// });
57///
58/// // resulting lambda only has the `i: u32` parameter
59/// assert_eq!(fib(7), 13);
60/// ```
61#[macro_export]
62macro_rules! fix_fn {
63 (
64 $($mov:ident)? |$self_arg:ident $(, $arg_name:ident : $arg_type:ty)* $(,)? |
65 -> $ret_type:ty
66 $body:block
67 ) => {{
68 trait HideFn {
69 fn call(&self, $($arg_name : $arg_type ,)*) -> $ret_type;
70 }
71
72 struct HideFnImpl<F: Fn(&dyn HideFn, $($arg_type ,)*) -> $ret_type>(F);
73
74 impl<F: Fn(&dyn HideFn, $($arg_type ,)*) -> $ret_type> HideFn for HideFnImpl<F> {
75 #[inline]
76 fn call(&self, $($arg_name : $arg_type ,)*) -> $ret_type {
77 self.0(self, $($arg_name ,)*)
78 }
79 }
80
81 let inner = HideFnImpl(
82 #[inline]
83 $($mov)?
84 |$self_arg, $($arg_name : $arg_type ,)*| -> $ret_type {
85 let $self_arg = |$($arg_name : $arg_type ),*| $self_arg.call($($arg_name ,)*);
86 {
87 $body
88 }
89 }
90 );
91
92
93 #[inline]
94 move |$($arg_name : $arg_type),*| -> $ret_type {
95 inner.call($($arg_name),*)
96 }
97 }};
98 (
99 $($mov:ident)? |$($arg_name:ident $(: $arg_type:ty)?),* $(,)?|
100 $body:expr
101 ) => {
102 compile_error!("Closure passed to fix_fn needs return type!");
103 };
104 (
105 $($mov:ident)? |$self_arg:ident : $self_type:ty $(, $arg_name:ident $(: $arg_type:ty)?)* $(,)? |
106 -> $ret_type:ty
107 $body:block
108 ) => {
109 compile_error!(concat!("First parameter ", stringify!($self_arg), " may not have type annotation!"));
110 };
111 (
112 $($mov:ident)? |$self_arg:ident $(, $arg_name:ident $(: $arg_type:ty)?)* $(,)? |
113 -> $ret_type:ty
114 $body:block
115 ) => {
116 compile_error!("All parameters except first need to have an explicit type annotation!");
117 };
118}
119
120#[cfg(test)]
121mod tests {
122 use std::cell::RefCell;
123
124 #[test]
125 fn test_zero_parameter() {
126 fn create() -> impl Fn() -> i32 {
127 let cell = RefCell::new(0);
128
129 fix_fn!(move |rec| -> i32 {
130 if *cell.borrow() == 10 {
131 10
132 } else {
133 *cell.borrow_mut() += 1;
134 rec()
135 }
136 })
137 }
138
139 let f = create();
140
141 assert_eq!(f(), 10);
142 }
143
144 #[test]
145 fn test_one_parameter() {
146 let fib = fix_fn!(|fib, i: u32| -> u32 {
147 if i <= 1 {
148 i
149 } else {
150 fib(i - 1) + fib(i - 2)
151 }
152 });
153
154 assert_eq!(fib(7), 13);
155 }
156
157 #[test]
158 fn test_two_parameter() {
159 let pow = fix_fn!(|pow, x: u32, y: u32| -> u32 {
160 if y == 1 {
161 x
162 } else if x % 2 == 0 {
163 pow(x * x, x / 2)
164 } else {
165 x * pow(x, y - 1)
166 }
167 });
168
169 assert_eq!(pow(3, 9), 19683);
170 }
171}