1

In writing my own generic sigma summation function to get practice with Rust, I've hit an issue with providing a seed value of zero as the accumulator.

fn sigma<I, T, F>(iter: I, func: F) -> T
    where I: Iterator<Item=T>,
          T: Add<Output=T>,
          F: Fn(T) -> T
{
    iter.fold(0, |acc, x| acc + func(x))
}

Playground Link

I understand that it's wrong, as 0 is a concrete type, and thus not T. Other answers (like this and this one) rely on constructs like Int::zero, which are deprecated as of 1.11.

There are other ways of doing this, but I am particularly interested in how it should be done, as testing for one, zero, or negativity is a common operation in numeric procedures that I'll hit again soon enough. Plus, now I'm curious.

My Rust version is 1.16.

Community
  • 1
  • 1
AManOfScience
  • 1,183
  • 9
  • 9

1 Answers1

4

Asking for how your function should be done can become opinion-based: there are multiple good ways of achieving this. Nevertheless, it is answerable if we narrow down to these two approaches, which I would consider typical and idiomatic:


Zero and One were deprecated (never even stabilized, in fact!) from the standard library mostly because there is a more generalized means of making products and sums from iterators: the Sum and Product traits are relied upon by iterators when calling methods sum() and product(), and can even yield a result of a type other than the items'.

use std::iter::{Iterator, Sum};

fn sigma<I, T, F>(iter: I, func: F) -> T
    where I: Iterator<Item = T>,
          T: Sum,
          F: Fn(T) -> T
{

    iter.map(func).sum::<T>()
}

Playground. I took the liberty of moving the element transformation to a map in the iterator definition chain, thus enabling the use of sum() as a terminal operation.


The trait Zero and One are still available in crate num (or num-traits), so you can use that instead.

extern crate num;

use std::ops::Add;
use std::iter::Iterator;
use num::Zero;

fn sigma<I, T, F>(iter: I, func: F) -> T
    where I: Iterator<Item = T>,
          T: Zero + Add<Output = T>,
          F: Fn(T) -> T
{
    iter.map(func).fold(T::zero(), |a, b| a + b)
}

Playground

E_net4
  • 27,810
  • 13
  • 101
  • 139
  • 3
    A strong emphasis on the fact that the *standard libraries* `Zero` and `One` where *never* stabilized and it's always been recommended to use the `num` crate. – Shepmaster Apr 29 '17 at 23:11
  • 2
    Using [`num-traits`](https://crates.io/crates/num-traits) is better (less dependencies), unless you want everything provided by `num` (big integer, complex, rational, ...). – kennytm Apr 30 '17 at 03:53
  • Thank you; I clearly need to keep hammering on getting my traits down. And since I have you here: I'm assuming that the `.map.sum` will lead to iterating over the list twice, where as `fold` only uses a single pass? – AManOfScience May 02 '17 at 11:32
  • 1
    Not really, `map` will yield a new lazy iterator that wraps the original iterator without consuming it immediately. It's more like Java's Stream#map, and unlike JavaScript's map. – E_net4 May 02 '17 at 12:01
  • Cool. I'll add for future folks: the reason being `.map` creates a `Map::Iterator` whose `.next()` lazily computes the next value from the Iterator it's wrapping, as seen in the std lib source code [here](https://doc.rust-lang.org/src/core/iter/mod.rs.html) (search for `fn next`). – AManOfScience May 02 '17 at 12:11