A purely-functional library for defining type-safe schemas for algebraic data types, providing free generators, SQL queries, JSON codecs, binary codecs, and migration from this schema definition
This allows for a, more or less, acceptable workaround for the following problem.
When we implement algebras, we are tempted to write something like:
type Encoder[A] = A => String
val algebra = new (Schema[R, Encoder, ?] ~> Encoder) {
def apply[A](schema: Schema[R, Encoder, A]): Encoder[A] = schema match {
case IsoSchema(base, iso) => base.compose(iso.get)
// ...
}
}
This would compile fine, but throw a ClassCastException when executed, because we confused iso.get with iso.reverseGet. It compiles because the inferred type for base is Encoder[Any] (ie. Any => String), so scalac lets us compose pretty much any function with it.
This makes perfect sense since there is no information available about the "inner" type of this IsoSchema (the type represented by base) whatsoever. All that scalac knows for sure is that iso is of type Iso[_, A].
A workaround would be to write the same pattern as :
case i: IsoSchema[Encoder, _, A] => i.base.compose(i.iso.get)
Which wouldn't compile because there, scalac would be able to figure out that the types of base and iso.get don't align.
Unfortunately, with the current module structure, such pattern would trigger a (fatal) warning, because the path of the IsoSchema type isn't stable (it refers to an inner class in some trait).
This PR aims to make the paths of the Schema GADT members stable (by putting them at top level), while keeping the ability to abstract over the Prim, ProductTermId and SumTermId by bundling them into an additional type parameter on Schema, making such pattern compile without a warning.
This allows for a, more or less, acceptable workaround for the following problem.
When we implement algebras, we are tempted to write something like:
This would compile fine, but throw a
ClassCastException
when executed, because we confusediso.get
withiso.reverseGet
. It compiles because the inferred type forbase
isEncoder[Any]
(ie.Any => String
), so scalac lets uscompose
pretty much any function with it.This makes perfect sense since there is no information available about the "inner" type of this
IsoSchema
(the type represented bybase
) whatsoever. All that scalac knows for sure is thatiso
is of typeIso[_, A]
.A workaround would be to write the same pattern as :
Which wouldn't compile because there, scalac would be able to figure out that the types of
base
andiso.get
don't align.Unfortunately, with the current module structure, such pattern would trigger a (fatal) warning, because the path of the
IsoSchema
type isn't stable (it refers to an inner class in some trait).This PR aims to make the paths of the
Schema
GADT members stable (by putting them at top level), while keeping the ability to abstract over thePrim
,ProductTermId
andSumTermId
by bundling them into an additional type parameter onSchema
, making such pattern compile without a warning.