通过类型成员而不是类型参数进行F-限制量化?

时间:2013-01-08 23:04:15

标签: scala types type-projection bounded-quantification

我想将类型参数移动到类型成员。

这是起作用的起点:

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#TxS#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]让您知道这无处可去。

4 个答案:

答案 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 <: IdentifierS <: 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类型的持有者以及其他所有内容(SysIdentifier作为内部特征),以及保留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#IdS#Tx变为“分离”的原因。实际上,它们没有分离,声明type S = this.type使S成为单例类型,然后使S#T成为路径依赖类型。

为了更清楚,给定val a: A {type B}a.Aa.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#Idval以暴露路径依赖类型:

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)
  }
}