Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Combine trait for string and collection concatenation #203

Closed
wants to merge 8 commits into from
79 changes: 79 additions & 0 deletions active/0000-compose-trait.md
@@ -0,0 +1,79 @@
- Start Date: 2014-08-17
- RFC PR #: (leave this empty)
- Rust Issue #: https://github.com/rust-lang/rust/issues/16541

# Summary

A `Combine` trait is added with a single function `combine` which sugars to
the `++` operator. The `Add` implementation for `String` is replaced by one
for `Combine`.

# Motivation

As @huonw mentions in https://github.com/rust-lang/rust/issues/14482, mathematical
convention is that addition is commutative. The stdlib already follows mathematical
convention in some cases (for example, `Zero` requires `Add` and is expected to
be the additive identity; ditto for `One` and `Mul`).

It is an (often unstated) assumption in many algorithms that `+` is a commutative
operator. Violating this assumption in the stdlib forces programmers to memorize
that `+` means something different in rust than it does in common language, and
also risks encouraging abuse of operator overloading.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add trait is an interface, not an implementation. You couldn't assume whether it's a commutative or not.
Traits specify the interfaces not implementations.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does Eq fit into this claim?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A trait summarises common behaviour, e.g. things that implement Eq are expected to have a sensible implementation of equality, things that implement Add are expected to have a sensible definition of addition.

Functions can operate assuming that these traits have sensible implementations or else they will return unexpected results, e.g. HashMap will not give expected results if a type has a bad Eq implementation.

The only thing a non-unsafe stdlib function can't do is be memory unsafe with a violation of these sensible properties, since that would allow memory-unsafety in safe code.

This sort of contract (i.e. not enforcable in the signature of a trait) is quite common and is the whole point of traits. It's not just that certain types have a method with a certain signature, but also that that method acts in a particular way.


There is a postponed proposal regarding having unit tests for Traits which enforce
invariants; commutativity of `+` is an natural one and it would be bad if it was
unenforcable because standard library was violating it.

# Detailed design

Currently everything in the stdlib which implements `Add` implements `add` as a
commutative operator, except for strings. Therefore I propose:
- Introduce a `Combine` trait with a `combine` function that sugars to the `++`
operator. The precedence of `++` is between the bitwise operators and the comparison operators.
- Implement this on `String` for concatenation. This replaces `Add` for `String`.
- Implement this for every collection, using the same semantics as their `Extendable`
implementations. (`Vec`s and `Bitv`s would be concatenated, sets and maps would
be unioned, etc.)
- Implement this on `Path` as a synonym for `join`
- Add "must be commutative" to the documentation for `Add`.
- Add "must be associative" to the documentation for `Combine`.

The signature of `combine` is exactly the same as that for `add` and the other
binary operators:

````rust
pub trait Combine<RHS,Result> {
/// The method for the `++` operator
fn combine(&self, rhs: &RHS) -> Result;
}
````
and will be updated alongside the other binary-operation traits as the trait system
is revamped. (For example, adding `CombineAssign` for in-place `++=` or making
`Result` an associated item.)

For those interested in algebraic names, this makes `++` into a semigroup operator.
Users who want an abelian group can then use `Add+Zero+Neg` (or `Add+Zero+Sub`,
this ambiguity should probably be addressed in a later RFC related to fixing the
numeric traits once we have associated items); users who want an arbitrary group
can use `Mul+One+Div`; users who want a monoid can use `Combine+Default`, etc.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if I want to write a generic method that works on all groups? According to this I would write

fn f<T: Mul+One+Div>(...

Naturally this algorithm also works for all abelian groups, but how does the compiler know that? The biggest argument for this RFC seems to be that abstract algorithms can make certain assumptions, but does this scheme even support abstract algorithms?


This way nobody is surprised by generic code which sometimes does the Wrong Thing,
but we avoid having a Haskell-like scenario where every category-theoretic object
is supported (with corresponding mental load). We just have a few binary operators
each with simple conventions.

# Drawbacks

Code which uses `+` to add strings will need to use `++` instead.

# Alternatives

Leave `+` as is.

# Unresolved questions

`Combine` should also be used for function compositions, at least for single-argument
functions `T->T`. How would this interact with our current/future coherence rules?

Where else should `Combine` be used?