Search code examples
scalapath-dependent-type

Constrain a class with implicit evidence


Say I have this:

  trait Animal {
    type Species
  }

I can easily enough write a function that only takes two animals of the same species

def breed(a: Animal, b: Animal)(implicit evidence: a.Species =:= b.Species) = ???

but I want to create a class with the same sort of constraint:

class Bed(a: Animal, b: Animal)(implicit evidence: a.Species =:= b.Species)

but it won't compile. I've tried a few combinations of trying to use traits with stable identifiers and constraints and what not, but no matter what I do -- I seem to always end up with problems

  trait Bed {
    type T
    def a: Animal { type Species = T }
    def b: Animal { type Species = T }
  }

  object Bed {

    def apply(a1: Animal, b1: Animal)(implicit ev: a1.Species =:= b1.Species) = new Bed {
      type T = b1.Species
      def a = a1  // this line won't compile,  as the compiler can't see the two species are equal ?
      def b = b1 
    }

  }

Thanks.


Solution

  • You can express the constraint via a type argument on Bed.apply instead of via a type equality constraint,

    object Bed {
      def apply[T1](
        a1: Animal { type Species = T1 },
        b1: Animal { type Species = T1 }) = new Bed {
        type T = T1
        def a = a1
        def b = b1
      }
    }
    

    This can be made a little terser with the aid of a type alias,

    type AnimalAux[S] = Animal { type Species = S }
    
    object Bed {
      def apply[T1](a1: AnimalAux[T1], b1: AnimalAux[T1]) =
        new Bed {
          type T = T1
          def a = a1
          def b = b1
        }
    }
    

    Sample REPL session,

    scala> trait Dog
    defined trait Dog
    
    scala> val tigger = new Animal { type Species = Dog }
    tigger: Animal{type Species = Dog} = $anon$1@64bd8f9c
    
    scala> val zebedee = new Animal { type Species = Dog }
    zebedee: Animal{type Species = Dog} = $anon$1@61f2bf35
    
    scala> Bed(tigger, zebedee)
    res0: Bed{type T = Dog} = Bed$$anon$1@2b0ce330
    
    scala> val b = Bed(tigger, zebedee)
    b: Bed{type T = Dog} = Bed$$anon$1@681c81de