Combining new Scala typeclass syntax with GADT types

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

Answers (1)

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

Related Questions