///|
pub(open) trait Semigroup {
  op_add(Self, Self) -> Self
  concat(Self, Self) -> Self = _
  concat_option(Self, Self?) -> Self = _
  concat_to_option(Self?, Self) -> Self? = _
  concat_many(Self, Many[Self]) -> Self = _
  concat_array(Self, Array[Self]) -> Self = _
  concat_iter(Self, Iter[Self]) -> Self = _
}

///|
impl Semigroup with concat(a, b) {
  a.op_add(b)
}

///|
impl Semigroup with concat_option(a, b) {
  match b {
    Some(b) => a.op_add(b)
    None => a
  }
}

///|
impl Semigroup with concat_to_option(a, b) {
  match a {
    Some(a) => Some(a.op_add(b))
    None => Some(b)
  }
}

///|
impl Semigroup with concat_many(x, xs) {
  xs.fold(init=x, Semigroup::concat)
}

///|
impl Semigroup with concat_array(x, xs) {
  xs.fold(init=x, Semigroup::concat)
}

///|
impl Semigroup with concat_iter(x, xs) {
  xs.fold(init=x, Semigroup::concat)
}

///|
pub impl Semigroup for Unit with op_add(_x, _y) {
  ()
}

///|
pub impl Semigroup for Int with op_add(_x, _y) {
  _x + _y
} ///|

///|
pub impl Semigroup for UInt with op_add(_x, _y) {
  _x + _y
}

///|
pub impl[A] Semigroup for Array[A] with op_add(_x, _y) {
  _x + _y
}

///|
pub impl Semigroup for String with op_add(x, y) {
  x + y
}

///|
pub impl Semigroup for String with concat_many(x, y) {
  Semigroup::concat_iter(x, y.inner().iter())
}

///|
pub impl Semigroup for String with concat_iter(x, y) {
  let builder = StringBuilder::new()
  builder.write_string(x)
  for s in y {
    builder.write_string(s)
  }
  builder.to_string()
}

///|
pub impl[A : Semigroup] Semigroup for A? with op_add(a, b) {
  match (a, b) {
    (Some(a), Some(b)) => Some(a.op_add(b))
    (Some(_) as fa, None) => fa
    (None, fb) => fb
  }
}

///|
pub fn[A : Semigroup] concat3(a : A, b : A, c : A) -> A {
  a.op_add(b).op_add(c)
}

///|
test "concat" {
  let a = Semigroup::concat([1], [2])
  println(a)
}