brendanzab / algebra Goto Github PK
View Code? Open in Web Editor NEWAbstract algebra for Rust (still very much a WIP!)
License: Apache License 2.0
Abstract algebra for Rust (still very much a WIP!)
License: Apache License 2.0
The solution we chose for #18 is to parametrize traits over the operator symbol (which is identified using structs). Thus we don't have MagmaAdditive
nor MagmaMultiplicative
traits. The down side is that we cannot use operator overloading without first wrapping our values into a struct so that we can "see" them as part of a Magma<Additive>
or Magma<Multiplicative>
.
Thus something like this, for elements of a field:
(a + b) * c
has to become something like that:
Wm((Wa(a) + Wa(b))) * Wm(c)
where Wm
and Wa
are wrappers to access the addition and multiplication operations.
There are very common places where wrapper structs could be avoided because we already know that, e.g., a type implements Magma<Additive>
iff. it implements Add<...>
because of default implementations like that one. What I propose here is, for example, to modify the ring trait from this:
pub trait RingApprox
: GroupAbelianApprox<Additive>
+ MonoidApprox<Multiplicative>
{ }
to that:
pub trait RingApprox
: GroupAbelianApprox<Additive>
+ MonoidApprox<Multiplicative>
+ Add<Self, Output = Self> // Addition operator.
+ Sub<Self, Output = Self> // Additive divisibility.
+ Neg<Output = Self> // Additive inverse.
+ Mul<Self, Output = Self> // Multiplication operator.
{ }
Because RingApprox
explicitly depends on the Additive
and Multiplicative
operators, it is fine to add the other operator-overloading-related constraints because we know they have been implemented as well because of our default implementations of the respective magmas. This trick may be applied to any trait that explicitly mention Additive
and Multiplicative
on their constraints.
I would like to try and separate the equality stuff from the structure traits. I'm thinking something like this:
pub trait MagmaAdditive
: Add<Self, Self>
+ Eq
{}
would become:
pub trait MagmaAdditive
: Add<Self, Self>
{}
pub trait MagmaAdditiveLaw
: MagmaAdditive
+ Eq
{}
I believe the scalaz folks are doing something like this.
I think this is important to have because there are many situations in which a monoid can be defined but there is no decidable equality. It is more of an issue for non-numeric instances, e.g., a monoid of endomorphisms.
It's also an issue for containers which are clearly monoids (e.g., Vec<A>
, DList<A>
) but where the particular A
does not have Eq
.
For example: http://typelevel.org/blog/2013/11/17/discipline.html
darinmorrison: bjz: kind of bikeshedding but what do you think about naming things like
QuasigroupAdditiveApprox
vsApproxAdditiveQuasigroup
? I prefer the former since structure properties sort nicely (e.g., in docs). I find it's good for readability too since the property becomes more specific as you read it to the right.
If somebody wants to make use of algebraic properties in unsafe code, they cannot trust normal traits.
Should there be some kind of unsafe marker trait that is needed for unsafe to trust algebraic traits?
Something like: unsafe trait CorrectAlgebra {}
In the source code their presence is implied but commented out. Is there a reason this has been left out so far?
There is a lot of code duplication for all the structural traits because all of them exist twice for addition and multiplication. The problem is also that one can not implement a magma, quasigroup, etc for any other operator than +
or *
. I would feel uncomfortable misusing these for, say, a wedge product. There are furthermore other built-in operators, for which it makes sense to implement these categories.
First I thought it might not be possible to do this to the type system, but then I tried and came up with a solution that allows us to actually be generic over the kind of operator. I haven't thought this through to the very end, but it looks like it works so far.
First of all, let's find an abstraction over closed operators.
/// A type closed under a binary operation
pub trait Closed<Op> {
/// Apply the operator to two arguments
fn op(&self, other: &Self) -> Self;
}
This trait provides a similar signature as the stdlib operators and it is generic over some type Op
. Now what is Op
? The simplest way to connect this trait to the language operators (or any other trait that qualifies) is to introduce a zero-sized struct for each and implement a concrete instance of this trait.
/// Addition
pub struct Addition;
impl<T> Closed<Addition> for T
where T: Add<T, T>
{
fn op(&self, other: &T) -> T {
*self + *other
}
}
This can be done analogously for all relevant operators and works nicely thanks to multidispatch. It can also be extended to arbitrary functions that can be used as an operator, e.g. a cross-product or a wedge product for differentials, etc.
The type parameter of the Closed
trait can not be inferred, which makes for a bit of an ugly signature using this generically:
fn commute<T: Closed<Op>, Op>(a: &T, b: &T) -> T {
b.op(a)
}
#[test]
fn int_ops_commute() {
let (a, b) = (3i, 2i);
assert_eq!(commute::<_, Addition>(&a, &b), a + b);
assert_eq!(commute::<_, Multiplication>(&a, &b), a * b);
}
Note that one may define operators more generically in terms of right-hand side and result but I wanted to keep this example simple.
Now this abstraction provides some benefits. For once we only need one generic definition for a Magma now instead of two and it automatically extends to all closed operators.
/// A magma
pub trait Magma<Op>: Closed<Op> + Eq {}
impl<T, Op> Magma<Op> for T
where T: Closed<Op> + Eq
{}
Another nice feature is that we can do stuff with more operators at the same time. Enter the quasigroup, which has two separate notions of division.
/// Left divisible
pub trait LeftDivisible<Op>: Closed<Op> {}
/// A quasigroup
pub trait QuasiGroup<Mag, LeftDiv, RightDiv>:
Magma<Mag>
+ LeftDivisible<LeftDiv>
+ Closed<RightDiv>
{ /* properties */ }
Currently, we can not write the following code due to rust-lang/rust#18693, which is why the above workaround using the helper trait is necessary. However, it was indicated that the restrictions here should be lifted in the future.
/// A quasigroup
/// NOTE: this does not compile at the moment!
pub trait QuasiGroup<Mag, LeftDiv, RightDiv>:
Magma<Mag>
+ Closed<LeftDiv>
+ Closed<RightDiv>
{ /* properties */ }
The names of algebraic structures can sometimes be quite intimidating. The fundamental operations and their possible properties are less so. Perhaps one way of achieving this could be:
// Manually impled by client
trait Op<Rhs, Result> { fn op(&self, other: &Rhs) -> Result; } // Mirrors traits from std::ops
trait Associative: Op<Self, Self> { /* associative property */ }
trait Commutative: Op<Self, Self> { /* commutative property */ }
trait Identity: Op<Self, Self> { fn ident() -> Self; /* preserves self property */ }
// Automatically impled for valid types
trait Magma: Op<Self, Self> {}
impl<T: Op<T, T>> Magma for T {}
trait Semigroup: Magma + Associative {}
impl<T: Magma + Associative> Semigroup for T {}
trait Monoid: Semigroup + Associative {}
impl<T: Semigroup + Associative> Monoid for T {}
I started implementing a similar library awhile ago for monoids and semigroups here. The focus is a little different since I am primarily motivated by algebra for non-numeric types (at least for now) but the basic ideas are the same. The list of "Inspiring Libraries" on the README is also similar :)
It seems that we have similar goals so rather than duplicating a bunch of effort, perhaps there is some way we can join forces and build something better together? I'd be open to structural changes on my end as long as we could still fit non-numeric stuff into the scope of the library.
Thoughts?
algebra.rs seems to have a good one.
Currently, the library is called "math", while the package is "num".
I find this needlessly unintuitive. We should stick with one of those two names.
Just throwing an idea out there, because its really a shame that you cant write a trait once and have it work for both the + 0, * 1 versions of the ops on the same data type.
I believe this requires some actions from the repository owner(s) to activate the service.
These can be done, as soon as there are fields. Should also enable nalgebra
to begin to migrate to the abstractions used here.
cc @sebcrozet
Both IdentityAdditive and IdentityMultiplicative have return type of Self without enforcing that Self: Sized.
This gives warning in 1.6.0 stable and errors with 1.7.0 nightlies.
Stable:
src/structure/ident.rs:44:1: 49:2 help: run `rustc --explain E0277` to see a detailed explanation
src/structure/ident.rs:44:1: 49:2 note: `Self` does not have a constant size known at compile-time
src/structure/ident.rs:44:1: 49:2 note: this warning results from recent bug fixes and clarifications; it will become a HARD ERROR in the next release. See RFC 1214 for details.
Nightly:
src/structure/ident.rs:44:1: 49:2 help: run `rustc --explain E0277` to see a detailed explanation
src/structure/ident.rs:44:1: 49:2 note: `Self` does not have a constant size known at compile-time
src/structure/ident.rs:44:1: 49:2 note: required by `core::ops::Mul`
src/structure/ident.rs:48:5: 48:23 error: the trait `core::marker::Sized` is not implemented for the type `Self` [E0277]
@EpsilonZ and I are now collaborating!
See epsilonz/algebra.rs#30
Currently quickcheck tests for quasigroup and semigroup fail because there is overflows.
Current approach are not suitable for more general algebras:
Lattice consists of two semigroups, their operators have very similiar properties to each other and should not be assumed as additive-like nor multiplicative-like.
Tensor product of modules consists of several different product-like operators, so I think all operators should be parameterized rather than be concrete instances.
A better approach might be letting all operators be parameterized through generics.
For example:
pub trait Ring<Additive:Op, Multiplicative:Op>
: RingApprox<Additive, Multiplicative>
+ GroupAbelian<Additive>
+ Monoid<Multiplicative>
instead of
pub trait Ring
: RingApprox
+ GroupAbelian<Additive>
+ Monoid<Multiplicative>
At the end of the day, the intention of this library is to provide a common basis for generic programming in Rust. It would be good to experiment with real world use cases, and then use those to drive the design of the library. It would also allow us to convince people of the benefits of this approach. As asked by @nical on gitter:
I'm going to ask some rather silly questions, I am more used to focusing on maths to fix a specific problem (like building a graphics engine) rather than design abstractions around math concepts.
silly question 1) what are the common use cases for high level math abstractions like this ?
Or phrased differently, can we list (roughly) the motivations
At the end of the day, we want this to be of a benefit as opposed to a hindrance. I can certainly see that we could get side-tracked down the route of mathematical taxonomic insanity without considering whether this is actually useful and confusing to end users. Thoughts?
Given e.g. a group, there are two sensible ways to view it as a magma. In situations like this, it is not possible to express this relation via trait inheritance without parametrizing over operator symbols. See the discussion in #18 for comparison. Wrapper structs essentially provide the same feature without parametrization.
They could be implemented like this:
struct GroupAsDivisiveMagma<G>(G);
struct GroupAsDivisiveMagmaByRef<'a, G>(&'a G);
Both these variants have their downsides. The first one allows one to easily implement the magma trait but as it consumes its value, it is not practical for non-copy types (think of the group formed by large invertible matrices with their multiplication).
The second one serves as a view of the group but it can not satisfy the function signature of the magma trait making it impossible to use with functionality implemented for magmas.
NOTE: the discussion to decide between this and #18 takes place at #18.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.