16

If I want to create a Cartesian product of a list of lists in Haskell, I can do this:

product [] = [[]]
product (xs:xss) = concatMap (\k -> map (k:) (product1 xss)) xs

or even this:

sequence xss

I'm trying to implement an efficient iterator that would do the same in Rust, but I'm not sure what is wrong with my attempt:

use std::iter::{empty, once};

fn product<T, I, V>(xss: I) -> Box<Iterator<Item = Iterator<Item = T>>>
where
    T: Clone,
    V: IntoIterator<Item = T>,
    I: IntoIterator<Item = V>,
{
    Box::new(xss.into_iter().fold(once(empty()), |acc, xs| {
        xs.into_iter().flat_map(|x| acc.map(|ys| ys.chain(once(x))))
    }))
}

fn main() {
    let data = vec![[1, 2, 3], [10, 20, 30], [100, 200, 300]];
    let it: Vec<Vec<u32>> = product(data).collect();
    println!("{:?}", it);
}

(playground)

Produces these errors:

error[E0308]: mismatched types
  --> src/main.rs:10:9
   |
10 |         xs.into_iter().flat_map(|x| acc.map(|ys| ys.chain(once(x))))
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected struct `std::iter::Once`, found struct `std::iter::FlatMap`
   |
   = note: expected type `std::iter::Once<std::iter::Empty<T>>`
              found type `std::iter::FlatMap<<V as std::iter::IntoIterator>::IntoIter, std::iter::Map<std::iter::Once<std::iter::Empty<T>>, [closure@src/main.rs:10:45: 10:67 x:_]>, [closure@src/main.rs:10:33: 10:68 acc:_]>`

error[E0271]: type mismatch resolving `<std::iter::Once<std::iter::Empty<T>> as std::iter::Iterator>::Item == std::iter::Iterator<Item=T>`
  --> src/main.rs:9:5
   |
9  | /     Box::new(xss.into_iter().fold(once(empty()), |acc, xs| {
10 | |         xs.into_iter().flat_map(|x| acc.map(|ys| ys.chain(once(x))))
11 | |     }))
   | |_______^ expected struct `std::iter::Empty`, found trait std::iter::Iterator
   |
   = note: expected type `std::iter::Empty<T>`
              found type `std::iter::Iterator<Item=T>`
   = note: required for the cast to the object type `std::iter::Iterator<Item=std::iter::Iterator<Item=T>>`

error[E0277]: the trait bound `[{integer}; 3]: std::iter::Iterator` is not satisfied
  --> src/main.rs:16:29
   |
16 |     let it: Vec<Vec<u32>> = product(data).collect();
   |                             ^^^^^^^ `[{integer}; 3]` is not an iterator; maybe try calling `.iter()` or a similar method
   |
   = help: the trait `std::iter::Iterator` is not implemented for `[{integer}; 3]`
   = note: required because of the requirements on the impl of `std::iter::IntoIterator` for `[{integer}; 3]`
   = note: required by `product`

error: the `collect` method cannot be invoked on a trait object
  --> src/main.rs:16:43
   |
16 |     let it: Vec<Vec<u32>> = product(data).collect();
   |                                           ^^^^^^^

The first error is giving me the feeling that Rust cannot even create a lazily consumed iterator with fold because Empty<T> is an Iterator<Item = T> (at least conceptually), but I hope I'm wrong.

Rodrigo de Azevedo
  • 1,097
  • 9
  • 17
user1685095
  • 5,787
  • 9
  • 51
  • 100
  • 1
    Boxed iterator is not an adequate substitution for lazily evaluated Haskell variable as it cannot be rewound or cloned and thus such direct translation will not work. Representation of a list as a chain of boxed `Chain`s will not be efficient either. – red75prime Dec 06 '17 at 09:09
  • @red75prime Okay, so how do I do that generically and in functional style? – user1685095 Dec 06 '17 at 09:56
  • 7
    You're writing Rust but thinking Haskell, it will go wrong. Take a look at [this](http://killercup.github.io/vibrant-rs/itertools/struct.Product.html) to see what a rust implementation could look like. – papagaga Dec 06 '17 at 10:12
  • 1
    @user1685095, you'll need to implement all the machinery functional languages hide under the hood. I [gave it a try](https://play.rust-lang.org/?gist=0b85820cad5cd9cfceb44ca03e7e250b&version=nightly). – red75prime Dec 08 '17 at 08:10
  • The code isn't fast. It could be expected as it is a naïve translation of the functional program. – red75prime Dec 08 '17 at 12:56
  • @red75prime 1. `FnBox` is obsolate. You can simply use `FnOnce` according to the latest PR.(You still need beta or nightly) 2. `clone_closure` and `copy_closure` are stable already. – Earth Engine Apr 16 '19 at 02:07
  • 1
    I posted a link to this question on reddit, and got very interesting answers: https://www.reddit.com/r/rust/comments/bdlna5/is_there_a_good_rust_translation_of_these_2_lines/ – lovasoa Apr 16 '19 at 12:39
  • I think [`Itertools::multi_cartesian_product`](https://docs.rs/itertools/latest/itertools/trait.Itertools.html#method.multi_cartesian_product) does this. Duplicate of https://stackoverflow.com/q/75834334/5445670. – Solomon Ucko Mar 24 '23 at 13:56
  • Does this answer your question? [Cartesian product of n ranges](https://stackoverflow.com/questions/75834334/cartesian-product-of-n-ranges) – Solomon Ucko Mar 25 '23 at 04:02

2 Answers2

1

The first reason your approach is bound to fail is because you're trying to transpose an algorithm designed to work on lists into an algorithm working on iterators. Lists are suitable for a functional approach, iterators aren't, because they have a state. The next(&mut self) function won't return the same value each time it's called with the same argument, whereas a next(x:xs) function will. This is the reason why the implementation found in itertools clones iterators: to save their initial state and recover it for the next iteration over the set.

The second reason, the one behind the error messages, is that you're fighting against Rust's type system. The result values of all your calls to iterator functions (fold, flat_map, etc.) aren't trait objects but 'concrete types'. For instance iterator.fold(init, fn)'s result type is init's type. That's why the compiler complains when you pass fold a lambda that doesn't return a std::iter::Empty<T>.

But it gets worse. You could imagine to coerce or cast that std::iter::Empty<T> into a trait object. Alas, object safety is required. To put it in a nutshell, "A good intuition is “except in special circumstances, if your trait’s method uses Self, it is not object-safe.". But iterators' main method is next(&mut self).

papagaga
  • 1,108
  • 8
  • 14
  • 2
    Object safety doesn't come into play here — [it's fine to create a trait object from `iter::empty`](https://play.integer32.com/?gist=11a96a3cec3980c939de73ec8c0c121f&version=stable). That "intuition" refers to arguments other than `self`. – Shepmaster Dec 06 '17 at 18:11
  • 1
    "you're trying to transpose an algorithm designed to work on lists into an algorithm working on iterators": this is not really true... Haskell lists are closer to a cloneable iterator than they are to a Vec in rust. The algorithm presented here only relies on fetching the next element from an iterator, and cloning it. The thing that makes this two-lines haskell program hard to implement in rust is the lazy nature of Haskell. – lovasoa Apr 15 '19 at 15:59
1

For what its worth, the Itertools crate implements a workable cartesian product function:

use itertools::Itertools;

let it = (0..2).cartesian_product("αβ".chars());
itertools::assert_equal(it, vec![(0, 'α'), (0, 'β'), (1, 'α'), (1, 'β')]);