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