Passing Expr /Function as input parameter to anther function

I have the below Julia code:

f(n) = 6n + 2
result = sum(f, 1:6) # or sum(f(n) for n=1:6)

And replicated it in the below Rust code:

let f = |n| 6 * n + 2;
let result = (1..=6).map(|n| f(n)).fold(0, |acc, ele| acc + ele);
// or let result: i32 = (1..=6).map(|n| f(n)).sum();

Now I want to make a general function for this, so I wrote the below, but looks like passing the input parameters is wrong:

fn main() {
    println!("result =  {:?}", result(1..=6, |n: T| 6 * n + 2););
}

fn result(r:Range<T>, f: function ()) {
    r.map(|n| f(n)).fold(0, |acc, ele| acc + ele);
}

The syntax for a function that takes no parameters and returns nothing is f: fn().

To be more general and support closures that capture values, you would use one of the Fn, FnMut, or FnOnce traits. You want to use the most flexible one that still works how you will be calling it, which would be FnMut here. So that parameter would now be f: impl FnMut().

You're actually trying to map, where the function takes a parameter and returns a value, so that would be more like f: impl FnMut(T) -> T.

Your result function needs to declare T as a type parameter, and add constraints for all the things you're trying to do with it. Apart from the map call, you're also creating a 0 and adding values. So the most direct translation I can make of your code is:

extern crate num;
use num::Zero;
use std::ops::{Add, Range};

fn result<T>(r: Range<T>, mut f: impl FnMut(T) -> T) -> T
where
    T: Zero + Add<Output = T>,
    Range<T>: Iterator<Item = T>,
{
    r.map(|n| f(n)).fold(T::zero(), |acc, ele| acc + ele)
}

You can do this without the num crate if you use the Sum trait instead of the manual fold. The call to map can also be simplified in this case, so I'd write it like this:

use std::iter::Sum;
use std::ops::Range;

fn result<T: Sum>(r: Range<T>, f: impl FnMut(T) -> T) -> T
where
    Range<T>: Iterator<Item = T>,
{
    r.map(f).sum()
}
2 Likes

Thanks,

  1. As I used 1..=6 I got from the compiler that I've to use RangeInclusive instead of Range
  2. I was in need to remove the T from result(1..=6, |n: T| 6 * n + 2) as I got compile error for it as ^ not found in this scope

I don't see a need to specify Range<T> (or RangeInclusive<T>) here - just specify the r param as impl Iterator<Item = T>.

1 Like

Thanks @cuviper and @vitalyd

If I used float in (1..=6, |n| 6 * n + 2)then everything is destroyed, I tried using (1..=6, |n| 6.5 * n as f32 + 2.0) but did not work!

You'd need something like the following if you want to change the type you iterate over vs what you sum and return:

fn result<T, R: Sum>(r: impl Iterator<Item = T>, f: impl FnMut(T) -> R) -> R {
    r.map(f).sum()
}
// or (same thing, just different syntax)
fn result<I: Iterator, R: Sum>(r: I, f: impl FnMut(I::Item) -> R) -> R {
    r.map(f).sum()
}
1 Like

Thanks @vitalyd I liked the second approach:

use std::iter::Sum;

fn main() {
    println!("result =  {:?}", result(1..=6, |n| 6 * n + 2));
}

fn result<I: Iterator, R: Sum>(r: I, f: impl FnMut(I::Item) -> R) -> R {
    r.map(f).sum()
}

I can understand the main function in returning a sum as R, but did not get why f also return R also! appreciate if you explain little more, thanks

You're basically creating a processing pipeline here, starting with items of type T and ending in R. f is your mapping function - it translates (maps) each T to R, and then you run the sum over all the resulting Rs. Does that help/make sense? Or did I miss the thrust of your question?

By default, Sum takes the same type as an input, but it can be parameterized too. So you could be a little more generic like:

fn result<I: Iterator, T, R: Sum<T>>(r: I, f: impl FnMut(I::Item) -> T) -> R {
    r.map(f).sum()
}

So we have I::Item mapped to T and summed as R.

The primitive types in the standard library only implement Sum<Self> and Sum<&Self>, but other types can do more. For instance, num-bigint's types can Sum any integer input.

2 Likes

Turtles all the way down ... :smile:

3 Likes

All of these are valid Moss:

result = (1..6).sum(|n| 6*n+2)

f = |n| 6*n+2
result = (1..6).map(f).reduce(0,|x,y| x+y)

result = (1..).map(|n| 6*n+2).take(6).sum()

result = (6*n+2 for n in 1..6).sum()

result = (6*n+2 for n in 1..).take(6).sum()

As a general function:

result = |r,f,zero=0| r.map(f).reduce(zero,|x,y| x+y)

Moss is already oxidated. And soon it might be moisted too.

Then nothing will prevent me from feeding the dragon, disgracing everybody down to earth.

But feeding dragons before you need to is already flawed. It takes an amount of time before they come out of their dark holes.