如何定义“types析取”(联合types)?

一种build议处理重载方法的双重定义的方法是用模式匹配来replace重载:

object Bar { def foo(xs: Any*) = xs foreach { case _:String => println("str") case _:Int => println("int") case _ => throw new UglyRuntimeException() } } 

这种方法要求我们放弃对foo参数的静态types检查。 能够写作会更好

 object Bar { def foo(xs: (String or Int)*) = xs foreach { case _: String => println("str") case _: Int => println("int") } } 

我可以接近Either ,但它有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") } } 

它看起来像一个普通(优雅,高效)的解决scheme将需要定义Either3Either4 ,….有谁知道一个替代解决scheme,以达到相同的目的? 据我所知,Scala没有内置的“types分离”。 另外,隐藏在标准库中的隐式转换是否隐藏在某个地方,以便我可以导入它们?

那么,在Any*的具体情况下,下面的这个技巧将不起作用,因为它不会接受混合types。 但是,由于混合types不能用于重载,这可能是你想要的。

首先,用你想要接受的types声明一个类,如下所示:

 class StringOrInt[T] object StringOrInt { implicit object IntWitness extends StringOrInt[Int] implicit object StringWitness extends StringOrInt[String] } 

接下来,像这样声明foo

 object Bar { def foo[T: StringOrInt](x: T) = x match { case _: String => println("str") case _: Int => println("int") } } 

就是这样。 你可以调用foo(5)foo("abc") ,它可以工作,但是尝试foo(true)会失败。 这可以通过创build一个StringOrInt[Boolean]客户端代码StringOrInt[Boolean] ,除非如下Randall所述,您将StringOrInt一个sealed类。

它的工作原理是因为T: StringOrInt意味着有一个types为StringOrInt[T]的隐式参数,并且因为Scala会查找types的伴随对象,以查看是否有暗示让代码请求该types的工作。

Miles Sabin描述了一种非常好的方式,在他最近的博客文章中通过Curry-Howard同构在Scala中取得联合types:

他首先将types的否定定义为

 type ¬[A] = A => Nothing 

使用德摩根定律,这允许他定义工会types

 type ∨[T, U] = ¬[¬[T] with ¬[U]] 

有以下辅助结构

 type ¬¬[A] = ¬[¬[A]] type |∨|[T, U] = { type λ[X] = ¬¬[X] <:< (T ∨ U) } 

你可以编写联合types如下:

 def size[T : (Int |∨| String)#λ](t : T) = t match { case i : Int => i case s : String => s.length } 

Dotty是一个新的实验性的Scala编译器,它支持uniontypes(写成A | B ),所以你可以做到你想要的:

 def foo(xs: (String | Int)*) = xs foreach { case _: String => println("str") case _: Int => println("int") } 

这里是Rex Kerr编码联合types的方法。 简单而直接!

 scala> def f[A](a: A)(implicit ev: (Int with String) <:< A) = a match { | case i: Int => i + 1 | case s: String => s.length | } f: [A](a: A)(implicit ev: <:<[Int with String,A])Int scala> f(3) res0: Int = 4 scala> f("hello") res1: Int = 5 scala> f(9.2) <console>:9: error: Cannot prove that Int with String <:< Double. f(9.2) ^ 

资料来源: Miles Sabin在这篇出色的博文中的评论#27提供了另一种在Scala中编码联合types的方法。

Daniel的解决scheme可以概括如下:

 sealed trait Or[A, B] object Or { implicit def a2Or[A,B](a: A) = new Or[A, B] {} implicit def b2Or[A,B](b: B) = new Or[A, B] {} } object Bar { def foo[T <% String Or Int](x: T) = x match { case _: String => println("str") case _: Int => println("int") } } 

这种方法的主要缺点是

  • 正如Daniel指出的,它不处理混合types的集合/可变参数
  • 如果匹配不是详尽的,编译器不会发出警告
  • 如果匹配包含不可能的情况,编译器不会发出错误
  • 像任Either方法一样,进一步的泛化要求定义类似的Or3Or4等特征。 当然,定义这种特性比定义相应的Either类要简单得多。

更新:

米奇·布莱温斯(Mitch Blevins) 展示 了一种非常相似的方法,并展示了如何将其推广到两种以上types,称之为“口吃或”。

我曾经把types列表的概念与Miles Sabin在这个领域的工作简化相结合,在另一个答案中提到了一个相对干净的n元联盟types的实现。

给定types¬[-A]A上是逆变A ,根据定义给定A <: B我们可以写成¬[B] <: ¬[A] ,颠倒types的sorting。

给定typesABX ,我们要expressionX <: A || X <: B X <: A || X <: B 。 应用反变换,我们得到¬[A] <: ¬[X] || ¬[B] <: ¬[X] ¬[A] <: ¬[X] || ¬[B] <: ¬[X] 。 这又可以表示为¬[A] with ¬[B] <: ¬[X] ,其中AB必须是XX本身的超types(考虑函数参数)。

 object Union { import scala.language.higherKinds sealed trait ¬[-A] sealed trait TSet { type Compound[A] type Map[F[_]] <: TSet } sealed trait ∅ extends TSet { type Compound[A] = A type Map[F[_]] = ∅ } // Note that this type is left-associative for the sake of concision. sealed trait ∨[T <: TSet, H] extends TSet { // Given a type of the form `∅ ∨ A ∨ B ∨ ...` and parameter `X`, we want to produce the type // `¬[A] with ¬[B] with ... <:< ¬[X]`. type Member[X] = T#Map[¬]#Compound[¬[H]] <:< ¬[X] // This could be generalized as a fold, but for concision we leave it as is. type Compound[A] = T#Compound[H with A] type Map[F[_]] = T#Map[F] ∨ F[H] } def foo[A : (∅ ∨ String ∨ Int ∨ List[Int])#Member](a: A): String = a match { case s: String => "String" case i: Int => "Int" case l: List[_] => "List[Int]" } foo(42) foo("bar") foo(List(1, 2, 3)) foo(42d) // error foo[Any](???) // error } 

我花了一些时间试图将这个想法与TList / up的TList的成员types的上限结合起来 ,但是带有types边界的Map的实现迄今已经certificate是有挑战性的。

types类的解决scheme可能是最好的方式去这里,使用implicits。 这与Odersky / Spoon / Venners书中提到的monoid方法类似:

 abstract class NameOf[T] { def get : String } implicit object NameOfStr extends NameOf[String] { def get = "str" } implicit object NameOfInt extends NameOf[Int] { def get = "int" } def printNameOf[T](t:T)(implicit name : NameOf[T]) = println(name.get) 

如果你然后在REPL中运行这个:

 scala> printNameOf(1) int scala> printNameOf("sss") str scala> printNameOf(2.0f) <console>:10: error: could not find implicit value for parameter nameOf: NameOf[ Float] printNameOf(2.0f) ^ 

还有这个黑客

 implicit val x: Int = 0 def foo(a: List[Int])(implicit ignore: Int) { } implicit val y = "" def foo(a: List[String])(implicit ignore: String) { } foo(1::2::Nil) foo("a"::"b"::Nil) 

请参阅解决types删除歧义(Scala)

你可以看看MetaScala ,它有一个叫做OneOf东西。 我觉得这对于match语句来说效果不好,但是可以使用高阶函数来模拟匹配。 看看这个片段 ,但是请注意,“模拟匹配”部分被注释掉了,也许是因为它还没有正常工作。

现在对于一些社论:我不认为如你所描述的那样,定义Either3,Either4等是不可思议的。 这基本上与Scala内置的标准22元组types是双重的。 如果Scala具有内置的析取types,那么它肯定会很好,也许像{x, y, z}这样的一些很好的语法。

我认为,第一类不相交的types是一个密封的超types,替代的子types,并隐式转换到/从所需的types的析取到这些替代的子types。

我认为这个地址是Miles Sabin的解决scheme的意见 33-36,所以可以在使用站点使用的第一类types,但是我没有testing。

 sealed trait IntOrString case class IntOfIntOrString( v:Int ) extends IntOrString case class StringOfIntOrString( v:String ) extends IntOrString implicit def IntToIntOfIntOrString( v:Int ) = new IntOfIntOrString(v) implicit def StringToStringOfIntOrString( v:String ) = new StringOfIntOrString(v) object Int { def unapply( t : IntOrString ) : Option[Int] = t match { case v : IntOfIntOrString => Some( vv ) case _ => None } } object String { def unapply( t : IntOrString ) : Option[String] = t match { case v : StringOfIntOrString => Some( vv ) case _ => None } } def size( t : IntOrString ) = t match { case Int(i) => i case String(s) => s.length } scala> size("test") res0: Int = 4 scala> size(2) res1: Int = 2 

一个问题是Scala不会在匹配上下文的情况下使用,从IntOfIntOrStringInt (和StringOfIntOrStringString )的隐式转换,因此必须定义提取器和用case Int(i)而不是case i : Int


ADD:我在他的博客上回应了Miles Sabin如下。 也许有一些改进比任何一个:

  1. 它扩展到2种以上,在使用或定义站点没有任何额外的噪音。
  2. 参数被隐式地装箱,例如不需要size(Left(2))size(Right("test"))
  3. 模式匹配的语法被隐式地拆箱。
  4. 装箱和拆箱可能会被JVM热点优化掉。
  5. 语法可能是未来的第一类工会types采用的语法,所以迁移可能是无缝的? 也许对于联合types名称,最好使用V而不是Or ,例如IntVString ,“ Int |v| String Int |v| String `,` Int or String `,或我最喜欢的` Int|String String`?

更新:对上述模式的逻辑否定,我在Miles Sabin的博客上添加了一个替代(也许更有用)的模式 。

 sealed trait `Int or String` sealed trait `not an Int or String` sealed trait `Int|String`[T,E] case class `IntOf(Int|String)`( v:Int ) extends `Int|String`[Int,`Int or String`] case class `StringOf(Int|String)`( v:String ) extends `Int|String`[String,`Int or String`] case class `NotAn(Int|String)`[T]( v:T ) extends `Int|String`[T,`not an Int or String`] implicit def `IntTo(IntOf(Int|String))`( v:Int ) = new `IntOf(Int|String)`(v) implicit def `StringTo(StringOf(Int|String))`( v:String ) = new `StringOf(Int|String)`(v) implicit def `AnyTo(NotAn(Int|String))`[T]( v:T ) = new `NotAn(Int|String)`[T](v) def disjunction[T,E](x: `Int|String`[T,E])(implicit ev: E =:= `Int or String`) = x def negationOfDisjunction[T,E](x: `Int|String`[T,E])(implicit ev: E =:= `not an Int or String`) = x scala> disjunction(5) res0: Int|String[Int,Int or String] = IntOf(Int|String)(5) scala> disjunction("") res1: Int|String[String,Int or String] = StringOf(Int|String)() scala> disjunction(5.0) error: could not find implicit value for parameter ev: =:=[not an Int or String,Int or String] disjunction(5.0) ^ scala> negationOfDisjunction(5) error: could not find implicit value for parameter ev: =:=[Int or String,not an Int or String] negationOfDisjunction(5) ^ scala> negationOfDisjunction("") error: could not find implicit value for parameter ev: =:=[Int or String,not an Int or String] negationOfDisjunction("") ^ scala> negationOfDisjunction(5.0) res5: Int|String[Double,not an Int or String] = NotAn(Int|String)(5.0) 

其他更新:关于Mile Sabin解决scheme的评论23和35,这里是在使用站点上声明联合types的一种方法。 注意它是在第一级之后拆箱的,即它具有可以扩展到任意数量的types的优势,而Either需要嵌套装箱,而且在我之前的注释41中的范例是不可扩展的。 换句话说,一个D[Int ∨ String]可赋值给D[Int ∨ String]一个子types。

 type ¬[A] = (() => A) => A type ∨[T, U] = ¬[T] with ¬[U] class D[-A](v: A) { def get[T](f: (() => T)) = v match { case x : ¬[T] => x(f) } } def size(t: D[Int ∨ String]) = t match { case x: D[¬[Int]] => x.get( () => 0 ) case x: D[¬[String]] => x.get( () => "" ) case x: D[¬[Double]] => x.get( () => 0.0 ) } implicit def neg[A](x: A) = new D[¬[A]]( (f: (() => A)) => x ) scala> size(5) res0: Any = 5 scala> size("") error: type mismatch; found : java.lang.String("") required: D[?[Int,String]] size("") ^ scala> size("hi" : D[¬[String]]) res2: Any = hi scala> size(5.0 : D[¬[Double]]) error: type mismatch; found : D[(() => Double) => Double] required: D[?[Int,String]] size(5.0 : D[?[Double]]) ^ 

显然Scala编译器有三个错误。

  1. 它不会为目标析取中的第一个types之后的任何typesselect正确的隐式函数。
  2. 它不排除比赛中的D[¬[Double]]情况。

3。

 scala> class D[-A](v: A) { def get[T](f: (() => T))(implicit e: A <:< ¬[T]) = v match { case x : ¬[T] => x(f) } } error: contravariant type A occurs in covariant position in type <:<[A,(() => T) => T] of value e def get[T](f: (() => T))(implicit e: A <:< ?[T]) = v match { ^ 

get方法在inputtypes上没有被正确的约束,因为编译器不允许A处于协变位置。 有人可能会认为这是一个错误,因为我们所需要的仅仅是证据,我们并不能访问函数中的证据。 我做了select,不要在get方法中testingcase _ ,所以我不必在size() match unbox。


2012年3月5日:之前的更新需要改进。 Miles Sabin的解决scheme可以正确地使用子types。

 type ¬[A] = A => Nothing type ∨[T, U] = ¬[T] with ¬[U] class Super class Sub extends Super scala> implicitly[(Super ∨ String) <:< ¬[Super]] res0: <:<[?[Super,String],(Super) => Nothing] = scala> implicitly[(Super ∨ String) <:< ¬[Sub]] res2: <:<[?[Super,String],(Sub) => Nothing] = scala> implicitly[(Super ∨ String) <:< ¬[Any]] error: could not find implicit value for parameter e: <:<[?[Super,String],(Any) => Nothing] implicitly[(Super ? String) <:< ?[Any]] ^ 

我之前的更新的build议(近一stream的工会types)打破子types。

  scala> implicitly[D[¬[Sub]] <:< D[(Super ∨ String)]] error: could not find implicit value for parameter e: <:<[D[(() => Sub) => Sub],D[?[Super,String]]] implicitly[D[?[Sub]] <:< D[(Super ? String)]] ^ 

问题是在(() => A) => A出现了协变(返回types)和逆变(函数input,或者在这种情况下是函数的input返回值)位置,因此replace只能是不变的。

注意A => Nothing必要,因为我们希望A处于逆变位置,所以A超types不是 D[¬[A]] 子types ,也不是D[¬[A] with ¬[U]] ( 参见 )。 既然我们只需要双重反变换,即使我们可以丢弃¬ ,我们也可以实现等价于Miles的解。

 trait D[-A] scala> implicitly[D[D[Super]] <:< D[D[Super] with D[String]]] res0: <:<[D[D[Super]],D[D[Super] with D[String]]] = scala> implicitly[D[D[Sub]] <:< D[D[Super] with D[String]]] res1: <:<[D[D[Sub]],D[D[Super] with D[String]]] = scala> implicitly[D[D[Any]] <:< D[D[Super] with D[String]]] error: could not find implicit value for parameter e: <:<[D[D[Any]],D[D[Super] with D[String]]] implicitly[D[D[Any]] <:< D[D[Super] with D[String]]] ^ 

所以完整的解决scheme是。

 class D[-A] (v: A) { def get[T <: A] = v match { case x: T => x } } implicit def neg[A](x: A) = new D[D[A]]( new D[A](x) ) def size(t: D[D[Int] with D[String]]) = t match { case x: D[D[Int]] => x.get[D[Int]].get[Int] case x: D[D[String]] => x.get[D[String]].get[String] case x: D[D[Double]] => x.get[D[Double]].get[Double] } 

注意Scala中的前两个bug仍然存在,但是第三个bug被避免,因为T现在被限制为A子types。

我们可以确认子types的作品。

 def size(t: D[D[Super] with D[String]]) = t match { case x: D[D[Super]] => x.get[D[Super]].get[Super] case x: D[D[String]] => x.get[D[String]].get[String] } scala> size( new Super ) res7: Any = Super@1272e52 scala> size( new Sub ) res8: Any = Sub@1d941d7 

我一直认为,头等交集types是非常重要的,无论是由于锡兰有他们的原因 ,而是因为而不是收缩到Any意味着拆箱与预期types的match可以产生一个运行时错误,拆箱( 异构包含a)分离的集合可以被types检查(Scala必须修复我注意到的错误)。 工会比使用 metascala实验性的HList异构集合的复杂性更直接。

我们想要一个types操作符Or[U,V] ,它可以用来限制types参数X ,使得X <: UX <: V 。 以下是我们可以得到的最接近的定义:

 trait Inv[-X] type Or[U,T] = { type pf[X] = (Inv[U] with Inv[T]) <:< Inv[X] } 

以下是它的使用方法:

 // use class A; class B extends A; class C extends B def foo[X : (B Or String)#pf] = {} foo[B] // OK foo[C] // OK foo[String] // OK foo[A] // ERROR! foo[Number] // ERROR! 

这使用了一些Scalatypes的技巧。 主要的是使用广义types约束 。 给定typesUV ,当且仅当Scala编译器可以certificateUV的子types时,Scala编译器提供一个名为U <:< V (以及该类的隐式对象)。 下面是一个更简单的例子,它使用一般化的types约束,它适用于某些情况:

 def foo[X](implicit ev : (B with String) <:< X) = {} 

XB类的一个实例,一个String ,或者一个types既不是超types也不是BString的子types时,这个例子可以工作。 在前两种情况下,通过关键字(B with String) <: B(B with String) <: String的定义是真实的,因此Scala将提供一个隐式对象,它将以evforms传入: Scala编译器将正确接受foo[B]foo[String]

在最后一种情况下,我依靠的事实是,如果U with V <: X ,那么U <: XV <: X 。 这似乎是直截了当的,我只是假设它。 从这个假设中可以清楚地看出,当XBString的超types或子types时,为什么这个简单的例子失败:例如,在上面的例子中, foo[A]被错误地接受, foo[C]被错误地拒绝。 再一次,我们想要的是variablesUVX上的某种typesexpression式,当X <: UX <: V时,它们是正确的。

斯卡拉的逆转概念可以在这里帮助。 记住特质trait Inv[-X] ? 因为它的types参数X是相反的,当且仅当Y <: XInv[X] <: Inv[Y] 。 这意味着我们可以用上面的例子replace上面的例子:

 trait Inv[-X] def foo[X](implicit ev : (Inv[B] with Inv[String]) <:< Inv[X]) = {} 

这是因为当Inv[U] <: Inv[X]Inv[V] <: Inv[X]时,通过上面相同的假设,expression式(Inv[U] with Inv[V]) <: Inv[X] Inv[V] <: Inv[X] ,通过反variables的定义,当X <: UX <: V时,这是正确的。

通过声明一个可参数化的typesBOrString[X]并使用它,可以使事情变得更加可重用,如下所示:

 trait Inv[-X] type BOrString[X] = (Inv[B] with Inv[String]) <:< Inv[X] def foo[X](implicit ev : BOrString[X]) = {} 

Scala现在将尝试为每个被调用的X构造typesBOrString[X] ,并且当XBString的子types时,将精确地构造types。 这是有效的,并有一个速记符号。 下面的语法是等价的(除了ev现在必须在方法体中作为implicitly[BOrString[X]]引用implicitly[BOrString[X]]而不是简单的ev )并且使用BOrString作为types上下文绑定 :

 def foo[X : BOrString] = {} 

我们真正喜欢的是创buildtypes上下文绑定的灵活方式。 types上下文必须是可参数化的types,我们需要一个可参数化的方法来创build一个。 这听起来就像我们试图在types上嵌套函数一样,就像我们在函数的值上一样。 换句话说,我们希望如下所示:

 type Or[U,T][X] = (Inv[U] with Inv[T]) <:< Inv[X] 

这在Scala中不是直接可能的,但是我们可以使用一个技巧来实现。 这给我们带来了以上的定义:

 trait Inv[-X] type Or[U,T] = { type pf[X] = (Inv[U] with Inv[T]) <:< Inv[X] } 

在这里,我们使用结构types和斯卡拉的磅操作符来创build一个结构typesOr[U,T] ,保证有一个内部types。 这是一个奇怪的野兽。 为了给出一些上下文,函数def bar[X <: { type Y = Int }](x : X) = {}必须在其中定义typesYAnyRef子类中AnyRef

 bar(new AnyRef{ type Y = Int }) // works! 

使用pound运算符允许我们引用内部typesOr[B, String]#pf ,并为types运算符使用中缀表示法 Or ,我们到达foo的原始定义:

 def foo[X : (B Or String)#pf] = {} 

我们可以使用这样的事实,即函数types在它们的第一个types参数中是逆变的,以避免定义特征Inv

 type Or[U,T] = { type pf[X] = ((U => _) with (T => _)) <:< (X => _) } 

如果你不喜欢库里 – 霍华德,还有另一种方法可以让你更容易理解:

 type v[A,B] = Either[Option[A], Option[B]] private def L[A,B](a: A): v[A,B] = Left(Some(a)) private def R[A,B](b: B): v[A,B] = Right(Some(b)) // TODO: for more use scala macro to generate this for up to 22 types? implicit def a2[A,B](a: A): v[A,B] = L(a) implicit def b2[A,B](b: B): v[A,B] = R(b) implicit def a3[A,B,C](a: A): v[v[A,B],C] = L(a2(a)) implicit def b3[A,B,C](b: B): v[v[A,B],C] = L(b2(b)) implicit def a4[A,B,C,D](a: A): v[v[v[A,B],C],D] = L(a3(a)) implicit def b4[A,B,C,D](b: B): v[v[v[A,B],C],D] = L(b3(b)) implicit def a5[A,B,C,D,E](a: A): v[v[v[v[A,B],C],D],E] = L(a4(a)) implicit def b5[A,B,C,D,E](b: B): v[v[v[v[A,B],C],D],E] = L(b4(b)) type JsonPrimtives = (String v Int v Double) type ValidJsonPrimitive[A] = A => JsonPrimtives def test[A : ValidJsonPrimitive](x: A): A = x test("hi") test(9) // test(true) // does not compile 

我在第戎使用类似的技术

那么,这一切都很聪明,但我相当确定你已经知道你的主要问题的答案是“不”的各种变化。 Scala处理重载的方式不同,它必须被承认,不如你描述的那么优雅。 其中一些原因是由于Java的互操作性,其中一些原因是由于不希望碰到types推理algorithm的边缘情况,其中一些原因是因为它不是Haskell。

在这里添加已经很好的答案。 这里有一个基于Miles Sabin联合types(和Josh的想法)构build的要点,但也使得它们recursion定义,所以你可以在联合中有两种types( def foo[A : UNil Or Int Or String Or List[String]

https://gist.github.com/aishfenton/2bb3bfa12e0321acfc904a71dda9bfbb

注意:我应该补充一点,在完成一个项目之后,我回到了简单的旧式(即带有子类的密封特征)。 Miles Sabin联合types对于限制types参数非常有用,但是如果您需要返回联合types,那么它不会提供太多的function。

From the docs , with the addition of sealed :

 sealed class Expr case class Var (x: String) extends Expr case class Apply (f: Expr, e: Expr) extends Expr case class Lambda(x: String, e: Expr) extends Expr 

Regarding the sealed part:

It is possible to define further case classes that extend type Expr in other parts of the program (…). This form of extensibility can be excluded by declaring the base class Expr sealed; in this case, all classes that directly extend Expr must be in the same source file as Expr.