3

Not really sure the standard terminology here, so I'll try to describe what I'm trying to do. In case you're curious, the app 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]. In case you're curious, TaskDef represents "how to execute an asynchronous task which takes argument type ArgsT and result type ResultT, or, the code behind a task".

I'm trying to define a type TaskInst[DefT <: TaskDef]. In case you're curious, TaskInst represents "a TaskDef and associated argument to run it with, or, an actual task instance being submitted to the queue". TaskInst has two members, definition: DefT and arguments whose type I cannot write in code.

In English, my desired constraint is: "For a given DefT, where DefT is some TaskDef[ArgsT, ResultT], TaskInst[DefT] should contain a DefT and an ArgsT". That is, the argument type of the task definition should match the type of the argument given to the task.

How do I express this in the Scala type system?

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

Thanks in advance!

EDIT:

I think my historical self writing Java would probably have resorted to unchecked casts at this point. This is definitely feasible with some amount of unchecked casts and just leaving out the constraint between the type of the TaskInst's arguments vs the type of the embedded TaskDef's arguments. But, I do wonder whether this is something the compiler can enforce, and hopefully without too scary a syntax.

3
  • Not quite sure what happened here. Saw an answer, was reading it, and then saw the answer disappear. Not quite sure how or why the answer disappeared. Weird server bug? But thank you nonetheless to the answer's author! Commented Jul 17, 2015 at 8:24
  • I was editing the answer and making an SSCCE, so I deleted it in the meantime. Commented Jul 17, 2015 at 8:24
  • 1
    Oh, OK. Thank you! If it had been a server issue or similar, I was going to reproduce as much of the answer as I could remember for future readers' sake. Commented Jul 17, 2015 at 8:25

2 Answers 2

7

Define them as abstract types:

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

Then use a type projection:

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

Live Demo

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks for explaining, and thanks for clarifying the standard name for this technique!
0

An add-on to the answer that @rightfold gave:

If you are looking to use type parameters throughout, you will need to properly pass the type parameters through to the type constructors.

Excuse me, that's a bit ambiguous for me to say it that way, so let me use my current code as a concrete 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 TaskDef takes type parameters. Then, when TaskInst's declaration references TaskDef, it must provide appropriate types to the type constructor.

I initially made the mistake of passing in placeholders. That is, I was writing class TaskInst[DefT <: TaskDef[_, _]. Turns out, this doesn't mean what I thought it meant. (I don't know. Perhaps others might be inclined to follow the same line of thought. So, this is just a warning not to.) Don't do that, because then scalac will interpret the expected to mean a generated placeholder (which, as you might imagine, nothing matches), and then you get an obscure error message like 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 posting this in hopes that future readers don't fall into the same hole I did.

EDIT:

As a further addendum, now that I've tried it out for a day, my impression is that this isn't actually a good data model for asynchronous tasks. You're better off combining, since stand-alone instances of TaskDef aren't really useful.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.