如何定义“类型分离”(联合类型) ?

使用 有人建议来处理重载方法的双重定义的一种方法是用模式匹配代替重载:

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

看起来一个通用的(优雅的,高效的)解决方案需要定义 Either3Either4,... 。有人知道达到同样目的的替代方案吗?据我所知,Scala 没有内置的“类型分离”。另外,上面定义的隐式转换是否潜伏在标准库的某个地方,以便我可以直接导入它们?

55942 次浏览

这些都很聪明,但我很确定你已经知道你的诱导性问题的答案是各种各样的“不”。Scala 处理重载的方式不同,而且必须承认,它比你描述的要稍微逊色一些。一部分是因为 Java 的互操作性,一部分是因为不想触及类型推断算法的边缘情况,还有一部分是因为它不是 Haskell。

你可以看看 MetaScala,它有一个叫 OneOf的东西。我得到的印象是,这对于 match语句并不能很好地工作,但是您可以使用高阶函数模拟匹配。例如,看看 这个片段,但是注意“模拟匹配”部分被注释掉了,可能是因为它还没有完全工作。

现在进行一些社论: 我不认为像您所描述的那样定义任何一个3、任何一个4等等有什么过分的地方。这基本上与 Scala 内置的标准22元组类型是双重的。如果 Scala 有内置的析取类型,或许还有一些像 {x, y, z}这样的语法,那当然很好。

好吧,在 Any*的特定情况下,下面的技巧不会起作用,因为它不会接受混合类型。但是,由于混合类型也不能用于重载,所以这可能是您想要的。

首先,声明一个类,其类型如下:

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),它将失败。客户机代码可以通过创建一个 StringOrInt[Boolean]来规避这个问题,除非如下面的 兰德尔所指出的那样,您将 StringOrInt设置为一个 sealed类。

因为 T: StringOrInt意味着存在一个类型为 StringOrInt[T]的隐式参数,而且因为 Scala 在一个类型的伴随对象中查看是否存在隐式,以使请求该类型的代码能够工作。

还有这个 黑客:

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)

参见 解决类型消除歧义(Scala)

类型类解决方案可能是这里使用隐式的最好方法。 这类似于 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)


^

可以将 丹尼尔的解决方案推广如下:

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 指出的,它不处理混合类型的集合/varargs
  • 如果匹配不是穷举的,编译器不会发出警告
  • 如果匹配包含不可能的情况,则编译器不会发出错误
  • Either方法一样,进一步的推广需要定义类似的 Or3Or4等特征。当然,定义这些特征要比定义相应的 Either类简单得多。

更新:

Mitch Blevins 的 演示 非常相似的方法,并展示了如何将其推广到两种以上的类型,称之为“口吃或”。

Miles Sabin 在他最近的博客文章 在 Scala 中通过柯里-霍华德同构实现未装箱的联合类型中描述了一个非常好的获得工会类型的方法:

他首先将类型的否定定义为

type ¬[A] = A => Nothing

利用德摩根法则,他可以定义工会类型

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

使用以下辅助构造

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

您可以按以下方式编写联合类型:

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

下面是 Rex Kerr 编码联合类型的方法!

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

来源: 这个下的第27条评论 Miles Sabin 的优秀博客文章,它提供了另一种在 Scala 中编码联合类型的方法。

我认为第一类不相交类型是一个密封的超类型,带有替代子类型,并且隐式地转换为/从所需的析出类型转换为这些替代子类型。

我假设这个地址的 评论33-36迈尔斯萨宾的解决方案,所以第一类类型,可以使用在使用网站,但我没有测试它。

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( v.v )
case _ => None
}
}


object String {
def unapply( t : IntOrString ) : Option[String] = t match {
case v : StringOfIntOrString => Some( v.v )
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. 语法可能是未来的第一类联合类型所采用的语法,所以迁移可能是无缝的?也许对于联合类型名称,最好使用 V而不是 Or,例如 IntVStringInt |v| String & # 96; ,或者我最喜欢的 Int|String & # 96; ?

更新: 上述模式的分离逻辑非如下,并且 I 在 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 的解决方案的注释23和35,这里有一种在 use 站点声明联合类型的方法。注意,它在第一个级别之后是取消装箱的,也就是说,它的优势是 可扩展到分离中的任意数量的类型,而 Either需要嵌套装箱,而我之前注释41中的范例是不可扩展的。换句话说,D[Int ∨ String]可分配给 D[Int ∨ String ∨ Double](即是 D[Int ∨ String ∨ Double]的子类型)。

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 编译器有三个 bug。

  1. 它不会在目标分离中的第一个类型之后为任何类型选择正确的隐函数。
  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 方法对输入类型的约束不正确,因为编译器不允许 A处于协变位置。有人可能会说这是一个 bug,因为我们想要的只是证据,我们从来没有访问过函数中的证据。我选择不在 get方法中测试 case _,所以我不必在 size()match中解压 Option


2012年3月5日: 之前的更新需要改进。

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]]
^

我之前更新的提案(针对接近一流的联合类型)破坏了子类型。

 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中的 A同时出现在协变量(返回类型)和逆变量(函数输入,在这种情况下是函数输入的返回值)位置,因此替换只能是不变的。

请注意,A => Nothing的必要性仅仅是因为我们希望 A处于逆变位置,这样,D[¬[A]]A 不是子类型的超类型和 D[¬[A] with ¬[U]](参见)。因为我们只需要双逆变,所以即使抛弃 ¬,也可以得到相当于迈尔斯解的结果。

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]]]
^

所以完全的解决办法就是。

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的子类型。

我们可以确认子类型的工作。

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

我一直在想,一级交叉类型对于 Ceylon 拥有它们的原因来说非常重要,因为不管是对于 归并还是对于 Any来说,这都意味着在预期类型上使用 match解除装箱可能会产生运行时错误,解除装箱(包含 a 的 异质收藏)可以通过类型检查(Scala 必须修复我提到的 bug)。对于异构集合,联合比 使用的复杂性更直接,原发性椎间盘突出的实验 HList更直接。

通过将类型列表的概念与 Miles Sabin 在这个地区的工作的简化相结合,我偶然发现了一种相对简洁的 n 元联合类型的实现,有人在另一个答案中提到了 Miles Sabin 在这个地区的工作

给定 ¬[-A]类型,它在 A上是逆变的,根据给定 A <: B的定义,我们可以写 ¬[B] <: ¬[A],反转类型的顺序。

给定类型 ABX,我们希望表达 X <: A || X <: B。 应用逆变,我们得到 ¬[A] <: ¬[X] || ¬[B] <: ¬[X]。这可以依次 表示为 ¬[A] with ¬[B] <: ¬[X],其中 AB必须是 XX本身的超类型(考虑函数参数)。

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中所看到的成员类型的上限结合起来,但是带有类型界限的 Map的实现迄今为止被证明是具有挑战性的。

如果你不理解库里-霍华德的话,还有一种方法稍微容易理解一点:

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

我用的是类似的 第戎语中的技巧

Dotty ,一个新的实验性 Scala 编译器,支持联合类型(编写的 A | B) ,所以你可以做你想做的:

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

我们想要一个类型操作符 Or[U,V],它可以用来约束类型参数 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!

这使用了一些 Scala 类型技巧。其中最主要的就是 广义类型约束广义类型约束的使用。给定类型 UV,当且仅当 Scala 编译器能够证明 UV的子类型时,Scala 编译器才会提供一个称为 U <:< V的类(以及该类的一个隐式对象)。下面是一个简单的例子,它使用广义类型约束,适用于某些情况:

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

X是类 BString的实例,或者其类型既不是超类型,也不是 BString的子类型时,此示例可以工作。在前两种情况下,根据 with关键字的定义,(B with String) <: B(B with String) <: String是正确的,因此 Scala 将提供一个隐式对象,作为 ev传入: Scala 编译器将正确地接受 foo[B]B0。

在最后一种情况下,我依赖于这样一个事实: 如果 U with V <: X,那么 U <: XV <: X。从直觉上来说,这似乎是真的,我只是假设而已。从这个假设中可以清楚地看出,当 XBString的超类型或子类型时,为什么这个简单的示例会失败: 例如,在上面的示例中,foo[A]被错误地接受,而 foo[C]被错误地拒绝。同样,我们需要的是变量 UVX上的某种类型表达式,这种表达式在 U <: X1或 U <: X2时为真。

Scala 的逆变性概念可以在这里提供帮助。还记得 trait Inv[-X]特征吗?因为它的类型参数 X是逆变的,Inv[X] <: Inv[Y]当且仅当 Y <: X。这意味着我们可以用一个真正有效的例子来代替上面的例子:

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

这是因为表达式 (Inv[U] with Inv[V]) <: Inv[X]是真的,根据上面的相同假设,确切的时间 Inv[U] <: Inv[X]Inv[V] <: Inv[X],根据逆变的定义,确切的时间 X <: UX <: V

通过声明一个可参数化类型 BOrString[X]并使用它,我们可以让事情变得更加可重用:

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

Scala 现在将尝试为调用 foo的每个 X构造类型 BOrString[X],当 XBString的子类型时,将精确地构造该类型。这样就行了,还有一个速记符号。下面的语法是等价的(除了 ev现在必须在方法体中引用为 implicitly[BOrString[X]]而不是简单的 ev) ,并使用 BOrString作为 X0:

def foo[X : BOrString] = {}

我们真正想要的是一种灵活的方式来创建类型上下文绑定。类型上下文必须是可参数化的类型,我们希望以可参数化的方式创建类型。这听起来像是我们试图在类型上套用函数,就像我们在值上套用函数一样。换句话说,我们希望是这样的:

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

这是 Scala 中的 不是直接可能的,但是我们可以使用一个技巧来非常接近它。这就引出了上面 Or的定义:

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

在这里,我们使用 结构分类法和 Scala 的 井场操作员井场操作员来创建一个结构类型 Or[U,T],它保证具有一个内部类型。这是一只奇怪的野兽。为了给出一些上下文,函数 def bar[X <: { type Y = Int }](x : X) = {}必须使用 AnyRef的子类来调用,这些子类中定义了类型 Y:

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

使用井号运算符允许我们引用内部类型 Or[B, String]#pf,使用 中缀表示法作为类型运算符 Or,我们得到了 foo的原始定义:

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

我们可以利用函数类型在它们的第一个类型参数中是逆变的这一事实来避免定义 trait Inv:

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

为这里已经很棒的答案添砖加瓦。这里有一个基于 Miles Sabin 联合类型(和 Josh 的想法)的要点,但是也使它们递归定义,所以你可以在联合中使用 > 2种类型(def foo[A : UNil Or Int Or String Or List[String])

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

注意: 我应该补充一下,在对一个项目进行了上述操作之后,我最终回到了普通的老式和类型(即带有子类的密封 trait)。Miles Sabin 联合类型对于限制类型参数非常有用,但是如果需要返回联合类型,那么它就不能提供太多。

那些文件开始,加上 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

关于 sealed部分:

可以在程序的其他部分(...)中定义进一步扩展 Expr 类型的 case 类。可以通过声明基类 Expr 是密封的来排除这种形式的可扩展性; 在这种情况下,直接扩展 Expr 的所有类都必须与 Expr 位于同一个源文件中。

在 Scala 3中,可以使用 Union 类型 启动一个 Scala 3项目: < a href = “ https://dotty.epfl.ch/# getting-Start”rel = “ nofollow norefrer”> https://dotty.epfl.ch/#getting-started

一种方法是

sbt new lampepfl/dotty.g8

然后您可以将目录更改为 project,并键入 sbt console以启动 REPL。

档号: https://dotty.epfl.ch/docs/reference/new-types/union-types.html

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


scala> foo(2,"2","acc",-223)
int
str
str
int