Non-Orphan Typeclass Instances
As of smithy4s version 0.18.x
you are able to create custom typeclass instances inside the companion objects of classes in the code generated by smithy4s. This allows you to have instances that are found via implicit resolution without any need to special imports. Common examples where this will come in handy are for the cats.Show
and cats.Eq
typeclasses, but you can use this feature for any typeclass.
Here we will show an example using the cats.Hash
typeclass.
Setup Typeclass in Smithy
Here we will use the smithy4s.meta#typeclass
trait to define a hash
trait that represents the cats.Hash
typeclass.
use smithy4s.meta#typeclass
@trait
@typeclass(targetType: "cats.Hash", interpreter: "smithy4s.interopcats.SchemaVisitorHash")
structure hash {}
We are specifying cats.hash
as the targetType
since that is the typeclass which this trait represents. We are then specifying smithy4s.interopcats.SchemaVisitorHash
as the classpath which points to a CachedSchemaCompiler
for the cats.Hash
typeclass.
Implement CachedSchemaCompiler
Smithy4s has a concept called CachedSchemaCompiler
which is an abstraction which we use here to interpret a smithy4s.Schema
to produce an instance of a typeclass. Here is what this will look like:
object SchemaVisitorHash extends CachedSchemaCompiler.Impl[Hash] {
protected type Aux[A] = Hash[A]
def fromSchema[A](
schema: Schema[A],
cache: Cache
): Hash[A] = {
schema.compile(new SchemaVisitorHash(cache))
}
}
Here we are delegating to the SchemaVisitorHash
which is doing the heavy lifting of interpreting the smithy4s.Schema
. The CachedSchemaCompiler.Impl
provides a Cache
which we utilize to make sure we are not recompiling the same schema more than once. For more details on implementing a SchemaVisitor
, you can check out the full cats.Hash
schema compiler and visitor here.
Use the hash typeclass trait
Now we are ready to use the hash
trait we defined above.
@hash
structure MovieTheater {
name: String
}
This tells smithy4s to generate an instance of the Hash
typeclass in the companion object of the MovieTheater
type and to use the CachedSchemaCompiler
defined above for the implementation. The generated code will look like:
case class MovieTheater(name: Option[String] = None)
object MovieTheater extends ShapeTag.Companion[MovieTheater] {
val id: ShapeId = ShapeId("smithy4s.example", "MovieTheater")
// ...
implicit val schema: Schema[MovieTheater] = // ...
implicit val movieTheaterHash: cats.Hash[MovieTheater] = SchemaVisitorHash.fromSchema(schema)
}