使用been suggested来处理重载方法的双重定义的一种方法是用模式匹配替换重载:
object Bar {
def foo(xs: Any*) = xs foreach {
case _:String => println("str")
case _:Int => println("int")
case _ => throw new UglyRuntimeException()
}
}
这种方法要求我们放弃对 foo
的参数的静态类型检查 . 能够写作会好得多
object Bar {
def foo(xs: (String or Int)*) = xs foreach {
case _: String => println("str")
case _: Int => println("int")
}
}
我可以接近 Either
,但它有两种以上类型的快速丑陋:
type or[L,R] = Either[L,R]
implicit def l2Or[L,R](l: L): L or R = Left(l)
implicit def r2Or[L,R](r: R): L or R = Right(r)
object Bar {
def foo(xs: (String or Int)*) = xs foreach {
case Left(l) => println("str")
case Right(r) => println("int")
}
}
看起来像一般(优雅,高效)的解决方案需要定义 Either3
, Either4
,....有没有人知道实现相同目的的替代解决方案?据我所知,Scala没有内置的"type disjunction" . 另外,上面定义的隐式转换是否隐藏在某个标准库中,以便我可以导入它们?
15 回答
好吧,在
Any*
的特定情况下,下面的这个技巧也会超载,这可能就是你想要的 .首先,声明一个包含您希望接受的类的类,如下所示:
接下来,声明
foo
如下:就是这样 . 您可以调用
foo(5)
或foo("abc")
,它会起作用,但请尝试foo(true)
并且它将失败 . 这可以通过创建StringOrInt[Boolean]
由客户端代码支持,除非,如下面的Randall所示,您使StringOrInt
为sealed
类 .它的工作原理是因为
T: StringOrInt
意味着有一个类型为StringOrInt[T]
的隐式参数,并且因为Scala在一个类型的伴随对象中查看是否存在implicits,以使代码要求该类型起作用 .Miles Sabin在他最近的博客文章_1697152中描述了一种非常好的方式来获得联盟类型:
他首先将类型否定定义为
使用De Morgan定律,这允许他定义联合类型
使用以下辅助构造
您可以按如下方式编写联合类型:
Dotty,一个新的实验性Scala编译器,支持联合类型(写成
A | B
),因此您可以完全按照自己的意愿执行操作:以下是Rex Kerr编码联合类型的方法 . 直接而简单!
Source: 评论#27在Miles Sabin的this优秀博客文章中提供了另一种在Scala中编码联合类型的方法 .
可以如下概括Daniel's solution:
这种方法的主要缺点是
正如丹尼尔指出的那样,它不处理混合类型的集合/变量
如果匹配不详尽,编译器不会发出警告
如果匹配包含不可能的情况,编译器不会发出错误
与
Either
方法类似,进一步的泛化需要定义类似的Or3
,Or4
等特征 . 当然,定义这些特征比定义相应的Either
类要简单得多 .Update:
Mitch Blevins demonstrates a very similar approach并展示如何将其概括为两种以上类型,将其复制为"stuttering or" .
通过将类型列表的概念与Miles Sabin's work in this area的简化相结合,我有点偶然发现n-ary联合类型的相对干净的实现,有人在另一个答案中提到 .
给定类型
¬[-A]
,它在A
上是逆变的,根据给定A <: B
的定义,我们可以写¬[B] <: ¬[A]
,反转类型的排序 .给定
A
,B
和X
类型,我们想表达X <: A || X <: B
. 应用逆变,我们得到¬[A] <: ¬[X] || ¬[B] <: ¬[X]
. 这可以表示为¬[A] with ¬[B] <: ¬[X]
,其中A
或B
之一必须是X
或X
本身的超类型(考虑函数参数) .我确实花了一些时间尝试将这个想法与成员类型的上限结合起来,如harrah/up的_1697189中所见,但是迄今为止,带有类型边界的
Map
的实现具有挑战性 .使用implicits,类型类解决方案可能是最好的方法 . 这类似于Odersky / Spoon / Venners书中提到的monoid方法:
如果你在REPL中运行它:
还有这个黑客:
见Working around type erasure ambiguities (Scala) .
我们想要一个类型操作符
Or[U,V]
,它可以用于以X <: U
或X <: V
的方式约束类型参数X
. 这是一个尽可能接近的定义:这是怎么回事用过的:
这使用了一些Scala类型的技巧 . 主要是使用generalized type constraints . 给定类型
U
和V
,当且仅当Scala编译器能够证明U
是V
的子类型时,Scala编译器才提供名为U <:< V
的类(以及该类的隐式对象) . 这是一个使用通用类型约束的简单示例,适用于某些情况:此示例适用于
X
类B
的实例,String
,或者类型既不是超类型也不是B
或String
的子类型 . 在前两种情况下,with
关键字(B with String) <: B
和(B with String) <: String
的定义是正确的,因此Scala将提供一个将作为ev
传入的隐式对象:Scala编译器将正确接受foo[B]
和foo[String]
.在最后一种情况下,我依赖的事实是,如果
U with V <: X
,那么U <: X
或V <: X
. 这看起来很直观,我只是假设它 . 从这个假设可以清楚地看出,当X
是B
或String
的超类型或子类型时,为什么这个简单示例失败:例如,在上面的示例中,foo[A]
被错误地接受并且foo[C]
被错误地拒绝 . 同样,我们想要的是变量U
,V
和X
上的某种类型表达式,恰好在X <: U
或X <: V
时为真 .斯卡拉的逆变概念在这里可以提供帮助 . 记住特质
trait Inv[-X]
?因为它的类型参数X
,Inv[X] <: Inv[Y]
是逆变的,当且仅当Y <: X
. 这意味着我们可以将上面的示例替换为实际可行的示例:那是因为表达式
(Inv[U] with Inv[V]) <: Inv[X]
是真的,通过上面的相同假设,恰好是Inv[U] <: Inv[X]
或Inv[V] <: Inv[X]
,并且通过逆变的定义,这恰好是X <: U
或X <: V
.通过声明可参数化的类型
BOrString[X]
并使用它如下所示,可以使事情更加可重用:Scala现在将尝试为
X
调用X
构造类型BOrString[X]
,并且当X
是B
或String
的子类型时,将精确构造类型 . 这有效,并且有一个简写符号 . 下面的语法是等效的(除了ev
现在必须在方法体中引用为implicitly[BOrString[X]]
而不是简单地ev
)并使用BOrString
作为type context bound:我们真正喜欢的是一种创建类型上下文绑定的灵活方法 . 类型上下文必须是可参数化的类型,我们需要一种可参数化的方法来创建一个 . 这听起来像我们试图在类型上讨论函数,就像我们在值上调整函数一样 . 换句话说,我们喜欢以下内容:
这是Scala中的not directly possible,但我们可以使用一个技巧来达到非常接近 . 这将我们带到上面
Or
的定义:这里我们使用structural typing和Scala的pound operator创建一个结构类型
Or[U,T]
,保证有一个内部类型 . 这是一个奇怪的野兽 . 要给出一些上下文,必须使用AnyRef
的子类调用函数def bar[X <: { type Y = Int }](x : X) = {}
,其中定义了类型Y
:使用pound运算符允许我们引用内部类型
Or[B, String]#pf
,并使用infix notation作为类型运算符Or
,我们得到了foo
的原始定义:我们可以使用函数类型在其第一个类型参数中是逆变的事实,以避免定义特征
Inv
:你可以看看MetaScala,它有一个叫OneOf的东西 . 我得到的印象是,这不适用于
match
语句,但您可以使用高阶函数模拟匹配 . 例如,看看this snippet,但请注意"simulated matching"部分已被注释掉,可能是因为它尚未完成 .现在进行一些编辑:我不会像你描述的那样对任何关于定义Either3,Either4等的东西感到震惊 . 这基本上是Scala内置的标准22元组类型的两倍 . 如果Scala有内置的析取类型,并且可能是一些很好的语法,如
{x, y, z}
,那肯定会很好 .我认为第一类不相交类型是一个密封的超类型,具有备用子类型,并且隐式转换为/来自所需类型的这些替代亚型的分离 .
我假设这个地址是comments 33 - 36英里萨林's solution, so the first class type that can be employed at the use site, but I didn' t测试它 .
一个问题是Scala不会在大小写匹配上下文中使用,从
IntOfIntOrString
到Int
(和StringOfIntOrString
到String
)的隐式转换,因此必须定义提取器并使用case Int(i)
而不是case i : Int
.ADD:我在他的博客上回复了Miles Sabin如下 . 也许有几个改进:
它扩展到2种以上,在使用或定义网站上没有任何额外的噪音 .
参数被隐式装箱,例如不需要
size(Left(2))
或size(Right("test"))
.模式匹配的语法是隐式取消装箱的 .
装箱和拆箱可能会被JVM热点优化掉 .
语法可能是未来的第一类联合类型所采用的语法,因此迁移可能是无缝的?也许对于联合类型名称,最好使用
V
而不是Or
,例如IntVString
,Int |v| String
,
Int or String
,或者我最喜欢的
Int|String
?
更新:对上述模式的析取的逻辑否定如下,我added an alternative (and probably more useful) pattern at Miles Sabin's blog .
另一个更新:关于Mile Sabin's solution的注释23和35,这里有一种在使用站点声明联合类型的方法 . 注意它在第一级之后是未装箱的,即它具有extensible to any number of types in the disjunction的优点,而
Either
需要嵌套装箱,而我之前的注释41中的范例不可扩展 . 换句话说,D[Int ∨ String]
可分配给D[Int ∨ String ∨ Double]
(即是其子类型) .显然Scala编译器有三个错误 .
在目标析取中的第一个类型之后,它不会为任何类型选择正确的隐式函数 .
它不会从匹配中排除
D[¬[Double]]
案例 .3 .
get方法不是't constrained properly on input type, because the compiler won' t允许
A
在协变位置 . 有人可能会认为这是一个错误,因为我们想要的只是证据,我们永远不会访问函数中的证据 . 我选择不在get
方法中测试case _
,所以我不必在size()
中的match
中取消Option
.2012年3月5日:之前的更新需要改进 . Miles Sabin's solution正确处理子类型 .
我之前更新的提议(近一流联盟类型)打破了子类型 .
问题是
(() => A) => A
中的A
出现在协变(返回类型)和逆变(函数输入,或者在这种情况下是函数的返回值,它是函数输入)位置,因此替换只能是不变的 .注意
A => Nothing
是必要的只是因为我们想要A
在逆变位置,所以A
are not subtypes的D[¬[A]]
和D[¬[A] with ¬[U]]
(see also)的超类型 . 由于我们只需要双重逆处理,即使我们可以丢弃¬
和∨
,我们也可以达到Miles的解决方案 .所以完整的解决方案是 .
请注意,Scala中的前2个错误仍然存在,但是第3个错误被避免,因为
T
现在被约束为A
的子类型 .我们可以确认子类型的工作原理 .
我一直认为第一类交集类型非常重要,对于reasons Ceylon has them,并且因为而不是subsuming到
Any
这意味着在预期类型上使用match
取消装箱会产生运行时错误,所以取消装箱(heterogeneous collection包含a)析取可以进行类型检查(Scala必须修复我记录的错误) . 对于异构集合,联合比metascala的实验更为直接 .如果你不理解库里 - 霍华德,还有另一种方法更容易理解:
我用的是technique in dijon
嗯,这一切都非常聪明,但我很确定你已经知道你的主要问题的答案是各种各样的“不” . Scala处理重载的方式不同,必须承认,比你描述的更不优雅 . 其中一些原因是由于Java互操作性,其中一些原因是由于不希望遇到类型推理算法的边缘情况,其中一些原因是它不是Haskell .
在这里添加已经很好的答案 . 这里's a gist that builds on Miles Sabin union types (and Josh'的想法)但也使它们递归定义,所以你可以在联合中有> 2种类型(
def foo[A : UNil Or Int Or String Or List[String]
)https://gist.github.com/aishfenton/2bb3bfa12e0321acfc904a71dda9bfbb
注意:我应该补充一点,在玩了上面的项目之后,我最终回到了普通的旧和类型(即带有子类的密封特性) . Miles Sabin联合类型非常适合限制类型参数,但是如果你需要返回一个联合类型,那么它就不会提供太多 .
从the docs开始,添加
sealed
:关于
sealed
部分: