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.
source to share
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.
source to share