A couple of notes: 1. &, == etc are likely to be heavily overloaded by code that knows nothing about laws you imply. For example, a & identity(type(a)) === a' might not hold for some container type with referential equality - but it still would be a `Monoid by your condition. In concepts it's generally better to use some names that are unlikely to be unintentionally overriden, preferably ones that match corresponding entities in other languages/libraries (e.g. mappend instead of &). More convenient aliases can be implemented as generic wrappers. 2. Parameterized concepts (like type Container[A] = concept ...) are not properly supported at the moment (there is an ongoing effort to implement those). Even if they were, I personally do not think that inferring a parameter without some additional information would be possible. Fortunately, parameterized concepts are not required in this example. 3. Concepts are type classes, rather than actual types. This means that this code:
> > proc `&`(a: Semigroup, b: Semigroup): Semigroup > > > is more or less the same as this: > > > proc `&`[A: Semigroup, B: Semigroup, C: Semigroup](a: A, b: B): C > > > So a and b can have different types, and C can't even be inferred (so this > won't compile). Type-safe append operator for all semigroups would actually > look like this: > > > proc `&`[T: Semigroup](a: T, b: T): T > Here's a working piece of code that (I think) does what you want it to: type # There's no point in making concepts generic - # and it doesn't really work yet, AFAIK. Setoid = concept a, b a == b is bool Semigroup = concept a, b a & b is type(a) Monoid = concept a a is Semigroup identity(type(a)) is type(a) Identity[T] = object value: T # Instances for simple types proc `&`(a, b: int): int = a + b # Changed the argument to a `typedesc` - we don't use the value anyway proc identity(t = int): int = 0 # Same as this: # proc identity(t: typedesc[int]): int = 0 assert: int is Setoid assert: int is Semigroup assert: int is Monoid assert: string is Setoid # We've got `&` for free assert: string is Semigroup # No `identity` assert: string isNot Monoid assert: float is Setoid assert: float isNot Semigroup assert: float isNot Monoid # Instances for Identity proc `==`[T: Setoid](a, b: Identity[T]): bool = a.value == b.value # Only defined if the underlying type itself is a Semigroup proc `&`[T: Semigroup](a, b: Identity[T]): Identity[T] = Identity[T](value: a.value & b.value) proc identity[T: Monoid](t:typedesc[Identity[T]]): Identity[T] = Identity[T](value: identity(T)) assert: Identity[int] is Setoid assert: Identity[int] is Semigroup assert: Identity[int] is Monoid assert: Identity[string] is Setoid assert: Identity[string] is Semigroup assert: Identity[string] isNot Monoid assert: Identity[float] is Setoid assert: Identity[float] isNot Semigroup assert: Identity[float] isNot Monoid let ca = Identity[int](value: 3) let cb = Identity[int](value: 6) let cc = Identity[int](value: 6) echo("Setoid compare: ", cb == cc) echo("Monoid identity: ", identity(Identity[int])) echo("Semigroup append (=9): ", ca & cb) Also, you might want to take a look at [emmy](https://github.com/unicredit/emmy), specifically [structures](https://github.com/unicredit/emmy/blob/master/emmy/structures.nim) module