我想将类型参数移动到类型成员。
这是起作用的起点:
trait Sys[S <: Sys[S]] {
type Tx
type Id <: Identifier[S#Tx]
}
trait Identifier[Tx] {
def dispose()(implicit tx: Tx): Unit
}
trait Test[S <: Sys[S]] {
def id: S#Id
def dispose()(implicit tx: S#Tx) {
id.dispose()
}
}
让我烦恼的是,我在整个图书馆中携带了一个类型参数[S <: Sys[S]]
。所以我在想的是:
trait Sys {
type S = this.type // ?
type Tx
type Id <: Identifier[S#Tx]
}
trait Identifier[Tx] {
def dispose()(implicit tx: Tx): Unit
}
trait Test[S <: Sys] {
def id: S#Id
def dispose()(implicit tx: S#Tx) {
id.dispose()
}
}
哪个失败...... S#Tx
和S#Id
变得分离:
error: could not find implicit value for parameter tx: _9.Tx
id.dispose()
^
任何使其有效的技巧或变化?
编辑:为了澄清,我主要希望修复S
中的Sys
类型以使其正常工作。在我的案例中使用路径依赖类型存在许多问题。仅举一个反映了足癣和欧文答案的例子:
trait Foo[S <: Sys] {
val s: S
def id: s.Id
def dispose()(implicit tx: s.Tx) {
id.dispose()
}
}
trait Bar[S <: Sys] {
val s: S
def id: s.Id
def foo: Foo[S]
def dispose()(implicit tx: s.Tx) {
foo.dispose()
id.dispose()
}
}
<console>:27: error: could not find implicit value for parameter tx: _106.s.Tx
foo.dispose()
^
尝试让def foo: Foo[s.type]
让您知道这无处可去。
答案 0 :(得分:2)
以下是编译的Test
版本:
trait Test[S <: Sys] {
val s : S
def id: s.Id
def dispose()(implicit tx: s.Tx) {
id.dispose()
}
}
你绝对正确地说“S#Tx和S#Id在某种程度上脱离了”。根据我的理解,你无法保证在两个S中它们实际上是同一类型。
答案 1 :(得分:2)
这不是对pedrofurla答案的评论的答案;我认为是正确的。让我解释一下原因。
Scala有一个有趣的事情,当你编写一个类的类型成员时,它实际上创建了两个不同的名称,其中一个属于该类,另一个属于该类的对象。它们之间有一些联系,即对象成员类型必须是类成员类型的子类型,但根据我的经验,你很少想要使用这个连接;大多数时候你应该把它们视为完全不同的东西。
你真正想要做的是打包两种类型,这样你就可以给它们起一个名字。所以我会写Sys
像:
trait Sys {
type Tx
type Id <: Identifier[Tx]
}
因为这确切地说明了你想做什么,没有魔法或绒毛:创造一种对象,每种对象存储两件事,而那些东西是类型(并且它们之间有一些约束)。
然后你可以像pedrofurla建议的那样写Test
:
trait Test {
val s: Sys
def id: s.Id
def dispose()(implicit tx: s.Tx) {
id.dispose()(tx)
}
}
同样,只有你需要的东西而且没有额外的东西:要创建Test
的实例,你必须提供Sys
,以及Sys
的实例将包含Test
需要使用的类型。
换句话说,有时只是将类型视为常规旧值来打包和传递。
修改强>:
可伸缩性(至少在你的例子中,可能还有其他我没有想到的)如果你再次坚持你需要的东西,那应该不是问题。在Foo
/ Bar
示例中
// This is normal; nothing unexpected.
trait Foo {
val s: Sys
def id: s.Id
def dispose()(implicit tx: s.Tx) {
id.dispose()
}
}
trait Bar { self =>
val s: Sys
def id: s.Id
// Now here's the key!
val foo: Foo { val s: Sys { type Tx = self.s.Tx } }
def dispose()(implicit tx: s.Tx) {
foo.dispose()
id.dispose()
}
}
在这里,我们对foo
的真正期望是s.Tx
与我们的 s.Tx
相同,因为我们想要做的就是使用他们可以互换。所以,我们只是要求它,它没有任何问题编译。
答案 2 :(得分:1)
虽然这不能解答您的问题(确保对现有代码进行最少的修改),但这是一个想法:
而不是Tx
类型是Sys
的成员,而是在Identifier
中使用,我作为起点,将其作为Sys
的参数,并确保Id <: Identifier
和S <: Sys
以相同的方式使用它,如下所示:
trait Sys[Tx] {
type S <: Sys[Tx]
type Id <: Identifier[Tx]
}
trait Identifier[Tx] {
def dispose()(implicit tx: Tx): Unit
}
trait Test[Tx, S <: Sys[Tx]] {
def id: S#Id
def dispose()(implicit tx: Tx) = id.dispose()
}
这在你的动机方面几乎没有改进(Sys
仍然有一个类型参数),但我的下一步是将Tx
转换为类型成员。我可以使其工作的唯一方法是,不使用任何类型的val s: S
技巧(以及基于它的类型)是:
Sys
分为两个特征,引入OuterSys
作为Tx
类型的持有者以及其他所有内容(Sys
和Identifier
作为内部特征),以及保留Sys
以便为你做的其他事情Test
个特征属于OuterSys
以下是代码:
trait OuterSys {
type Tx
type S <: Sys
type Id <: Identifier
trait Sys {
}
trait Identifier {
def dispose()(implicit tx: Tx): Unit
}
trait Test {
def id: Id
def dispose()(implicit tx: Tx) = id.dispose()
}
}
所以虽然没有真正回答你的问题或解决你的问题,但我希望它至少可以让你们知道如何解决这个问题。我试过的其他所有东西都回到了我的身边,编译器大喊一些S
的实例,并期待一个基于它的类型。
修改强>:
不需要拆分Sys
:
trait Sys {
type Tx
type Id <: Identifier
trait Identifier {
def dispose()(implicit tx: Tx): Unit
}
trait Test {
def id: Id
def dispose()(implicit tx: Tx) = id.dispose()
}
}
同样忽略了显而易见的 - 类型依赖于Sys
实例,我认为这是有意义的(系统之间没有共享标识符?事务可能?)。
无需在Sys
实例中进行“测试”,也不再需要type S <: Sys
(以及MySystem中的type S = this.type
):
object MySystem extends Sys {
type Tx = MyTransaction
type Id = MyIdentifier
class MyTransaction (...)
class MyIdentifier (...) extends Identifier {
def dispose()(implicit tx: MySystem.Tx) {}
}
}
object MyOuterTest {
{
def id: MySystem.Id = new MySystem.MyIdentifier(...)
def dispose()(implicit tx: MySystem.Tx) {
id.dispose()
}
}
答案 3 :(得分:1)
我有2个版本可以编译,但是我不完全确定你在库中寻找的是什么。 (编辑:此版本本质上存在缺陷,请参阅评论)。这里我们从Sys中完全删除类型参数S,并继续使用类型投影(与路径相关的类型)。
trait Sys {
type Tx
type Id <: Identifier[Sys#Tx]
}
trait Identifier[Tx] {
def dispose()(implicit tx: Tx)
}
trait Test[S <: Sys] {
def id: S#Id
def dispose()(implicit tx: S#Tx) {
id.dispose()(tx)
}
}
在这个版本中,我们将type参数转换为类型成员(我不完全确定这是正确的翻译),然后使用类型细化和类型投影的组合来确保Test中的正确类型。 / p>
trait Sys {
type S <: Sys
type Tx
type Id <: Identifier[S#Tx]
}
trait Identifier[Tx] {
def dispose()(implicit tx: Tx)
}
trait Test[A <: Sys {type S = A}] {
def id: A#Id
def dispose()(implicit tx: A#S#Tx) {
id.dispose()
}
}
另请注意,我们必须使用A#S#Tx
作为隐式参数的类型投影,这有望解释为什么S#Id
和S#Tx
变为“分离”的原因。实际上,它们没有分离,声明type S = this.type
使S
成为单例类型,然后使S#T
成为路径依赖类型。
为了更清楚,给定val a: A {type B}
,a.A
是a.type#A
的简写。即S#T
实际上是this.type#T
,这也是为什么简单地声明def dispose()(implicit tx: S#S#T)
无效,因为S#S#T
是一种类型投影,而不是所需的路径依赖类型,如上所示在需要val s: S
编译的答案中。
修改强>: 您可以按如下方式删除Test上的参数:
trait Test {
type A <: Sys {type S = A}
def id: A#Id
def dispose()(implicit tx: A#S#Tx) {
id.dispose()
}
}
但是,这可能需要修改很多源代码。
无论您使用类型参数还是类型成员,指定类型都不会消失,而不会重新处理类型在库中的工作方式。即,类型参数和抽象类型成员是等价的,因此您似乎不能完全摆脱类型S <: Sys[S]
。
EDIT2 :如果不使用路径依赖类型或类似Duduk的答案,这似乎是不可能的。以下是对我已经提供的内容的略微修改,以避免传递val s: S
,但它可能无法在您的库中使用,因为它需要将Identifier[Tx]
更改为类型成员和def id: S#Id
到val
以暴露路径依赖类型:
trait Sys {self =>
type Tx
type Id <: Identifier {type Tx = self.Tx}
}
trait Identifier {
type Tx
def dispose()(implicit tx: Tx)
}
trait Test[S <: Sys] {
val id: S#Id
def dispose()(implicit tx: id.Tx) {
id.dispose()(tx)
}
}