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