3
votes

I'm new to Scala and new to higher kinded types. I want to write something like this;

trait Actor[E[Dependency] <: Event[Dependency]] {
  def execute(dependency: Dependency): Unit
}

However I can't refer to the type parameter Dependency in the execute method - the compiler doesn't know it.

I'm aware I can solve it in the following way without HKTs, but this isn't what this question is about;

trait Actor[T <: Event[Dependency], Dependency] {
   def execute(dependency: Dependency): Unit
}

I'd like to understand why it doesn't work with the higher kinded type syntax that I've tried? Is it possible at all to express this with HKTs? Is this a valid use-case for a HKT?


EDIT

A bit more information, Event looks like this;

trait Event[Data] {
   val payload: Data
}

...and I'm looking to define an event and an actor like this;

case class FooEvent(payload: Foo) extends Event[Foo]

class FooActor extends Actor[FooEvent] {
   def execute(dependency: Foo) = {}
}
2

2 Answers

2
votes

I will try to improve Alexey's answer - he is right, but he is too short. But I must say that I'm not an expert in HKT and I think I'm only starting to understand the concept.

In your code E[Dependency] is the same as E[_] which says that you have E with some type as parameter. This means that you do not operate over Dependency as type. You also do not operate over E or E[Dependency] as the type either. E is a type constructor, and E[Dependency] is an existential type if I understood it correctly. Please note that

trait Actor[E[D] <: Event[D]] { def execute(d: E) {} }

or

trait Actor[E[D] <: Event[D]] { def execute(d: E[D]) {} }

won't compile either.

You need to specify the proper type as an argument for execute:

trait Actor[E[D] <: Event[D]] { def execute[B](d: E[B]) {} }

This one will compile as E[B] is the type in this context.

Updated:

Please take a look at this code:

  trait Event[P] {
    val payload: P
  }

  case class FooEvent(payload: Int) extends Event[Int]

  trait BaseActor {
    type E = Event[P]
    type P
    def execute(dep: P)
    def runEvent(event: E)
  }

  trait IntActor extends BaseActor {
    type P = Int
  }

  class FooActor extends IntActor {
    def execute(dependency: P) = {}
    def runEvent(event: E) = {}
  }

  val c = new FooActor()
  c.runEvent(FooEvent(5))
  c.execute(5)

Basically the trick is to define type P which is our Dependency and type E = Event[P] which is always Event[Dependency] then you can use the actor by defining P without defining E as it is already defined. Not sure whether it solves the issue, but it looks like a way to go to me. There are also too many types here, some like IntActor is not necessary. I've put them so it is easier to understand the example

1
votes

However I can't refer to the type parameter Dependency in the execute method - the compiler doesn't know it.

You can't because it isn't a parameter of Actor. Consider

val actor = new Actor[Event] // E is Event
actor.execute(???) // what argument is this supposed to take? I.e. what is Dependency for Actor[Event]?

UPDATE: Given your edit, the [Dependency, T <: Event[Dependency]] option is precisely what you need. When you write Actor[E[Dependency] <: Event[Dependency]], this means E itself has to have a type parameter. And FooEvent doesn't, so Actor[FooEvent] won't compile.

UPDATE 2: You could try using type members as follows:

trait Event {
  type Dependency
  val payload: Dependency
}

trait Actor {
  type E <: Event

  def execute(e: E#Dependency)
}

class Foo

case class FooEvent(payload: Foo) extends Event {
  type Dependency = Foo
}

class FooActor extends Actor {
  type E = FooEvent

  def execute(e: Foo) = {}
}