简体   繁体   English

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

[英]F-bounded quantification through type member instead of type parameter?

I would like to move a type parameter to a type member. 我想将类型参数移动到类型成员。

This is the starting point which works: 这是起作用的起点:

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

What annoys me is that I'm carrying around a type parameter [S <: Sys[S]] throughout my entire libraries. 让我烦恼的是,我在整个图书馆里都带着一个类型参数[S <: Sys[S]] So what I was thinking is this: 所以我在想的是:

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

Which fails... S#Tx and S#Id became somehow detached: 哪个失败了...... S#TxS#Id在某种程度上分离了:

error: could not find implicit value for parameter tx: _9.Tx
               id.dispose()
                         ^

Any tricks or changes that make it work? 任何使其有效的技巧或变化?


EDIT : To clarify, I am primarily hoping to fix the type S in Sys to make it work. 编辑 :为了澄清,我主要希望在Sys修复类型S以使其工作。 There are numerous problems in my case using path-dependent types. 在我的案例中使用路径依赖类型存在许多问题。 To give just one example which reflects the answers of pedrofuria and 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()
                          ^

Try to make that def foo: Foo[s.type] to give you an idea that this leads nowhere. 尝试制作那个def foo: Foo[s.type]让你知道这无处可去。

Here is a version of Test that compiles: 这是一个编译的Test版本:

trait Test[S <: Sys] {
  val s : S
  def id: s.Id
  def dispose()(implicit tx: s.Tx) {
    id.dispose()
  }
}

You absolutely right in saying "S#Tx and S#Id became somehow detached". 你完全正确地说“S#Tx和S#Id在某种程度上脱离了”。 You can't guarantee that in both S's they are actually the same type, as I understand. 根据我的理解,你无法保证在两个S中它们实际上是同一类型。

This is not so much an answer as a comment on pedrofurla's answer; 这不是对pedrofurla答案的评论的答案; which I think is correct. 我认为是正确的。 Let me explain why. 让我解释一下原因。

Scala has this funny thing where, when you write a type member of a class, it essentially creates two different names, one of which belongs to the class, and the other of which belongs to objects of that class. Scala有一个有趣的事情,当你编写一个类的类型成员时,它实际上创建了两个不同的名称,其中一个属于该类,另一个属于该类的对象。 There is some connection between them, namely that the object member type has to be a subtype of the class member type, but in my experience you very rarely want to use this connection; 它们之间有一些联系,即对象成员类型必须是类成员类型的子类型,但根据我的经验,你很少想要使用这个连接; most of the time you should think of them as entirely separate things. 大多数时候你应该把它们视为完全不同的东西。

What you really wanted to do here is package up two types so that you can give a name to the pair of them. 你真正想要做的是打包两种类型,这样你就可以给它们一个名字。 So I would write Sys like: 所以我会写Sys像:

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

because that says exactly what you want to do, with no magic or fluff: create a type of objects, each of which stores two things, and those things are types (and have some constraints between them). 因为这确切地说明了你想要做什么,没有魔法或绒毛:创造一种对象,每种对象存储两件事,而那些东西是类型(并且它们之间有一些约束)。

Then you can write Test the way pedrofurla suggestes: 然后你可以写下Test pedrofurla建议的方式:

trait Test {
    val s: Sys
    def id: s.Id
    def dispose()(implicit tx: s.Tx) {
        id.dispose()(tx)
    }
}

Again, only what you need and nothing extra: to create an instance of Test , you must supply a Sys , and that instance of Sys will contain the types that Test needs to work with. 同样,只有你需要什么,没有什么多余的:创建实例Test ,你必须提供一个Sys ,以及该实例 Sys将包含该类型的Test需要一起工作。

In other words, sometimes just think of types as regular old values to be packaged up and passed around. 换句话说,有时只是将类型视为常规旧值来打包和传递。


edit : 编辑

Scalability (at least in your example, there may be others I haven't thought of) should not be a problem if you again stick to exactly what you need. 可伸缩性(至少在你的例子中,可能有其他我没有想到的)如果你再次坚持你需要的东西,那应该不是问题。 In your Foo / Bar example, 在你的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()
    }
}

Here, what we really desire of our foo is that it's s.Tx is the same as our s.Tx , because what we want to do is use them interchangeably. 在这里,我们真正渴望我们的foo是它的s.Tx我们的 s.Tx相同,因为我们想要做的是交替使用它们。 So, we just require exactly that, and it compiles with no problems. 所以,我们只是要求它,它编译没有任何问题。

Although this doesn't answer your question (ensuring minimal modification of existing code), here's a thought: 虽然这不能解答您的问题(确保对现有代码进行最少的修改),但这里有一个想法:

Instead of Tx type being a member of Sys , and being used in Identifier , I would, as a starting point, make it a parameter of Sys , and ensure it is being used in the same way by both Id <: Identifier and S <: Sys , like this: 我不是将Tx类型作为Sys的成员,而是在Identifier中使用,我将作为一个起点,使其成为Sys的参数,并确保Id <: IdentifierS <: Sys以相同的方式使用它。 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()
    }

This is hardly an improvement in respect to your motivation ( Sys still has a type parameter), but my next step would be to convert Tx to type member. 这在你的动机方面几乎没有改进( Sys仍有类型参数),但我的下一步是将Tx转换为类型成员。 The only way I could make it work however, without using any sort of val s: S trickery (and types based on it) is to: 然而,我可以使它工作的唯一方法,不使用任何类型的val s: S技巧(和基于它的类型)是:

  • Split Sys into two traits, introducing OuterSys as a holder of Tx type and everything else ( Sys and Identifier as inner traits), and retaining Sys for whatever else it is doing for you Sys拆分为两个特征,引入OuterSys作为Tx类型的持有者和其他所有东西( SysIdentifier作为内部特征),并保留Sys以用于其他任何为您做的事情
  • Have Test trait belong to OuterSys Test特征属于OuterSys

Here's the code: 这是代码:

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

So although not really answering your question, or solving your problem, I was hoping it might at least give you guys some idea how to pull this through. 因此,虽然没有真正回答你的问题,或解决你的问题,但我希望它至少可以让你们知道如何解决这个问题。 Everything else I tried came back at me with compiler shouting for some instance of S and expecting a type based on it. 我试过的其他所有东西都回到了我的身边,编译器为S某个实例大喊大叫,并期待一个基于它的类型。


EDIT : No real need for splitting Sys : 编辑 :没有真正需要分裂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()
        }
    }

Also neglected to mention the obvious - that types depend on Sys instance, which I guess makes sense (no sharing of identifiers between systems? transactions maybe?). 也忽略了显而易见的 - 那些类型依赖于Sys实例,我认为这是有意义的(系统之间没有共享标识符?事务可能?)。

No need to "test" from within Sys instance either, and no need for type S <: Sys any more (and type S = this.type in MySystem): 无需在Sys实例中“测试”,也不需要type S <: Sys (并在MySystem中type S = this.type 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()
        }
    }

I have 2 versions that compile, however I'm not entirely sure either is what you are looking for in your library. 我有2个版本可以编译,但我不完全确定你在库中找到的是什么。 ( EDIT : This version is inherently flawed, see comments). 编辑 :这个版本本质上是有缺陷的,请参阅评论)。 Here we remove the type parameter S completely from Sys, and continue to use type projections (vs. path dependent types). 这里我们从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)
  }
}

In this version, we convert the type parameter to a type member (I'm not entirely sure this is the correct translation), and then use a combination of type refinement and type projections to assure the correct type in Test. 在这个版本中,我们将type参数转换为类型成员(我不完全确定这是正确的翻译),然后使用类型细化和类型投影的组合来确保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()
  }
}

Also notice that we have to use A#S#Tx as our type projection for the implicit parameter, which hopefully sheds some light into why S#Id and S#Tx become "detached." 还要注意我们必须使用A#S#Tx作为隐式参数的类型投影,这有望解释为什么S#IdS#Tx变得“分离”。 In reality, they aren't detached, declaring type S = this.type makes S a singleton type, which then makes S#T a path dependent type. 实际上,它们没有分离,声明type S = this.type使S成为单例类型,然后使S#T成为路径依赖类型。

To be more clear, given val a: A {type B} , aA is shorthand for a.type#A . 更清楚的是,给定val a: A {type B}aAa.type#A简写。 Ie S#T is really this.type#T , which is also why simply declaring def dispose()(implicit tx: S#S#T) will not work, because S#S#T is a type projection, not a path dependent type as desired, as exemplified above in the answers that required a val s: S to compile. S#T真的是this.type#T ,这也是为什么简单地声明def dispose()(implicit tx: S#S#T)将不起作用,因为S#S#T是一种类型投影,而不是路径所需的依赖类型,如上面在需要val s: S编译的答案中所例示的。

EDIT : You can remove the parameter on Test as follows: 编辑 :您可以删除测试上的参数,如下所示:

trait Test {
  type A <: Sys {type S = A}
  def id: A#Id
  def dispose()(implicit tx: A#S#Tx) {
    id.dispose()
  }
}

However this might require a lot of source code modification. 但是,这可能需要修改很多源代码。

Regardless of if you use type parameters or type members, specifying the type won't just disappear without reworking how types work in your library. 无论您是使用类型参数还是类型成员,指定类型都不会消失而不会重新处理类型在库中的工作方式。 Ie, type parameters and abstract type members are equivalent, so it doesn't seem that you can get rid of the type S <: Sys[S] entirely. 即,类型参数和抽象类型成员是等价的,所以似乎你不能完全摆脱S <: Sys[S]类型。

EDIT2 : Without using path-dependent types or something along the lines of Duduk's answer, this doesn't seem to be possible. 编辑2 :没有使用路径依赖类型或类似Duduk的答案,这似乎是不可能的。 Here is a slight modification to what I already gave that avoids passing around val s: S , however it may not be use-able in your library as it requires changing Identifier[Tx] to a type member and def id: S#Id to a val in order to expose the path dependent type: 这是对我已经给出的稍微修改,避免传递val s: S ,但是它可能在您的库中无法使用,因为它需要将Identifier[Tx]更改为类型成员并且def id: S#Id to 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)
  }
}

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

相关问题 Scala将递归有界类型参数(F-bounded)转换为类型成员 - Scala converting recursively bounded type parameter (F-bounded) to type member F界多态性中子类型的Scala重写类型参数 - Scala rewriting type parameter of sub type in F-bounded polymorphism 尝试将F界多态性建模为Scala中的类型成员 - Attempting to model F-bounded polymorphism as a type member in Scala Scala F界类型多态性 - Scala F-Bounded Type Polymorphism F边界类型泛型与抽象类型 - F-bounded type generic vs abstract type 不能使用类型投影到递归(f边界)类型 - Can't use a type projection to a recursive (f-bounded) type 使F界多态性可用于具有类型参数的基本特征? - Getting F-bounded polymorphism to work on a base trait with type parameters? F-bounded类型和方法,在参数和返回站点具有类型参数 - F-bounded types and methods with type parameters at argument and return sites 为什么 f 有界多态性 im Scala 通常使用上限类型和自类型来实现 - Why is f-bounded polymorphism im Scala commonly implemented with an upper type bound as well as a self type 对于返回电流类型问题,F 有界多态性相对于类型类的优势 - Advantages of F-bounded polymorphism over typeclass for return-current-type problem
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM