Providing Typeclasses

Imagine you have a typeclass for serialization, for example:

trait SerializeToString[T] {
  def serialize(t: T): String
}
object SerializeToString {
  given SerializeToString[String] = str => str
  given SerializeToString[Long] = _.toString
}

trait DeserializeFromString[T] {
  def deserialize(s: String): Either[String, T]
}
object DeserializeFromString {
  given DeserializeFromString[String] = Right(_)
  given DeserializeFromString[Long] = str => str.toLongOption.toRight(s"Not a long: $str")
}

And now you have your newtypes:

import yantl.*

object Age extends Newtype.ValidatedOf(IArray(ValidatorRule.minValue(0L)))
type Age = Age.Type

object Name extends Newtype.WithoutValidationOf[String]
type Name = Name.Type

Wouldn't it be nice if these typeclass instances could be provided for your newtypes automatically?

given newTypeSerializeToString[TUnderlying, TWrapper](using
  newType: yantl.Newtype.WithType[TUnderlying, TWrapper],
  serializer: SerializeToString[TUnderlying],
): SerializeToString[TWrapper] =
  t => serializer.serialize(t.unwrap)

given newTypeDeserializeFromString[TUnderlying, TWrapper](using
  newType: yantl.Newtype.WithType[TUnderlying, TWrapper],
  deserializer: DeserializeFromString[TUnderlying],
): DeserializeFromString[TWrapper] =
  str => deserializer.deserialize(str).flatMap(newType.makeAsString)

// The instances are automatically provided
summon[SerializeToString[Age]]
// res0: SerializeToString[Type] = repl.MdocSession$MdocApp$$Lambda$13708/0x0000000103737040@1e4da5e0
summon[DeserializeFromString[Age]]
// res1: DeserializeFromString[Type] = repl.MdocSession$MdocApp$$Lambda$13710/0x0000000103738040@2f825473

summon[SerializeToString[Name]]
// res2: SerializeToString[Type] = repl.MdocSession$MdocApp$$Lambda$13708/0x0000000103737040@44d6b2a9
summon[DeserializeFromString[Name]]
// res3: DeserializeFromString[Type] = repl.MdocSession$MdocApp$$Lambda$13710/0x0000000103738040@54e179fc

You can also provide instances only for unvalidated newtypes:

given newTypeDeserializeUnvalidatedFromString[TUnderlying, TWrapper](using
  newType: yantl.Newtype.WithUnvalidatedType[TUnderlying, TWrapper],
  deserializer: DeserializeFromString[TUnderlying],
): DeserializeFromString[TWrapper] =
  str => deserializer.deserialize(str).map(newType.apply)

// The instance is automatically provided
summon[DeserializeFromString[Name]]
// res4: DeserializeFromString[Type] = repl.MdocSession$MdocApp$$Lambda$13713/0x0000000103739440@69958152