Scala type system, restricts the type of a member to a parameter of its own type

Not really sure about the standard terminology, so I'll try to describe what I'm trying to do. In case you're wondering, the application I'm actually trying to write is an asynchronous task queue similar to Resque or rq.

I have a type TaskDef[ArgsT <: AnyVal, ResultT <: AnyVal]

. If you're curious, TaskDef

presents "how to perform an asynchronous task that takes an argument ArgsT

type and a result type ResultT

, or the code behind the task."

I am trying to determine the type TaskInst[DefT <: TaskDef]

. In case you're wondering, TaskInst

represents "a TaskDef

and its associated argument to run it, or the actual task instance sent to the queue." TaskInst

has two members, definition: DefT

and arguments

, whose type I cannot write in the code.

In English, my search constraint is: "For a given DefT

, where DefT

is some TaskDef[ArgsT, ResultT]

, TaskInst[DefT]

must contain DefT

and ArgsT

". That is, the type of the argument of the task definition must match the type of the argument given to the task.

How do you express this in Scala's type system?

Alternatively, am I modeling my domain incorrectly and trying to do something non-idiomatic? Would some alternative approach be more idiomatic?

Thanks in advance!

EDIT:

I think my historical self-writing Java would probably have resorted to untested throws at this point. This is definitely possible with some amount of uncontrolled throwaway and simply omitting the constraint between the type of arguments TaskInst

and the type of built-in arguments TaskDef

. But I really wonder if this could be a compiler, and hopefully without the too scary syntax.

+3


source to share


2 answers


Define them as abstract types:

trait TaskDef {
    type Arguments <: AnyVal
    type Result <: AnyVal
}

      

Then use a projection like:



trait TaskInst[DefT <: TaskDef] {
    def definition: DefT
    def arguments: DefT#Arguments
}

      

Live Demo

+7


source


Addition to the answer that @rightfold gave:

If you want to use type parameters throughout, you will need to correctly pass type parameters through type constructors.

Sorry, this is a bit ambiguous for me to say so, so let me use my current code as a specific example.

trait TaskDef[ArgT_, ResT_] {
  type ArgT = ArgT_
  type ResT = ResT_
  val name: String
  def exec(arg: ArgT): String \/ ResT
}

class TaskInst[ArgT, ResT, DefT <: TaskDef[ArgT, ResT]] (
  val id: UUID,
  val defn: DefT,
  val arg: ArgT
)

      

The main divergence of my current code from @ rightfold's example is that it TaskDef

takes type parameters. Then, when a declaration is TaskInst

referenced TaskDef

, it must provide the appropriate types to the type constructor.



I originally made a mistake in passing in placeholders. That is, I wrote class TaskInst[DefT <: TaskDef[_, _]

. Turns out that doesn't mean what I thought it meant. (I don't know, maybe others might be inclined to stick with the same idea. So this is just a warning.) Don't do this, because then it scalac

interprets the expected value as a generated placeholder (which, as you can imagine, is nothing does not match) and then you will receive an error message similar to the following.

[error] /Users/mingp/Code/scala-redis-queue/src/main/scala/io/mingp/srq/core/TaskInst.scala:8: type mismatch;
[error]  found   : TaskInst.this.arg.type (with underlying type _$1)
[error]  required: _$1
[error]   val arg: DefT#ArgT_
[error]       ^
[error] one error found

      

Just post this in the hope that future readers don't fall into the same hole as me.

EDIT:

As an added addition, now that I've tried this throughout the day, I find it actually not a good data model for asynchronous tasks. You are better off combining as standalone instances are TaskDef

not very useful.

0


source







All Articles