Reputation: 535
I am writing a library in Scala, and decided to jump on the updated (experimental) syntax introduced in version 3.5.0 for working with type classes. So my code has:
trait HasBoundary:
type Self
extension (self : Self)
def boundary[Coefficient : Fractional] : Chain[Self, Coefficient]
trait HasDimension:
type Self
extension (self : Self)
def dim : Int
trait OrderedCell extends HasBoundary with HasDimension
class Chain[Cell : OrderedCell, Coefficient : Fractional](val entries : Map[Cell, Coefficient])
sealed trait ElementaryInterval:
def n : Int
case class DegenerateInterval(n : Int) extends ElementaryInterval
case class FullInterval(n : Int) extends ElementaryInterval
At this point, I find myself struggling with writing given
statements to make the entire GADT ElementaryInterval = DegenerateInterval | FullInterval
a member of the OrderedCell
type class.
I could write:
given DegenerateInterval is OrderedCell:
extension (t : DegenerateInterval)
def boundary[CoefficientT : Fractional] = Chain(Map.empty)
def dim : Int = 0
end extension
end given
given FullInterval is OrderedCell:
extension (t : DegenerateInterval)
def boundary[CoefficientT : Fractional as fr] = Chain(Map.from(DegenerateInterval(t.n+1) → fr.one, DegenerateInterval(t.n) → fr.negate(fr.one)))
def dim : Int = 1
end extension
end given
But then my boundary
functions don't give the right return type - they would seem to need to be stuck to their respective case classes and not able to climb up the inheritance hierarchy.
Or I could write it all for the ElementaryInterval
type, as
given ElementaryInterval is OrderedCell:
extension (t : ElementaryInterval)
def boundary[Coefficient : Fractional as fr] = t match {
case DegenerateInterval(_) => Chain(Map.empty)
case FullInterval(n) => Chain(Map.from(DegenerateInterval(n+1) → fr.one, DegenerateInterval(n) → fr.negate(fr.one)))
end extension
end given
But then I seem to run into problems when building a Chain
object containing a DegenerateInterval
or a FullInterval
, because it goes looking for a given
clause for the case classes instead of the one I wrote for the sealed trait.
So, to summarize my question / issue:
Can I combine the new syntax for type classes with the sealed trait
/ case class
pattern for GADTs? How do I write my given
clauses to implement the type class membership so that it is compatible with the inheritance structure in the GADT?
Is there some way to annotate variance within the given
instance in some sensible way?
Upvotes: 1
Views: 70
Reputation: 535
I would still like to understand the interplay between GADT constructions and type classes better, but I have managed to resolve this particular knot:
The key seems to have been explicit type annotations in more places than expected. The following seems to have worked:
given ElementaryInterval is OrderedCell:
extension (t : ElementaryInterval)
def boundary[Coefficient : Fractional as fr] = t match {
case DegenerateInterval(_) => Chain[ElementaryInterval, Coefficient](Map.empty)
case FullInterval(n) => Chain[ElementaryInterval, Coefficient](Map.from(DegenerateInterval(n+1) → fr.one, DegenerateInterval(n) → fr.negate(fr.one)))
def dim : Int = t match {
case DegenerateInterval(_) => 0
case FullInterval(_) => 1
}
In other words, if I don't allow the type inference to land on something like Chain[DegenerateInterval, Coefficient]
, it resolves the given instance correctly when checking the conditions on Chain
.
Upvotes: 0