spartanz / schemaz

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
https://spartanz.github.io/schemaz
Apache License 2.0
164 stars 18 forks source link

Change the module structure so that the Schema ADT has a stable path #27

Closed vil1 closed 5 years ago

vil1 commented 5 years ago

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.

GrafBlutwurst commented 5 years ago

https://github.com/GrafBlutwurst/scalaz-schema/tree/module-structure-2 as long as type inference doesn't break the user shouldn't be impacted too much