gpt4 book ai didi

scala - Scala:参数化类型只是语法糖(如Odersky所建议)吗?

转载 作者:行者123 更新时间:2023-12-04 15:52:56 28 4
gpt4 key购买 nike

为了更深入地了解Scala类型系统,我发现了Martin Odersky的这个(旧)演示:

https://www.youtube.com/watch?v=ecekSCX3B4Q&t=3747s

大约在这部电影的时间[1:00:00],马丁解释说Scala中的参数化类型实际上只是语法糖,您可以完全重写代码,用抽象类型替换它们。作为此翻译的副作用,我们对“类型差异”进行了严格的解释。哇。听起来很不错,整个故事与我在代码中发现的一些问题相对应,所以我立即开始进行实验。但是,您猜怎么着-这种转换无法按预期进行。或我做错了什么。

这是我用来隔离问题的一小段代码:

import java.net.URL

trait MessagingClient[BrokerLocation] {
def connect(broker: BrokerLocation)
def sendMessage(targetNodeAddress: Long, msg: Any)
}

class KafkaMessaging extends MessagingClient[URL] {
override def connect(broker: URL): Unit = ???
override def sendMessage(targetNodeAddress: Long, msg: Any): Unit = ???
}

class ClusterNode[BrokerLocation](messagingClient: MessagingClient[BrokerLocation]) {
def startNode(brokerLocation: BrokerLocation): Unit = {
messagingClient.connect(brokerLocation)
}
}

object Test {
def main(args: Array[String]): Unit = {
val messagingClient = new KafkaMessaging
val clusterNode = new ClusterNode[URL](messagingClient)
val brokerLocation = new URL("http://1.2.3.4:666")
clusterNode.startNode(brokerLocation)
}
}


上面的代码编译没有问题。现在,这是我消除参数化类型的首次尝试:

import java.net.URL

trait MessagingClient {
type BrokerLocation
def connect(broker: BrokerLocation)
def sendMessage(targetNodeAddress: Long, msg: Any)
}

class KafkaMessaging extends MessagingClient {
override type BrokerLocation = URL
override def connect(broker: URL): Unit = ???
override def sendMessage(targetNodeAddress: Long, msg: Any): Unit = ???
}

class ClusterNode(val messagingClient: MessagingClient) {
def startNode(brokerLocation: messagingClient.BrokerLocation): Unit = {
messagingClient.connect(brokerLocation)
}
}

object Test {
def main(args: Array[String]): Unit = {
val messagingClient = new KafkaMessaging
val clusterNode = new ClusterNode(messagingClient)
val brokerLocation = new URL("http://1.2.3.4:666")
clusterNode.startNode(brokerLocation)
}
}


但是,此尝试不起作用。 Typechecker似乎拥有批准打字所需要的所有信息,但是它抱怨这一行:

clusterNode.startNode(brokerLocation)


尝试失败后,我决定执行更严格的转换,即在先前参数化的每个类中引入抽象类型。令人惊讶的是,此尝试也未能编译:

import java.net.URL

trait MessagingClient {
type BrokerLocation
def connect(broker: BrokerLocation)
def sendMessage(targetNodeAddress: Long, msg: Any)
}

class KafkaMessaging extends MessagingClient {
override type BrokerLocation = URL
override def connect(broker: URL): Unit = ???
override def sendMessage(targetNodeAddress: Long, msg: Any): Unit = ???
}

class ClusterNode(val messagingClient: MessagingClient) {
type BrokerLocation = messagingClient.BrokerLocation
def startNode(brokerLocation: BrokerLocation): Unit = {
messagingClient.connect(brokerLocation)
}
}

object Test {
def main(args: Array[String]): Unit = {
val messagingClient: MessagingClient = new KafkaMessaging
val clusterNode = new ClusterNode(messagingClient) {type BrokerLocation = URL}
val brokerLocation = new URL("http://1.2.3.4:666")
clusterNode.startNode(brokerLocation)
}
}


现在-错误从哪里来?我还试图找到参数化类型和抽象类型之间的全部等效性的“根本”解释,但是我很难在Scala语言规范中找到它。也许有些人已经设法调查了这个问题....

编辑
(作为对安德烈的长期调查之后的补充...相当长的评论...并没有改变原始问题,而是为问题提供了更多启示)

再次感谢Andrey对这个主题的广泛研究。我还花了一些时间分析我所知道的并遵循您的提示。

首先是技术问题:实际上,我实际上将您解决方案的最新版本('Cluster3')粘贴到了一起,不幸的是它没有编译。花了一些时间来改善您的想法以解决问题。看:

abstract class MessagingClient {
type BrokerLocation
def connect(b: BrokerLocation): Unit
}

class KafkaMessaging extends MessagingClient {
override type BrokerLocation = URL
override def connect(broker: URL): Unit = ???
}

abstract class ClusterNode3 {
val msgClient: MessagingClient
type BrokerLocation = msgClient.BrokerLocation
def connect(i: BrokerLocation): Unit =
msgClient.connect(i)
}

object AndreySolution {
def main(args: Array[String]): Unit = {
val messagingClient = new KafkaMessaging

//your original solution - unfortunately leads to compilation error in last line
//val clusterNode = wrapMessagingClientIntoClusterNode3_param(messagingClient)

//naive attempt to solve the problem by adding type annotation - does not help, really
//val clusterNode: ClusterNode3 {type BlokerLocation = URL} = wrapMessagingClientIntoClusterNode3_param(messagingClient)

//... but this actually works
//val clusterNode: ClusterNode3 {val msgClient: KafkaMessaging} = new ClusterNode3 {val msgClient = messagingClient}

//...and this also works - using the 'smarter' wrapping approach
val clusterNode = wrapMessagingClient_byWojciech(messagingClient)

val brokerLocation = new URL("http://1.2.3.4:666")
clusterNode.connect(brokerLocation)
}

def wrapMessagingClientIntoClusterNode3_param[I](p: MessagingClient { type BrokerLocation = I} ): ClusterNode3 { type BrokerLocation = I } =
new ClusterNode3 {
val msgClient = p
}

def wrapMessagingClient_byWojciech[T <: MessagingClient](p: T): ClusterNode3 {val msgClient: T} = new ClusterNode3 {val msgClient = p}

}


现在-最后的想法。我在这里认识到两个单独的(但相互联系的)问题:


问题1(=我的原始问题)参数化类型可以理解为抽象类型的语法糖吗? -仍然不确定,特别是因为在我们的解决方案代码中,我们显然又陷入了使用参数化类型的问题,这种用法感到至关重要(实际上很有趣)
问题2:在同一源代码中组合依赖路径的类型和抽象类型可能比人们预期的要复杂得多。在花了几个小时试图找到解决方法之后,我仍然觉得这是“在冰上行走”。到目前为止,我还没有一个明确的配方,当我将抽象类型与路径混合时,哪种编码模式是安全的,哪些不是安全的。


请看一下这段代码(来自我漫长的实验马拉松的相当随机的示例):

object Fancy {
val fooWithInt = new Foo {type A = Int; val numberA = 1; type B = String; val numberB = "42"}
val boxWithFooWithInt = new Box {type C = Int; val secret1 = fooWithInt; val secret2 = "bingo"}
val surprise: Int = boxWithFooWithInt.secret1.numberA
val secret: String = boxWithFooWithInt.secret2
}

trait Foo {
type A
type B
val numberA: A
val numberB: B
}

trait Box {
type C
type D = secret1.B
val secret1: Foo {type A = C}
val secret2: D
}


从人类的角度来看,此代码中的类型是正确的。现在尝试猜测-Scala编译器会快乐还是不快乐?

因此,事实证明Intellij抱怨类型错误,但是Scala编译器确认这次一切都很好。对我来说,在解决路径依赖类型理论中的类型方程组时,编译器的推理能达到多远仍然是一种乐透。

最有可能获得我所面临问题的最终答案,应该研究当前Scala编译器实现的实际类型理论。我现在也很好奇Dotty将如何处理我的示例(也许我可以花时间在Dotty beta上对其进行测试)。

最佳答案

请注意,这样的声明

class Foo[A](arg: Bar[A])


确保 Foo的第一类型参数和 Bar的第一类型参数
ClusterNode相同。在您的原始代码中,此行

class ClusterNode[BrokerLocation](messagingClient: MessagingClient[BrokerLocation])


确保 messagingClient和注入的 BrokerLocation同意相同的 BrokerLocation类型,此外,作为 ClusterNode[BrokerLocation]类型的一部分,从外部可以看到 ClusterNode的类型。

在您的第一次尝试中, messagingClient甚至没有
抽象类型成员,因此有关类型成员的信息
messagesClient立即丢失。

在第二次尝试中,您写的大致对应于

class ClusterNode(val messagingClient: MessagingClient[_ <: Any]) {
// once the type parameter of `messagingClient` is forgotten,
// use `BrokerLocation` typedef to publish the absent
// information about `messagingClient`s type parameter.
type BrokerLocation = Any
}


在类型参数语言中。也就是说,
ClusterNode类型和 (*)类型再次丢失。

现在考虑以下代码:

import java.net.URL

trait MessagingClient {
type BrokerLocation
def connect(broker: BrokerLocation)
def sendMessage(targetNodeAddress: Long, msg: Any)
}

class KafkaMessaging extends MessagingClient {
type BrokerLocation = URL
override def connect(broker: URL): Unit = ???
override def sendMessage(targetNodeAddress: Long, msg: Any): Unit = ???
}

abstract class ClusterNode { self =>
type BrokerLocation // type is declared

// (*) Coherence is enforced
val messagingClient: MessagingClient { type BrokerLocation = self.BrokerLocation }

def startNode(brokerLocation: BrokerLocation): Unit = {
messagingClient.connect(brokerLocation)
}
}

object Test {
def main(args: Array[String]): Unit = {
val msgCl = new KafkaMessaging
// (**)
val clusterNode = new ClusterNode {
type BrokerLocation = URL
val messagingClient = msgCl
}
val brokerLocation = new URL("http://1.2.3.4:666")
clusterNode.startNode(brokerLocation)
}
}


查看带有 ClusterNode标记的行周围的代码。
在其上方的代码行中,我们声明 BrokerLocation有一些
抽象类型成员称为 messagingClient,在其下面的行中,
我们强制 MessagingClient是带有
兼容的抽象成员类型。这样做后,摘要
类型成员不会丢失,并且 (**)之后的行中的代码
即使实例化更多,编译仍按预期进行
麻烦的

编辑:显然,仍不清楚为什么“第二次尝试”
丢失有关 BrokerLocationMessagingClient的信息。

总体情况是这样的:有某种 BrokerLocation类型,
并且此类型应该在三个不同的地方保持一致:


ClusterNode的外部看
作为 ClusterNode的类型成员
作为包装的 MessagingClient的类型成员


ClusterNode应该以某种方式确保 BrokerLocation
在( step1 outside-to-cn)外是已知的,因此我们可以从 BrokerLocation传递 main的实例。

此外,应该以某种方式确定 BrokerLocation
ClusterNode中的与 MessagingClient中的相同( step2:cn-to-mc)。
在问题中,该链以两种不同的方式断开:


step1损坏,step2损坏
步骤1损坏,步骤2正常


在发布的第一部分中,我只是提出了一个变体,其中整个链条都可以使用,但是我也替换了第二步:


步骤1正常,步骤2再次正常(但实现方式不同)


在下面的评论中,您假设存在问题
在第二个提案中使用 step2。不是这种情况。
组合“ step1正常, step2损坏”不是问题。
问题出在用代码编写的构造函数中。
ClusterNode的构造函数泄漏类型信息,
从而破坏 step1

我想保持EDIT的可编译性,因此让我们从第二次尝试中重复定义:

abstract class MessagingClient {
type BrokerLocation
def connect(b: BrokerLocation): Unit
}

class ClusterNode(val mc: MessagingClient) {
type BrokerLocation = mc.BrokerLocation
def startNode(b: BrokerLocation) = mc.connect(b)
}


忘记
声明的所有成员类型和方法
ClusterNode的主体片刻,然后看一下
ClusterNode
ClusterNode构造函数的签名告诉您什么?
它告诉您它接受任何类型的 MessagingClient,无论
BrokerLocation的类型。什么都没有
ClusterNode的声明使我们无法编写以下代码:

def wrapMessagingClientIntoClusterNode(p: MessagingClient)
: ClusterNode = new ClusterNode(p)


只看声明的第一行。
此方法签名中的任何内容均不能阻止您通过
各种消息传递客户端,并且没有类型信息流
从第一行到第二行。

编译器如何恢复有关类型的任何有用信息
BrokerLocation中的 MessagingClient传递给
ClusterNode?它无法恢复类型。而且不会。
一旦调用构造函数,类型信息就会丢失。
ClusterNode主体中没有大量的类型声明和方法
可以恢复此信息。

我们能否以某种方式将更多信息附加到 ClusterNode,以便类型为
保存?
好,是的,但是我们必须附加以下相当繁琐的结构
从外部:

def wrapMessagingClientIntoClusterNode_param[I]
(p: MessagingClient { type BrokerLocation = I } )
: ClusterNode { type BrokerLocation = I } =
(new ClusterNode(p)).asInstanceOf[ClusterNode { type BrokerLocation = I }]


注意,我们不能在这里省略 asInstanceOf部分,因为构造函数
本身会泄漏必要的类型信息,就像
wrapMessagingClientIntoClusterNode方法。

现在将其与以下定义进行比较:

abstract class ClusterNode2 { self =>
type BrokerLocation
val msgClient: MessagingClient {
type BrokerLocation = self.BrokerLocation
}
def startNode(i: BrokerLocation): Unit =
msgClient.connect(i)
}


如果您尝试以与上述相同的方式编写方法 wrapMessagingClientIntoClusterNode,会发生什么情况?

def wrapMessagingClientIntoClusterNode2(p: MessagingClient)
: ClusterNode2 = new ClusterNode2 {
type BrokerLocation = p.BrokerLocation
val msgClient = p
}


或多或少与 wrapMessagingClientIntoClusterNode方法一样无用,它也失去了
类型信息,因为我们本质上是在要求存在类型 MessagingClient[_]
在左手侧。但是这次,我们可以更轻松地更正它:

def wrapMessagingClientIntoClusterNode2_param[I]
(p: MessagingClient {type BrokerLocation = I})
: ClusterNode2 { type BrokerLocation = I } =
new ClusterNode2 {
type BrokerLocation = I
val msgClient = p
}


再次,我们引入类型参数 I,它可以隧穿类型
从参数部分到返回类型部分的信息。
它可以编译,但是这次没有 asInstanceOf,因为我们指定了
type BrokerLocation成员首先,然后构造函数将其遗忘。

现在,考虑第三个版本,该版本更接近于您尝试的版本
第二次尝试:

abstract class ClusterNode3 { self =>
val msgClient: MessagingClient
type BrokerLocation = msgClient.BrokerLocation
def connect(i: BrokerLocation): Unit =
msgClient.connect(i)
}


您可以使用相同的愚蠢错误:

def wrapMessagingClientIntoClusterNode3(p: MessagingClient)
: ClusterNode3 = new ClusterNode3 {
val msgClient = p
}


但是您也可以做正确的事情,并保留类型:

def wrapMessagingClientIntoClusterNode3_param[I]
(p: MessagingClient { type BrokerLocation = I} )
: ClusterNode3 { type BrokerLocation = I } =
new ClusterNode3 {
val msgClient = p
}


这几乎与您所做的相同。只是没有构造函数。

无论您选择 ClusterNode2还是 ClusterNode3都不那么重要,
ClusterNode3切换到 ClusterNode2
我回答的第一部分。消除“邪恶的”类型擦除构造函数
实际上更重要,因为它修复了 step1

我将尝试总结一下:在您的第二次尝试中,编译器知道 BrokerLocationMessagingClient中的 ClusterNode类型成员是相同的,但它不知道类型是什么。

我希望构造函数的角色现在可以更清楚一些。

关于scala - Scala:参数化类型只是语法糖(如Odersky所建议)吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48503473/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com