lloydmeta / frunk

Funktional generic type-level programming in Rust: HList, Coproduct, Generic, LabelledGeneric, Validated, Monoid and friends.
https://beachape.com/frunk/
MIT License
1.29k stars 58 forks source link

combine_n should do exponentation by squaring #122

Open ExpHP opened 6 years ago

ExpHP commented 6 years ago

https://github.com/lloydmeta/frunk/blob/bdb8b0fc1cb946a46b7c5817a8d865c75bb020b9/src/semigroup.rs#L90-L101

I'm honestly surprised to see that this doesn't do exponentation by squaring; to me, that has always been the point of having a function like combine_n

N.B. The correctness of using exponentation by squaring is guaranteed by the law of associativity.

ExpHP commented 6 years ago

p.s. pseudocode for monoid (well, okay, this is more code than pseudo):

    pub fn combine_n(&self, mut exp: u64) -> Self {
        let mut acc = Self::identity();
        let mut base = self.clone();
        while exp > 0 {
            if (exp & 1) == 1 {
                acc = acc.combine(&base); // multiply by a power of two
            }
            base = base.combine(&base); // square for next power of two
            exp /= 2;
        }
        acc
    }

and the one for Semigroup is tricky to write out by hand, but IIRC it ultimately can be expressed in terms of the monoid on Option (but don't quote me on this; it needs tests)

// FIXME needs tests
pub fn combine_n_opt(x: &T, n: u64) -> Option<T>
where T: Clone + Semigroup,
{
    // note: &T and &Some(T) have the same representation so
    //      this first clone can be avoided with unsafe...
    monoid::combine_n(&Some(x.clone()), n)
}

// FIXME needs tests
pub fn combine_n(x: &T, n: u64) -> T
where T: Clone + Semigroup,
{
    combine_n_opt(x, n).expect("can't combine 0 copies in a semigroup!")
}
lloydmeta commented 6 years ago

Oh, this is a good idea. This is definitely a much better combine_n!