pathikrit
pathikrit

Reputation: 33409

Scala Dependent type does not compile

This code should compile in Scala:

trait Pipe {
  type Input
  type Output
  def apply(input: Input): Output
}

object Pipe {
  trait Start extends Pipe {
    override type Input = Seq[String]
  }

  abstract class Connect(val prev: Pipe) extends Pipe {
    override type Input = prev.Output
  }
}

object Pipe1 extends Pipe.Start {
  override type Output = Int
  override def apply(input: Input): Output = 
   input.length
}

object Pipe2 extends Pipe.Connect(prev = Pipe1) {
  override type Output = Boolean
  override def apply(input: Input): Output = 
   input%2 == 0
}

Pipe1 compiles fine but Pipe2 fails to compile with:

value % is not a member of Pipe2.this.Input
     input%2 == 0
          ^

I know I can solve this with generics instead of dependent types but this should work as Pipe2.Input should typecheck to be Int from Pipe1.Output

Upvotes: 4

Views: 137

Answers (2)

pathikrit
pathikrit

Reputation: 33409

@Andrey-Tyukin's answer works above. I also found this work around:

trait Pipe {
  type Input
  type Output
  def apply(input: Input): Output
}

object Pipe {
  trait Start extends Pipe {
    override type Input = Seq[String]
  }

  abstract class Connect[O](val prev: Pipe.Emitting[O]) extends Pipe {
    override type Input = O
  }

  type Emitting[O] = Pipe {type Output = O}
}

object Pipe1 extends Pipe.Start {
  override type Output = Int
  override def apply(input: Input): Output = 
   input.length
}

object Pipe2 extends Pipe.Connect(prev = Pipe1) {
  override type Output = Boolean
  override def apply(input: Input): Output = 
   input%2 == 0
}

Upvotes: 2

Andrey Tyukin
Andrey Tyukin

Reputation: 44908

The prev = Pipe thing in the invocation of the constructor is not a proper path, the compiler cannot tie any type information to that, so you end up with a rather useless prev.Output =:= Input for some indeterminate prev: Pipe which has been set to something in the constructor.

With a minimal change, it works as expected:

trait Pipe {
  type Input
  type Output
  def apply(input: Input): Output
}

object Pipe {
  trait Start extends Pipe {
    override type Input = Seq[String]
  }

  abstract class Connect extends Pipe {
    val prev: Pipe
    override type Input = prev.Output
  }

}

object Pipe1 extends Pipe.Start {
  override type Output = Int
  override def apply(input: Input): Output = 
    input.length
}

object Pipe2 extends Pipe.Connect {
  val prev = Pipe1
  override type Output = Boolean
  override def apply(input: Input): Output = input % 2 == 0
}

That's why it's called path dependent (not member dependent, not value dependent etc.).

Upvotes: 6

Related Questions