简体   繁体   English

Scala类型系统,通过自有类型的参数约束成员的类型

[英]Scala type system, constrain member's type by parameter of own type

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. 如果您感到好奇,我实际上尝试编写的应用程序是一个类似于Resque或rq的异步任务队列。

I have a type TaskDef[ArgsT <: AnyVal, ResultT <: AnyVal] . 我有一个类型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". 如果你很好奇, TaskDef代表“如何执行一个异步任务,它接受参数类型ArgsT和结果类型ResultT ,或者任务背后的代码”。

I'm trying to define a type TaskInst[DefT <: TaskDef] . 我正在尝试定义一个类型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代表“一个TaskDef和相关的参数来运行它,或者,一个实际的任务实例被提交到队列”。 TaskInst has two members, definition: DefT and arguments whose type I cannot write in code. TaskInst有两个成员, definition: DefT和我不能在代码中写入的类型的arguments

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 ". 在英语中,我期望的约束是:“对于给定的DefT ,其中DefT是一些TaskDef[ArgsT, ResultT]TaskInst[DefT]应该包含DefTArgsT ”。 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? 如何在Scala类型系统中表达这一点?

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. 我认为我的历史自我写作Java可能会在这一点上使用未经检查的演员表。 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. 对于一些未经检查的强制转换,这肯定是可行的,只是省略了TaskInst参数类型与嵌入式TaskDef参数类型之间的约束。 But, I do wonder whether this is something the compiler can enforce, and hopefully without too scary a syntax. 但是,我确实想知道这是否是编译器可以强制执行的,并且希望没有太可怕的语法。

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 现场演示

An add-on to the answer that @rightfold gave: @rightfold给出的答案的附加内容:

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. 我当前代码与@ rightfold示例的主要TaskDef在于TaskDef采用类型参数。 Then, when TaskInst 's declaration references TaskDef , it must provide appropriate types to the type constructor. 然后,当TaskInst的声明引用TaskDef ,它必须为类型构造函数提供适当的类型。

I initially made the mistake of passing in placeholders. 我最初错误地传递了占位符。 That is, I was writing class TaskInst[DefT <: TaskDef[_, _] . 也就是说,我正在编写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. (我不知道。也许其他人可能倾向于遵循同样的思路。所以,这只是一个警告而不是。)不要这样做,因为那时scalac会将预期解释为生成的占位符(正如您可能想象的那样,没有任何内容匹配),然后您会得到如下所示的模糊错误消息。

[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. 你最好合并,因为TaskDef的独立实例并不真正有用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM