3

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

这是有效的起点:

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()
                         ^

任何使它起作用的技巧或改变?


编辑:为了澄清,我主要希望修复类型SSys使其工作。在我的案例中,使用路径相关类型存在许多问题。仅举一个反映 pedrofuria 和 Owen 答案的例子:

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

4 回答 4

2

这与其说是一个答案,不如说是对 pedrofurla 答案的评论;我认为这是正确的。让我解释一下为什么。

Scala 有一个有趣的地方,当你编写一个类的类型成员时,它实际上创建了两个不同的名称,一个属于该类,另一个属于该类的对象。它们之间有一些联系,即对象成员类型必须是类成员类型的子类型,但根据我的经验,您很少想要使用这种联系;大多数时候,您应该将它们视为完全独立的事物。

您在这里真正想要做的是打包两种类型,以便您可以为它们对命名。所以我会这样写Sys

trait Sys {
    type Tx
    type Id <: Identifier[Tx]
}

因为这准确地说明了您想要做什么,没有魔法或绒毛:创建一种对象,每个对象存储两个东西,这些东西是类型(并且它们之间有一些约束)。

然后您可以编写Testpedrofurla 建议的方式:

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

在这里,我们真正希望 our与our相同,因为我们想要做的是互换使用它们foo。所以,我们只需要它,它编译没有问题。s.Tx s.Tx

于 2013-01-09T00:10:41.483 回答
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 中它们实际上是同一类型。

于 2013-01-08T23:57:30.063 回答
1

虽然这不能回答您的问题(确保对现有代码的最小修改),但这里有一个想法:

type不是Tx的成员Sys并被用于Identifier,而是作为起点,将其设为 的参数Sys,并确保它以相同的方式被Id <: Identifierand使用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的持有者(以及作为内在特征)引入,并保留它为你做的任何其他事情TxSysIdentifierSys
  • 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 <: Systype S = this.type在 MySystem 中):

    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()
        }
    }
于 2013-01-09T01:55:37.800 回答
1

我有 2 个可以编译的版本,但是我不完全确定这两个版本是您在库中寻找的。(编辑:这个版本本质上是有缺陷的,见评论)。这里我们将类型参数 S 完全从 Sys 中移除,并继续使用类型投影(相对于路径依赖类型)。

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

在这个版本中,我们将类型参数转换为类型成员(我不完全确定这是正确的翻译),然后使用类型细化和类型投影的组合来确保 Test 中的类型正确。

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一个路径依赖类型。

更清楚地说, givenval a: A {type B}a.A的简写a.type#A。即S#T真的this.type#T,这也是为什么简单地声明def dispose()(implicit tx: S#S#T)不起作用的原因,因为S#S#T它是类型投影,而不是所需的路径依赖类型,如上面需要 aval 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#Idaval以公开路径相关类型:

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)
  }
}
于 2013-01-09T09:33:45.397 回答