Just an aside… Note that the encoding and decoding scenario are not as symmetrical as it may seem at first glance.
Here you declare that the empty tuple should always be encoded as an empty list, which matches intuition and doesn’t leave any ambiguity.
Here, on the other hand, you claim that any row can be decoded as an empty tuple (which is what’s triggered in your failing test case), whereas intuitively you’d probably want to constrain this decoding to the empty row.
I have no immediate suggestion how to “fix” this - I’m a Scala 3 beginner, as well, and I don’t have much experience with tuple/HList magic, so far. (And I fear it’d make things considerably more complicated.) Plus, it’s probably fine for now - you don’t have actual type safety here, since you need to explicitly declare your desired result type, anyway, as demonstrated by @Jasper-M. But this is the kind of type level issues that Scala encourages to think about more than other languages.
Another asymmetry is that field encoding will always succeed (there’s a #toString
for every type), whereas field decoding can fail - not every string can be parsed as an int, etc. Again, this is fine for now, failures will raise exceptions (which you’ll likely want to handle at the call site or above). At some point you may (or may not) want to make this failure mode explicit in the types, though, e.g. by using Either[Throwable, A]
as a return type rather than plain A
and catching/wrapping exceptions accordingly at the implementation site.