2
votes

With Rust traits, I can express a Monoid type class (forgive me for the naming of the methods):

trait Monoid {
  fn append(self, other: Self) -> Self;
  fn neutral() -> Self;
}

Then, I can also implement the trait for strings or integers:

impl Monoid for i32 {
  fn append(self, other: i32) -> i32 {
    self + other
  }
  fn neutral() -> Self { 0 }
}

However, how could I now add another implementation on i32 for the multiplication case?

impl Monoid for i32 {
  fn append(self, other: i32) -> i32 {
    self * other
  }
  fn neutral() { 1 }
}

I tried something like what is done in functional but that solution seems to rely on having an additional type parameter on the trait instead of using Self for the elements, which gives me a warning.

The preferred solution would be using marker traits for the operations - something I also tried but didn't succeed in.

1
I don't understand what this is supposed to represent. How is the compiler supposed to guess which implementation you want if both have exactly the same signatures?Félix Adriyel Gagnon-Grenier
You are right. I would like to find out, whether there is a way to use something like marker traits as additional type parameters or something the like to let the compiler distinguish the two cases.Yann
@Yann, about my post: Yes, but the case with the trait constrains is different. Since a type could implement just one of them. The trait constrains should be enough for him to realise it is 2 different implementations. And in case a type implement both mul and add, then you can disambiguate it explicitly in use.... I don't know why it cannot infer that they are 2 different implementations.Netwave
You can use a marker type such as in this playground. Is that what you want?rodrigo
@Yann: Nice! Now that you have a working code, feel free to answer (and eventually accept) your own question,rodrigo

1 Answers

3
votes

The answer, as pointed out by @rodrigo, is to use marker structs.

The following example shows a working snippet: playground

trait Op {}
struct Add;
struct Mul;
impl Op for Add {}
impl Op for Mul {}

trait Monoid<T: Op>: Copy {
    fn append(self, other: Self) -> Self;
    fn neutral() -> Self;
}

impl Monoid<Add> for i32 {
    fn append(self, other: i32) -> i32 {
        self + other
    }
    fn neutral() -> Self {
        0
    }
}

impl Monoid<Mul> for i32 {
    fn append(self, other: i32) -> i32 {
        self * other
    }
    fn neutral() -> Self {
        1
    }
}

pub enum List<T> {
    Nil,
    Cons(T, Box<List<T>>),
}

fn combine<O: Op, T: Monoid<O>>(l: &List<T>) -> T {
    match l {
        List::Nil => <T as Monoid<O>>::neutral(),
        List::Cons(h, t) => h.append(combine(&*t)),
    }
}

fn main() {
    let list = List::Cons(
        5,
        Box::new(List::Cons(
            2,
            Box::new(List::Cons(
                4,
                Box::new(List::Cons(
                    5,
                    Box::new(List::Cons(-1, Box::new(List::Cons(8, Box::new(List::Nil))))),
                )),
            )),
        )),
    );
    
    println!("{}", combine::<Add, _>(&list));
    println!("{}", combine::<Mul, _>(&list))
}