I've been implementing functions for concepts where the implementing types are heterogeneous tuples of 2,3,4 or more elements. Something like:
type Eq* = concept x, y
eq(x, y) is bool
# ordinals implement Eq
func eq*(x, y: SomeOrdinal): bool =
x == y
# floats implement Eq
func eq*(x, y: SomeFloat): bool =
x == y
# Any two tuple with Eq-compliant members implements Eq
func eq*[T, U: Eq](xs, ys: (T, U)): bool =
eq(xs[0], ys[0]) and eq(xs[1], ys[1])
# Ditto for 3,4, or more element heterogeneous tuples.
These implementations need to happen across a dozen or more concepts and for tuples of arity 2,3,4 or more. Writing all those tuples terms gets old fast, so I started looking into templates and macros.
Maybe something like:
template tuple_all*[T, U](rel: untyped, xs, ys: (T, U)): untyped =
rel(xs[0], ys[0]) and rel(xs[1], ys[1])
Which could be called with:
func eq*[T, U: Eq](xs, ys: (T, U)): bool =
tuple_all(eq, xs, ys)
The issue though is that rel:untyped parameter. In the expanded code it will likely be two different overloaded eq instances.
With a template is the expansion of the rel terms coming too late in the compilation process to match the correct eq call? Is this a use-case for a more generic macro rather than a template?
Any advice much appreciated.
func eq(x, y: SomeOrdinal): bool = x == y
func eq[T: tuple | object](x, y: T): bool =
for xi, yi in fields(x, y):
if not eq(xi, yi):
return false
return true
doAssert eq(7, 7)
doAssert not eq(7, 8)
doAssert eq((2, 3'u), (2, 3'u))
doAssert not eq((2, 3'u, 4'i8), (2, 3'u, 5'i8))
doAssert eq((2'i8, (3'u16, (4'u32, 5))), (2'i8, (3'u16, (4'u32, 5))))
doAssert not compiles(eq((2, 3'u32), (2, 3'i32)))
Thanks -- that's a very useful technique.
I think the generic T: tuple | object parameter and the eq(xi, yi) call have the same effect as my original eq*[T, U: Eq](...) function? That is, if types T and U implement eq so does (T, U)?