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

Reply via email to