One way that has been suggested to deal with double definitions of overloaded methods is to replace overloading with pattern matching:
建议处理重载方法的双重定义的一种方法是用模式匹配替换重载:
object Bar {
def foo(xs: Any*) = xs foreach {
case _:String => println("str")
case _:Int => println("int")
case _ => throw new UglyRuntimeException()
}
}
This approach requires that we surrender static type checking on the arguments to foo
. It would be much nicer to be able to write
这种方法要求我们放弃对foo的参数的静态类型检查。写起来会更好。
object Bar {
def foo(xs: (String or Int)*) = xs foreach {
case _: String => println("str")
case _: Int => println("int")
}
}
I can get close with Either
, but it gets ugly fast with more than two types:
我也可以接近,但它很快就会变丑,有两种类型:
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")
}
}
It looks like a general (elegant, efficient) solution would require defining Either3
, Either4
, .... Does anyone know of an alternate solution to achieve the same end? To my knowledge, Scala does not have built-in "type disjunction". Also, are the implicit conversions defined above lurking in the standard library somewhere so that I can just import them?
它看起来像一个将军(优雅、高效)解决方案需要定义Either3 Either4,....有没有人知道另一种方法来达到同样的目的?据我所知,Scala没有内置的“类型分离”。另外,在标准库中是否有定义的隐式转换,以便我可以导入它们?
15 个解决方案
#1
124
Well, in the specific case of Any*
, this trick below won't work, as it will not accept mixed types. However, since mixed types wouldn't work with overloading either, this may be what you want.
在任何*的具体情况下,下面这个技巧都不起作用,因为它不会接受混合类型。但是,由于混合类型不能处理重载,所以这可能是您想要的。
First, declare a class with the types you wish to accept as below:
首先,声明一个类,您希望接受的类型如下:
class StringOrInt[T]
object StringOrInt {
implicit object IntWitness extends StringOrInt[Int]
implicit object StringWitness extends StringOrInt[String]
}
Next, declare foo
like this:
接下来,像这样声明foo:
object Bar {
def foo[T: StringOrInt](x: T) = x match {
case _: String => println("str")
case _: Int => println("int")
}
}
And that's it. You can call foo(5)
or foo("abc")
, and it will work, but try foo(true)
and it will fail. This could be side-stepped by the client code by creating a StringOrInt[Boolean]
, unless, as noted by Randall below, you make StringOrInt
a sealed
class.
就是这样。您可以调用foo(5)或foo(“abc”),它将工作,但是尝试foo(true),它将失败。这可以通过创建一个StringOrInt[布尔]来绕过客户机代码,除非,正如Randall所指出的那样,您将StringOrInt作为一个密封的类。
It works because T: StringOrInt
means there's an implicit parameter of type StringOrInt[T]
, and because Scala looks inside companion objects of a type to see if there are implicits there to make code asking for that type work.
它的工作原理是:StringOrInt意味着有一个StringOrInt类型的隐式参数,因为Scala查看了一个类型的伙伴对象,以查看是否有一些implicits,以使代码请求该类型的工作。
#2
155
Miles Sabin describes a very nice way to get union type in his recent blog post Unboxed union types in Scala via the Curry-Howard isomorphism:
Miles Sabin描述了一种很好的方法,在他最近的博客文章中,通过Curry-Howard同构的方式在Scala中创建了Unboxed联合类型:
He first defines negation of types as
他首先定义了类型的否定。
type ¬[A] = A => Nothing
using De Morgan's law this allows him to define union types
利用德摩根定律,他可以定义工会类型。
type ∨[T, U] = ¬[¬[T] with ¬[U]]
With the following auxiliary constructs
有以下辅助结构。
type ¬¬[A] = ¬[¬[A]]
type |∨|[T, U] = { type λ[X] = ¬¬[X] <:< (T ∨ U) }
you can write union types as follows:
您可以将union类型写入如下:
def size[T : (Int |∨| String)#λ](t : T) = t match {
case i : Int => i
case s : String => s.length
}
#3
33
Dotty, a new experimental Scala compiler, supports union types (written A | B
), so you can do exactly what you wanted:
Dotty,一个新的实验性Scala编译器,支持union类型(写| B),所以你可以做你想做的:
def foo(xs: (String | Int)*) = xs foreach {
case _: String => println("str")
case _: Int => println("int")
}
#4
26
Here is the Rex Kerr way to encode union types. Straight and simple!
这是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)
^
Source: Comment #27 under this excellent blog post by Miles Sabin which provides another way of encoding union types in Scala.
来源:在这篇优秀的博客文章中,Miles Sabin提供了另一种方法,可以在Scala中对union类型进行编码。
#5
14
It's possible to generalize Daniel's solution as follows:
可以将丹尼尔的解决方案归纳如下:
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")
}
}
The main drawbacks of this approach are
这种方法的主要缺点是。
- As Daniel pointed out, it does not handle collections/varargs with mixed types
- 正如Daniel指出的,它不处理混合类型的集合/varargs。
- The compiler does not issue a warning if the match is not exhaustive
- 如果匹配不是详尽的,编译器不会发出警告。
- The compiler does not issue an error if the match includes an impossible case
- 如果匹配包含一个不可能的情况,编译器不会发出错误。
- Like the
Either
approach, further generalization would require defining analogousOr3
,Or4
, etc. traits. Of course, defining such traits would be much simpler than defining the correspondingEither
classes. - 就像这两种方法一样,进一步的泛化需要定义类似的Or3、Or4等特性。当然,定义这些特性要比定义相应的类简单得多。
Update:
更新:
Mitch Blevins demonstrates a very similar approach and shows how to generalize it to more than two types, dubbing it the "stuttering or".
Mitch Blevins展示了一种非常相似的方法,并展示了如何将其归纳为两种以上的类型,将其称为“口吃”或“。”
#6
13
I have sort of stumbled on a relatively clean implementation of n-ary union types by combining the notion of type lists with a simplification of Miles Sabin's work in this area, which someone mentions in another answer.
我偶然发现了一个相对干净的n-ary union类型的实现方法,将类型列表的概念与Miles Sabin在这个领域的工作进行了简化,有人在另一个答案中提到了这一点。
Given type ¬[-A]
which is contravariant on A
, by definition given A <: B
we can write ¬[B] <: ¬[A]
, inverting the ordering of types.
[A]在A上是逆变的,根据定义A <: B我们可以写[B] <: [A],反转类型的顺序。
Given types A
, B
, and X
, we want to express X <: A || X <: B
. Applying contravariance, we get ¬[A] <: ¬[X] || ¬[B] <: ¬[X]
. This can in turn be expressed as ¬[A] with ¬[B] <: ¬[X]
in which one of A
or B
must be a supertype of X
or X
itself (think about function arguments).
给定类型A,B,X,我们想表达X <:| | X <:B应用抗变性,我们得到¬[一]<:¬[X]| |¬[B]<:¬[X]。这反过来可以表示为¬与¬[A][B]<:¬[X]A或B的一个必须的超类型X或者X本身(考虑函数参数)。
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
}
I did spend some time trying to combine this idea with an upper bound on member types as seen in the TList
s of harrah/up, however the implementation of Map
with type bounds has thus far proved challenging.
我确实花了一些时间试图将这个想法与在harrah/up的tlist中看到的成员类型的上界结合起来,但是到目前为止,使用类型边界的Map的实现是具有挑战性的。
#7
12
A type class solution is probably the nicest way to go here, using implicits. This is similar to the monoid approach mentioned in the Odersky/Spoon/Venners book:
类型类解决方案可能是使用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)
If you then run this in the REPL:
如果你在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)
^
#8
8
There is also this hack:
还有这个黑客:
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)
See Working around type erasure ambiguities (Scala).
参见工作围绕类型消除歧义(Scala)。
#9
7
You might take a look at MetaScala, which has something called OneOf
. I get the impression that this doesn't work well with match
statements but that you can simulate matching using higher-order functions. Take a look at this snippet, for instance, but note that the "simulated matching" part is commented out, maybe because it doesn't quite work yet.
你可能会看一看MetaScala,它有一个叫做。我得到的印象是,这在匹配语句中效果不佳,但是您可以使用高阶函数模拟匹配。例如,看看这段代码,但是请注意,“模拟匹配”部分被注释掉了,可能是因为它还没有完全发挥作用。
Now for some editorializing: I don't think there's anything egregious about defining Either3, Either4, etc. as you describe. This is essentially dual to the standard 22 tuple types built in to Scala. It'd certainly be nice if Scala had built-in disjunctive types, and perhaps some nice syntax for them like {x, y, z}
.
现在,对于一些编辑:我不认为定义Either3, Either4等等有什么过分的地方,如你所描述的。这本质上是与标准的22元组类型在Scala中构建的。如果Scala有内置的析取类型,或者像{x, y, z}这样的语法,那肯定很好。
#10
6
I am thinking that the first class disjoint type is a sealed supertype, with the alternate subtypes, and implicit conversions to/from the desired types of the disjunction to these alternative subtypes.
我认为,第一类分离类型是一种密封的超类型,具有备用子类型,以及与这些可选子类型的分离类型的隐式转换。
I assume this addresses comments 33 - 36 of Miles Sabin's solution, so the first class type that can be employed at the use site, but I didn't test it.
我假定这个地址注释33 - 36英里Sabin的解决方案,所以第一类可以在使用站点上使用,但是我没有测试它。
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
One problem is Scala will not employ in case matching context, an implicit conversion from IntOfIntOrString
to Int
(and StringOfIntOrString
to String
), so must define extractors and use case Int(i)
instead of case i : Int
.
一个问题是Scala不会在case匹配上下文中使用,一个隐式转换从fintorstring到Int(和StringOfIntOrString到String),因此必须定义提取器和用例Int(i),而不是case i: Int。
ADD: I responded to Miles Sabin at his blog as follows. Perhaps there are several improvements over Either:
补充:我在他的博客里回应了迈尔斯·萨宾,如下。也许还有一些改进:
- It extends to more than 2 types, without any additional noise at the use or definition site.
- 它扩展到超过两种类型,没有任何额外的噪音在使用或定义网站。
- Arguments are boxed implicitly, e.g. don't need
size(Left(2))
orsize(Right("test"))
. - 参数是隐式的,例如不需要大小(左(2))或大小(右(“测试”))。
- The syntax of the pattern matching is implicitly unboxed.
- 模式匹配的语法是隐式的。
- The boxing and unboxing may be optimized away by the JVM hotspot.
- JVM热点可以优化装箱和取消装箱。
- The syntax could be the one adopted by a future first class union type, so migration could perhaps be seamless? Perhaps for the union type name, it would be better to use
V
instead ofOr
, e.g.IntVString
, `Int |v| String
`, `Int or String
`, or my favorite `Int|String
`? - 语法可能是未来的第一类联合类型所采用的,所以迁移可能是无缝的?也许对于联合类型的名称,最好使用V而不是或,例如IntVString, ' Int| V |字符串',' Int或String ',或者我最喜欢的' Int|字符串' ?
UPDATE: Logical negation of the disjunction for the above pattern follows, and I added an alternative (and probably more useful) pattern at Miles Sabin's blog.
更新:对上述模式的分离的逻辑否定如下,我在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)
ANOTHER UPDATE: Regarding comments 23 and 35 of Mile Sabin's solution, here is a way to declare a union type at the use site. Note it is unboxed after the first level, i.e. it has the advantage being extensible to any number of types in the disjunction, whereas Either
needs nested boxing and the paradigm in my prior comment 41 was not extensible. In other words, a D[Int ∨ String]
is assignable to (i.e. is a subtype of) a D[Int ∨ String ∨ Double]
.
另一个更新:关于Sabin的解决方案的注释23和35,这里有一种方法可以在使用站点上声明一个联合类型。注意,它在第一级之后是unboxed,即它的优点是可以扩展到分离中的任意数量的类型,而不是需要嵌套的装箱,而在我之前的注释41中是不可扩展的。换句话说,一个D(Int∨字符串)是可转让(即是一个亚型)D[Int∨字符串∨双]。
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]])
^
Apparently the Scala compiler has three bugs.
显然,Scala编译器有三个bug。
- It will not choose the correct implicit function for any type after the first type in the destination disjunction.
- 它不会选择在目标分离后的第一个类型之后的任何类型的正确隐式函数。
- It doesn't exclude the
D[¬[Double]]
case from the match. - 这并不排除D[¬[两]]从这场比赛。
3.
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 {
^
The get method isn't constrained properly on input type, because the compiler won't allow A
in the covariant position. One might argue that is a bug because all we want is evidence, we don't ever access the evidence in the function. And I made the choice not to test for case _
in the get
method, so I wouldn't have to unbox an Option
in the match
in size()
.
get方法在输入类型上没有适当的限制,因为编译器不允许在协变位置上使用A。有人可能会说这是一个错误,因为我们想要的只是证据,我们从来没有在函数中获取证据。我在get方法中选择不测试case _,这样我就不用在size()的匹配中打开一个选项。
March 05, 2012: The prior update needs an improvement. Miles Sabin's solution worked correctly with subtyping.
2012年3月05日:之前的更新需要改进。Miles Sabin的解决方案正确地使用了子类型。
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]]
^
My prior update's proposal (for near first-class union type) broke subtyping.
我之前更新的建议(接近一流的union类型)破坏了子类型。
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)]]
^
The problem is that A
in (() => A) => A
appears in both the covariant (return type) and contravariant (function input, or in this case a return value of function which is a function input) positions, thus substitutions can only be invariant.
问题是A () => A) => A出现在协变(返回类型)和逆变函数(函数输入,或者在本例中是函数输入的返回值)位置,因此替换只能是不变的。
Note that A => Nothing
is necessary only because we want A
in the contravariant position, so that supertypes of A
are not subtypes of D[¬[A]]
nor D[¬[A] with ¬[U]]
(see also). Since we only need double contravariance, we can achieve equivalent to Miles' solution even if we can discard the ¬
and ∨
.
注意= >什么是必要的在逆变的位置,只是因为我们想要一个这样的工具不是亚型的D(¬[一]]和D(¬与¬[一][美国]](参见)。因为我们只需要双逆变性,我们可以达到相当于英里的解决方案即使我们可以丢弃¬∨。
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]]]
^
So the complete fix is.
完整的修正是。
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]
}
Note the prior 2 bugs in Scala remain, but the 3rd one is avoided as T
is now constrained to be subtype of A
.
请注意Scala中的前两个bug仍然存在,但是第3个bug被避免了,因为T现在被限制为A的子类型。
We can confirm the subtyping works.
我们可以确认子类型的工作。
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
I have been thinking that first-class intersection types are very important, both for the reasons Ceylon has them, and because instead of subsuming to Any
which means unboxing with a match
on expected types can generate a runtime error, the unboxing of a (heterogeneous collection containing a) disjunction can be type checked (Scala has to fix the bugs I noted). Unions are more straightforward than the complexity of using the experimental HList of metascala for heterogeneous collections.
我一直认为一流的交叉类型是非常重要的,这两个原因锡兰有他们,因为不是运用到任何这意味着仅用一根火柴在预期类型可以生成一个运行时错误,的拆箱(异构集合包含)分离可以类型检查(我注意到Scala修复bug)。与使用metascala的实验HList进行异构集合的复杂性相比,结合更为直观。
#11
5
We’d like a type operator Or[U,V]
that can be used to constrain a type parameters X
in such a way that either X <: U
or X <: V
. Here's a definition that comes about as close as we can get:
我们想要一个类型运算符,或者[U,V]可以用来约束一个类型参数X,在这样的方式下,X <: U或X <: V。这里有一个定义,我们可以得到如下的定义:
trait Inv[-X]
type Or[U,T] = {
type pf[X] = (Inv[U] with Inv[T]) <:< Inv[X]
}
Here is how it's used:
下面是它的用法:
// 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!
This uses a few Scala type tricks. The main one is the use of generalized type constraints. Given types U
and V
, the Scala compiler provides a class called U <:< V
(and an implicit object of that class) if and only if the Scala compiler can prove that U
is a subtype of V
. Here’s a simpler example using generalized type constraints that works for some cases:
这使用了一些Scala类型的技巧。主要是使用广义类型约束。鉴于类型U和V,Scala编译器提供了一个类称为U <:< V(和一个隐式对象的类)当且仅当Scala编译器可以证明你是一个亚型诉这是一个简单的例子使用广义类型约束,适用于某些情况下:
def foo[X](implicit ev : (B with String) <:< X) = {}
This example works when X
an instance of class B
, a String
, or has a type that is neither a supertype nor a subtype of B
or String
. In the first two cases, it’s true by the definition of the with
keyword that (B with String) <: B
and (B with String) <: String
, so Scala will provide an implicit object that will be passed in as ev
: the Scala compiler will correctly accept foo[B]
and foo[String]
.
这个例子适用于类B、字符串或类型既不是超类型也不是B或字符串的子类型的类型。在前两种情况下,用with关键字的定义是正确的(B和String) <: B和(B与String) <: String,因此Scala将提供一个隐式对象,它将作为ev传递:Scala编译器将正确地接受foo[B]和foo[String]。
In the last case, I’m relying on the fact that if U with V <: X
, then U <: X
or V <: X
. It seems intuitively true, and I’m simply assuming it. It’s clear from this assumption why this simple example fails when X
is a supertype or subtype of either B
or String
: for example, in the example above, foo[A]
is incorrectly accepted and foo[C]
is incorrectly rejected. Again, what we want is some kind of type expression on the variables U
, V
, and X
that is true exactly when X <: U
or X <: V
.
在最后一个例子中,我依赖的是如果U与V <: X,那么U <: X或V <: X,这似乎是正确的,我只是假设。从这个假设可以清楚地看出,当X是B或String的超类型或子类型时,这个简单的例子失败了:例如,在上面的例子中,foo[a]被错误地接受,而foo[C]被错误地拒绝了。同样,我们想要的是在变量U, V和X上的某种类型表达式当X <: U或X <: V时,这是正确的。
Scala’s notion of contravariance can help here. Remember the trait trait Inv[-X]
? Because it is contravariant in its type parameter X
, Inv[X] <: Inv[Y]
if and only if Y <: X
. That means that we can replace the example above with one that actually will work:
Scala关于逆变的概念可以在这里有所帮助。还记得这种特质特征吗?因为它在其类型参数X中是逆变的,如果且仅当Y <: X,这意味着我们可以将上面的例子替换为一个实际可行的例子:
trait Inv[-X]
def foo[X](implicit ev : (Inv[B] with Inv[String]) <:< Inv[X]) = {}
That’s because the expression (Inv[U] with Inv[V]) <: Inv[X]
is true, by the same assumption above, exactly when Inv[U] <: Inv[X]
or Inv[V] <: Inv[X]
, and by the definition of contravariance, this is true exactly when X <: U
or X <: V
.
这是因为这个表达式(Inv[U] with Inv[V]) <: Inv[X]是正确的,与上面相同的假设是一样的,当Inv[U] <: Inv[X]或Inv[V] <: Inv[X],并且根据逆变的定义,当X <: U或X <: V时,这是正确的。
It’s possible to make things a little more reusable by declaring a parametrizable type BOrString[X]
and using it as follows:
通过声明一个可参数化类型BOrString[X]并使用它,可以使事情变得更可重用:
trait Inv[-X]
type BOrString[X] = (Inv[B] with Inv[String]) <:< Inv[X]
def foo[X](implicit ev : BOrString[X]) = {}
Scala will now attempt to construct the type BOrString[X]
for every X
that foo
is called with, and the type will be constructed precisely when X
is a subtype of either B
or String
. That works, and there is a shorthand notation. The syntax below is equivalent (except that ev
must now be referenced in the method body as implicitly[BOrString[X]]
rather than simply ev
) and uses BOrString
as a type context bound:
Scala现在将尝试为foo调用的每一个X构造类型BOrString[X],并且当X是B或String的子类型时,将会精确构造类型。这是可行的,还有一个简化符号。下面的语法是等价的(除了ev现在必须在方法主体中被引用为隐式[BOrString[X]]而不是简单的ev),并将BOrString作为一个类型上下文绑定:
def foo[X : BOrString] = {}
What we’d really like is a flexible way to create a type context bound. A type context must be a parametrizable type, and we want a parametrizable way to create one. That sounds like we’re trying to curry functions on types just like we curry functions on values. In other words, we’d like something like the following:
我们真正喜欢的是一种灵活的方式来创建类型上下文绑定。类型上下文必须是可参数化的类型,我们需要一个可参数化的方法来创建一个。这听起来像是我们在尝试在类型上curry函数,就像我们在值上curry函数一样。换句话说,我们想要的东西如下:
type Or[U,T][X] = (Inv[U] with Inv[T]) <:< Inv[X]
That’s not directly possible in Scala, but there is a trick we can use to get pretty close. That brings us to the definition of Or
above:
这在Scala中是不可能的,但是我们可以用一个技巧来接近它。这就引出了或以上的定义:
trait Inv[-X]
type Or[U,T] = {
type pf[X] = (Inv[U] with Inv[T]) <:< Inv[X]
}
Here we use structural typing and Scala’s pound operator to create a structural type Or[U,T]
that is guaranteed to have one internal type. This is a strange beast. To give some context, the function def bar[X <: { type Y = Int }](x : X) = {}
must be called with subclasses of AnyRef
that have a type Y
defined in them:
这里我们使用结构类型和Scala的磅操作符来创建一个结构类型或[U,T],保证有一个内部类型。这是一只奇怪的野兽。为了提供一些上下文,函数def bar[X <: {type Y = Int}](X: X) ={},必须调用具有Y类型定义的AnyRef的子类:
bar(new AnyRef{ type Y = Int }) // works!
Using the pound operator allows us to refer to the inner type Or[B, String]#pf
, and using infix notation for the type operator Or
, we arrive at our original definition of foo
:
使用磅运算符允许我们引用内部类型或[B, String]#pf,并使用infix符号来表示类型操作符,或者,我们到达了我们最初定义的foo:
def foo[X : (B Or String)#pf] = {}
We can use the fact that function types are contravariant in their first type parameter in order to avoid defining the trait Inv
:
我们可以使用函数类型在其第一个类型参数中是逆变的这个事实,以避免定义特征Inv:
type Or[U,T] = {
type pf[X] = ((U => _) with (T => _)) <:< (X => _)
}
#12
4
There is another way which is slightly easier to understand if you do not grok Curry-Howard:
还有另一种方法,如果你不懂,就更容易理解了。
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
I use similar technique in dijon
我在第戎使用了类似的技术。
#13
1
Well, that's all very clever, but I'm pretty sure you know already that the answers to your leading questions are various varieties of "No". Scala handles overloading differently and, it must be admitted, somewhat less elegantly than you describe. Some of that's due to Java interoperability, some of that is due to not wanting to hit edged cases of the type inferencing algorithm, and some of that's due to it simply not being Haskell.
嗯,这都很聪明,但我很确定你已经知道,你的主要问题的答案是各种各样的“不”。Scala处理重载的方式不同,必须承认,这比您描述的要优雅一些。其中一些是由于Java互操作性造成的,其中一些是由于不希望对类型推断算法的边缘案例进行攻击,而其中一些是由于它根本不是Haskell。
#14
1
Adding to the already great answers here. Here's a gist that builds on Miles Sabin union types (and Josh's ideas) but also makes them recursively defined, so you can have >2 types in the union (def foo[A : UNil Or Int Or String Or List[String]
)
在这里加上已经很好的答案。这里有一个根据Miles Sabin union类型(和Josh的想法)构建的要点,但是也使它们被递归地定义,所以您可以在union中拥有>2类型(def foo[a: UNil或Int或String或List[String])
https://gist.github.com/aishfenton/2bb3bfa12e0321acfc904a71dda9bfbb
https://gist.github.com/aishfenton/2bb3bfa12e0321acfc904a71dda9bfbb
NB: I should add that after playing around with the above for a project, I ended up going back to plain-old-sum-types (i.e. sealed trait with subclasses). Miles Sabin union types are great for restricting the type parameter, but if you need to return a union type then it doesn't offer much.
NB:我要补充的是,在完成了上面的项目之后,我最终回到了普通的类型(即带有子类的封闭特性)。Miles Sabin union类型对于限制类型参数非常有用,但是如果您需要返回一个union类型,那么它不会提供太多。
#15
0
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.
可以在程序的其他部分中定义扩展类型Expr的其他案例类(…)。这种可扩展性可以通过声明基类Expr密封来排除;在这种情况下,直接扩展Expr的所有类必须与Expr相同的源文件。
#1
124
Well, in the specific case of Any*
, this trick below won't work, as it will not accept mixed types. However, since mixed types wouldn't work with overloading either, this may be what you want.
在任何*的具体情况下,下面这个技巧都不起作用,因为它不会接受混合类型。但是,由于混合类型不能处理重载,所以这可能是您想要的。
First, declare a class with the types you wish to accept as below:
首先,声明一个类,您希望接受的类型如下:
class StringOrInt[T]
object StringOrInt {
implicit object IntWitness extends StringOrInt[Int]
implicit object StringWitness extends StringOrInt[String]
}
Next, declare foo
like this:
接下来,像这样声明foo:
object Bar {
def foo[T: StringOrInt](x: T) = x match {
case _: String => println("str")
case _: Int => println("int")
}
}
And that's it. You can call foo(5)
or foo("abc")
, and it will work, but try foo(true)
and it will fail. This could be side-stepped by the client code by creating a StringOrInt[Boolean]
, unless, as noted by Randall below, you make StringOrInt
a sealed
class.
就是这样。您可以调用foo(5)或foo(“abc”),它将工作,但是尝试foo(true),它将失败。这可以通过创建一个StringOrInt[布尔]来绕过客户机代码,除非,正如Randall所指出的那样,您将StringOrInt作为一个密封的类。
It works because T: StringOrInt
means there's an implicit parameter of type StringOrInt[T]
, and because Scala looks inside companion objects of a type to see if there are implicits there to make code asking for that type work.
它的工作原理是:StringOrInt意味着有一个StringOrInt类型的隐式参数,因为Scala查看了一个类型的伙伴对象,以查看是否有一些implicits,以使代码请求该类型的工作。
#2
155
Miles Sabin describes a very nice way to get union type in his recent blog post Unboxed union types in Scala via the Curry-Howard isomorphism:
Miles Sabin描述了一种很好的方法,在他最近的博客文章中,通过Curry-Howard同构的方式在Scala中创建了Unboxed联合类型:
He first defines negation of types as
他首先定义了类型的否定。
type ¬[A] = A => Nothing
using De Morgan's law this allows him to define union types
利用德摩根定律,他可以定义工会类型。
type ∨[T, U] = ¬[¬[T] with ¬[U]]
With the following auxiliary constructs
有以下辅助结构。
type ¬¬[A] = ¬[¬[A]]
type |∨|[T, U] = { type λ[X] = ¬¬[X] <:< (T ∨ U) }
you can write union types as follows:
您可以将union类型写入如下:
def size[T : (Int |∨| String)#λ](t : T) = t match {
case i : Int => i
case s : String => s.length
}
#3
33
Dotty, a new experimental Scala compiler, supports union types (written A | B
), so you can do exactly what you wanted:
Dotty,一个新的实验性Scala编译器,支持union类型(写| B),所以你可以做你想做的:
def foo(xs: (String | Int)*) = xs foreach {
case _: String => println("str")
case _: Int => println("int")
}
#4
26
Here is the Rex Kerr way to encode union types. Straight and simple!
这是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)
^
Source: Comment #27 under this excellent blog post by Miles Sabin which provides another way of encoding union types in Scala.
来源:在这篇优秀的博客文章中,Miles Sabin提供了另一种方法,可以在Scala中对union类型进行编码。
#5
14
It's possible to generalize Daniel's solution as follows:
可以将丹尼尔的解决方案归纳如下:
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")
}
}
The main drawbacks of this approach are
这种方法的主要缺点是。
- As Daniel pointed out, it does not handle collections/varargs with mixed types
- 正如Daniel指出的,它不处理混合类型的集合/varargs。
- The compiler does not issue a warning if the match is not exhaustive
- 如果匹配不是详尽的,编译器不会发出警告。
- The compiler does not issue an error if the match includes an impossible case
- 如果匹配包含一个不可能的情况,编译器不会发出错误。
- Like the
Either
approach, further generalization would require defining analogousOr3
,Or4
, etc. traits. Of course, defining such traits would be much simpler than defining the correspondingEither
classes. - 就像这两种方法一样,进一步的泛化需要定义类似的Or3、Or4等特性。当然,定义这些特性要比定义相应的类简单得多。
Update:
更新:
Mitch Blevins demonstrates a very similar approach and shows how to generalize it to more than two types, dubbing it the "stuttering or".
Mitch Blevins展示了一种非常相似的方法,并展示了如何将其归纳为两种以上的类型,将其称为“口吃”或“。”
#6
13
I have sort of stumbled on a relatively clean implementation of n-ary union types by combining the notion of type lists with a simplification of Miles Sabin's work in this area, which someone mentions in another answer.
我偶然发现了一个相对干净的n-ary union类型的实现方法,将类型列表的概念与Miles Sabin在这个领域的工作进行了简化,有人在另一个答案中提到了这一点。
Given type ¬[-A]
which is contravariant on A
, by definition given A <: B
we can write ¬[B] <: ¬[A]
, inverting the ordering of types.
[A]在A上是逆变的,根据定义A <: B我们可以写[B] <: [A],反转类型的顺序。
Given types A
, B
, and X
, we want to express X <: A || X <: B
. Applying contravariance, we get ¬[A] <: ¬[X] || ¬[B] <: ¬[X]
. This can in turn be expressed as ¬[A] with ¬[B] <: ¬[X]
in which one of A
or B
must be a supertype of X
or X
itself (think about function arguments).
给定类型A,B,X,我们想表达X <:| | X <:B应用抗变性,我们得到¬[一]<:¬[X]| |¬[B]<:¬[X]。这反过来可以表示为¬与¬[A][B]<:¬[X]A或B的一个必须的超类型X或者X本身(考虑函数参数)。
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
}
I did spend some time trying to combine this idea with an upper bound on member types as seen in the TList
s of harrah/up, however the implementation of Map
with type bounds has thus far proved challenging.
我确实花了一些时间试图将这个想法与在harrah/up的tlist中看到的成员类型的上界结合起来,但是到目前为止,使用类型边界的Map的实现是具有挑战性的。
#7
12
A type class solution is probably the nicest way to go here, using implicits. This is similar to the monoid approach mentioned in the Odersky/Spoon/Venners book:
类型类解决方案可能是使用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)
If you then run this in the REPL:
如果你在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)
^
#8
8
There is also this hack:
还有这个黑客:
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)
See Working around type erasure ambiguities (Scala).
参见工作围绕类型消除歧义(Scala)。
#9
7
You might take a look at MetaScala, which has something called OneOf
. I get the impression that this doesn't work well with match
statements but that you can simulate matching using higher-order functions. Take a look at this snippet, for instance, but note that the "simulated matching" part is commented out, maybe because it doesn't quite work yet.
你可能会看一看MetaScala,它有一个叫做。我得到的印象是,这在匹配语句中效果不佳,但是您可以使用高阶函数模拟匹配。例如,看看这段代码,但是请注意,“模拟匹配”部分被注释掉了,可能是因为它还没有完全发挥作用。
Now for some editorializing: I don't think there's anything egregious about defining Either3, Either4, etc. as you describe. This is essentially dual to the standard 22 tuple types built in to Scala. It'd certainly be nice if Scala had built-in disjunctive types, and perhaps some nice syntax for them like {x, y, z}
.
现在,对于一些编辑:我不认为定义Either3, Either4等等有什么过分的地方,如你所描述的。这本质上是与标准的22元组类型在Scala中构建的。如果Scala有内置的析取类型,或者像{x, y, z}这样的语法,那肯定很好。
#10
6
I am thinking that the first class disjoint type is a sealed supertype, with the alternate subtypes, and implicit conversions to/from the desired types of the disjunction to these alternative subtypes.
我认为,第一类分离类型是一种密封的超类型,具有备用子类型,以及与这些可选子类型的分离类型的隐式转换。
I assume this addresses comments 33 - 36 of Miles Sabin's solution, so the first class type that can be employed at the use site, but I didn't test it.
我假定这个地址注释33 - 36英里Sabin的解决方案,所以第一类可以在使用站点上使用,但是我没有测试它。
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
One problem is Scala will not employ in case matching context, an implicit conversion from IntOfIntOrString
to Int
(and StringOfIntOrString
to String
), so must define extractors and use case Int(i)
instead of case i : Int
.
一个问题是Scala不会在case匹配上下文中使用,一个隐式转换从fintorstring到Int(和StringOfIntOrString到String),因此必须定义提取器和用例Int(i),而不是case i: Int。
ADD: I responded to Miles Sabin at his blog as follows. Perhaps there are several improvements over Either:
补充:我在他的博客里回应了迈尔斯·萨宾,如下。也许还有一些改进:
- It extends to more than 2 types, without any additional noise at the use or definition site.
- 它扩展到超过两种类型,没有任何额外的噪音在使用或定义网站。
- Arguments are boxed implicitly, e.g. don't need
size(Left(2))
orsize(Right("test"))
. - 参数是隐式的,例如不需要大小(左(2))或大小(右(“测试”))。
- The syntax of the pattern matching is implicitly unboxed.
- 模式匹配的语法是隐式的。
- The boxing and unboxing may be optimized away by the JVM hotspot.
- JVM热点可以优化装箱和取消装箱。
- The syntax could be the one adopted by a future first class union type, so migration could perhaps be seamless? Perhaps for the union type name, it would be better to use
V
instead ofOr
, e.g.IntVString
, `Int |v| String
`, `Int or String
`, or my favorite `Int|String
`? - 语法可能是未来的第一类联合类型所采用的,所以迁移可能是无缝的?也许对于联合类型的名称,最好使用V而不是或,例如IntVString, ' Int| V |字符串',' Int或String ',或者我最喜欢的' Int|字符串' ?
UPDATE: Logical negation of the disjunction for the above pattern follows, and I added an alternative (and probably more useful) pattern at Miles Sabin's blog.
更新:对上述模式的分离的逻辑否定如下,我在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)
ANOTHER UPDATE: Regarding comments 23 and 35 of Mile Sabin's solution, here is a way to declare a union type at the use site. Note it is unboxed after the first level, i.e. it has the advantage being extensible to any number of types in the disjunction, whereas Either
needs nested boxing and the paradigm in my prior comment 41 was not extensible. In other words, a D[Int ∨ String]
is assignable to (i.e. is a subtype of) a D[Int ∨ String ∨ Double]
.
另一个更新:关于Sabin的解决方案的注释23和35,这里有一种方法可以在使用站点上声明一个联合类型。注意,它在第一级之后是unboxed,即它的优点是可以扩展到分离中的任意数量的类型,而不是需要嵌套的装箱,而在我之前的注释41中是不可扩展的。换句话说,一个D(Int∨字符串)是可转让(即是一个亚型)D[Int∨字符串∨双]。
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]])
^
Apparently the Scala compiler has three bugs.
显然,Scala编译器有三个bug。
- It will not choose the correct implicit function for any type after the first type in the destination disjunction.
- 它不会选择在目标分离后的第一个类型之后的任何类型的正确隐式函数。
- It doesn't exclude the
D[¬[Double]]
case from the match. - 这并不排除D[¬[两]]从这场比赛。
3.
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 {
^
The get method isn't constrained properly on input type, because the compiler won't allow A
in the covariant position. One might argue that is a bug because all we want is evidence, we don't ever access the evidence in the function. And I made the choice not to test for case _
in the get
method, so I wouldn't have to unbox an Option
in the match
in size()
.
get方法在输入类型上没有适当的限制,因为编译器不允许在协变位置上使用A。有人可能会说这是一个错误,因为我们想要的只是证据,我们从来没有在函数中获取证据。我在get方法中选择不测试case _,这样我就不用在size()的匹配中打开一个选项。
March 05, 2012: The prior update needs an improvement. Miles Sabin's solution worked correctly with subtyping.
2012年3月05日:之前的更新需要改进。Miles Sabin的解决方案正确地使用了子类型。
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]]
^
My prior update's proposal (for near first-class union type) broke subtyping.
我之前更新的建议(接近一流的union类型)破坏了子类型。
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)]]
^
The problem is that A
in (() => A) => A
appears in both the covariant (return type) and contravariant (function input, or in this case a return value of function which is a function input) positions, thus substitutions can only be invariant.
问题是A () => A) => A出现在协变(返回类型)和逆变函数(函数输入,或者在本例中是函数输入的返回值)位置,因此替换只能是不变的。
Note that A => Nothing
is necessary only because we want A
in the contravariant position, so that supertypes of A
are not subtypes of D[¬[A]]
nor D[¬[A] with ¬[U]]
(see also). Since we only need double contravariance, we can achieve equivalent to Miles' solution even if we can discard the ¬
and ∨
.
注意= >什么是必要的在逆变的位置,只是因为我们想要一个这样的工具不是亚型的D(¬[一]]和D(¬与¬[一][美国]](参见)。因为我们只需要双逆变性,我们可以达到相当于英里的解决方案即使我们可以丢弃¬∨。
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]]]
^
So the complete fix is.
完整的修正是。
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]
}
Note the prior 2 bugs in Scala remain, but the 3rd one is avoided as T
is now constrained to be subtype of A
.
请注意Scala中的前两个bug仍然存在,但是第3个bug被避免了,因为T现在被限制为A的子类型。
We can confirm the subtyping works.
我们可以确认子类型的工作。
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
I have been thinking that first-class intersection types are very important, both for the reasons Ceylon has them, and because instead of subsuming to Any
which means unboxing with a match
on expected types can generate a runtime error, the unboxing of a (heterogeneous collection containing a) disjunction can be type checked (Scala has to fix the bugs I noted). Unions are more straightforward than the complexity of using the experimental HList of metascala for heterogeneous collections.
我一直认为一流的交叉类型是非常重要的,这两个原因锡兰有他们,因为不是运用到任何这意味着仅用一根火柴在预期类型可以生成一个运行时错误,的拆箱(异构集合包含)分离可以类型检查(我注意到Scala修复bug)。与使用metascala的实验HList进行异构集合的复杂性相比,结合更为直观。
#11
5
We’d like a type operator Or[U,V]
that can be used to constrain a type parameters X
in such a way that either X <: U
or X <: V
. Here's a definition that comes about as close as we can get:
我们想要一个类型运算符,或者[U,V]可以用来约束一个类型参数X,在这样的方式下,X <: U或X <: V。这里有一个定义,我们可以得到如下的定义:
trait Inv[-X]
type Or[U,T] = {
type pf[X] = (Inv[U] with Inv[T]) <:< Inv[X]
}
Here is how it's used:
下面是它的用法:
// 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!
This uses a few Scala type tricks. The main one is the use of generalized type constraints. Given types U
and V
, the Scala compiler provides a class called U <:< V
(and an implicit object of that class) if and only if the Scala compiler can prove that U
is a subtype of V
. Here’s a simpler example using generalized type constraints that works for some cases:
这使用了一些Scala类型的技巧。主要是使用广义类型约束。鉴于类型U和V,Scala编译器提供了一个类称为U <:< V(和一个隐式对象的类)当且仅当Scala编译器可以证明你是一个亚型诉这是一个简单的例子使用广义类型约束,适用于某些情况下:
def foo[X](implicit ev : (B with String) <:< X) = {}
This example works when X
an instance of class B
, a String
, or has a type that is neither a supertype nor a subtype of B
or String
. In the first two cases, it’s true by the definition of the with
keyword that (B with String) <: B
and (B with String) <: String
, so Scala will provide an implicit object that will be passed in as ev
: the Scala compiler will correctly accept foo[B]
and foo[String]
.
这个例子适用于类B、字符串或类型既不是超类型也不是B或字符串的子类型的类型。在前两种情况下,用with关键字的定义是正确的(B和String) <: B和(B与String) <: String,因此Scala将提供一个隐式对象,它将作为ev传递:Scala编译器将正确地接受foo[B]和foo[String]。
In the last case, I’m relying on the fact that if U with V <: X
, then U <: X
or V <: X
. It seems intuitively true, and I’m simply assuming it. It’s clear from this assumption why this simple example fails when X
is a supertype or subtype of either B
or String
: for example, in the example above, foo[A]
is incorrectly accepted and foo[C]
is incorrectly rejected. Again, what we want is some kind of type expression on the variables U
, V
, and X
that is true exactly when X <: U
or X <: V
.
在最后一个例子中,我依赖的是如果U与V <: X,那么U <: X或V <: X,这似乎是正确的,我只是假设。从这个假设可以清楚地看出,当X是B或String的超类型或子类型时,这个简单的例子失败了:例如,在上面的例子中,foo[a]被错误地接受,而foo[C]被错误地拒绝了。同样,我们想要的是在变量U, V和X上的某种类型表达式当X <: U或X <: V时,这是正确的。
Scala’s notion of contravariance can help here. Remember the trait trait Inv[-X]
? Because it is contravariant in its type parameter X
, Inv[X] <: Inv[Y]
if and only if Y <: X
. That means that we can replace the example above with one that actually will work:
Scala关于逆变的概念可以在这里有所帮助。还记得这种特质特征吗?因为它在其类型参数X中是逆变的,如果且仅当Y <: X,这意味着我们可以将上面的例子替换为一个实际可行的例子:
trait Inv[-X]
def foo[X](implicit ev : (Inv[B] with Inv[String]) <:< Inv[X]) = {}
That’s because the expression (Inv[U] with Inv[V]) <: Inv[X]
is true, by the same assumption above, exactly when Inv[U] <: Inv[X]
or Inv[V] <: Inv[X]
, and by the definition of contravariance, this is true exactly when X <: U
or X <: V
.
这是因为这个表达式(Inv[U] with Inv[V]) <: Inv[X]是正确的,与上面相同的假设是一样的,当Inv[U] <: Inv[X]或Inv[V] <: Inv[X],并且根据逆变的定义,当X <: U或X <: V时,这是正确的。
It’s possible to make things a little more reusable by declaring a parametrizable type BOrString[X]
and using it as follows:
通过声明一个可参数化类型BOrString[X]并使用它,可以使事情变得更可重用:
trait Inv[-X]
type BOrString[X] = (Inv[B] with Inv[String]) <:< Inv[X]
def foo[X](implicit ev : BOrString[X]) = {}
Scala will now attempt to construct the type BOrString[X]
for every X
that foo
is called with, and the type will be constructed precisely when X
is a subtype of either B
or String
. That works, and there is a shorthand notation. The syntax below is equivalent (except that ev
must now be referenced in the method body as implicitly[BOrString[X]]
rather than simply ev
) and uses BOrString
as a type context bound:
Scala现在将尝试为foo调用的每一个X构造类型BOrString[X],并且当X是B或String的子类型时,将会精确构造类型。这是可行的,还有一个简化符号。下面的语法是等价的(除了ev现在必须在方法主体中被引用为隐式[BOrString[X]]而不是简单的ev),并将BOrString作为一个类型上下文绑定:
def foo[X : BOrString] = {}
What we’d really like is a flexible way to create a type context bound. A type context must be a parametrizable type, and we want a parametrizable way to create one. That sounds like we’re trying to curry functions on types just like we curry functions on values. In other words, we’d like something like the following:
我们真正喜欢的是一种灵活的方式来创建类型上下文绑定。类型上下文必须是可参数化的类型,我们需要一个可参数化的方法来创建一个。这听起来像是我们在尝试在类型上curry函数,就像我们在值上curry函数一样。换句话说,我们想要的东西如下:
type Or[U,T][X] = (Inv[U] with Inv[T]) <:< Inv[X]
That’s not directly possible in Scala, but there is a trick we can use to get pretty close. That brings us to the definition of Or
above:
这在Scala中是不可能的,但是我们可以用一个技巧来接近它。这就引出了或以上的定义:
trait Inv[-X]
type Or[U,T] = {
type pf[X] = (Inv[U] with Inv[T]) <:< Inv[X]
}
Here we use structural typing and Scala’s pound operator to create a structural type Or[U,T]
that is guaranteed to have one internal type. This is a strange beast. To give some context, the function def bar[X <: { type Y = Int }](x : X) = {}
must be called with subclasses of AnyRef
that have a type Y
defined in them:
这里我们使用结构类型和Scala的磅操作符来创建一个结构类型或[U,T],保证有一个内部类型。这是一只奇怪的野兽。为了提供一些上下文,函数def bar[X <: {type Y = Int}](X: X) ={},必须调用具有Y类型定义的AnyRef的子类:
bar(new AnyRef{ type Y = Int }) // works!
Using the pound operator allows us to refer to the inner type Or[B, String]#pf
, and using infix notation for the type operator Or
, we arrive at our original definition of foo
:
使用磅运算符允许我们引用内部类型或[B, String]#pf,并使用infix符号来表示类型操作符,或者,我们到达了我们最初定义的foo:
def foo[X : (B Or String)#pf] = {}
We can use the fact that function types are contravariant in their first type parameter in order to avoid defining the trait Inv
:
我们可以使用函数类型在其第一个类型参数中是逆变的这个事实,以避免定义特征Inv:
type Or[U,T] = {
type pf[X] = ((U => _) with (T => _)) <:< (X => _)
}
#12
4
There is another way which is slightly easier to understand if you do not grok Curry-Howard:
还有另一种方法,如果你不懂,就更容易理解了。
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
I use similar technique in dijon
我在第戎使用了类似的技术。
#13
1
Well, that's all very clever, but I'm pretty sure you know already that the answers to your leading questions are various varieties of "No". Scala handles overloading differently and, it must be admitted, somewhat less elegantly than you describe. Some of that's due to Java interoperability, some of that is due to not wanting to hit edged cases of the type inferencing algorithm, and some of that's due to it simply not being Haskell.
嗯,这都很聪明,但我很确定你已经知道,你的主要问题的答案是各种各样的“不”。Scala处理重载的方式不同,必须承认,这比您描述的要优雅一些。其中一些是由于Java互操作性造成的,其中一些是由于不希望对类型推断算法的边缘案例进行攻击,而其中一些是由于它根本不是Haskell。
#14
1
Adding to the already great answers here. Here's a gist that builds on Miles Sabin union types (and Josh's ideas) but also makes them recursively defined, so you can have >2 types in the union (def foo[A : UNil Or Int Or String Or List[String]
)
在这里加上已经很好的答案。这里有一个根据Miles Sabin union类型(和Josh的想法)构建的要点,但是也使它们被递归地定义,所以您可以在union中拥有>2类型(def foo[a: UNil或Int或String或List[String])
https://gist.github.com/aishfenton/2bb3bfa12e0321acfc904a71dda9bfbb
https://gist.github.com/aishfenton/2bb3bfa12e0321acfc904a71dda9bfbb
NB: I should add that after playing around with the above for a project, I ended up going back to plain-old-sum-types (i.e. sealed trait with subclasses). Miles Sabin union types are great for restricting the type parameter, but if you need to return a union type then it doesn't offer much.
NB:我要补充的是,在完成了上面的项目之后,我最终回到了普通的类型(即带有子类的封闭特性)。Miles Sabin union类型对于限制类型参数非常有用,但是如果您需要返回一个union类型,那么它不会提供太多。
#15
0
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.
可以在程序的其他部分中定义扩展类型Expr的其他案例类(…)。这种可扩展性可以通过声明基类Expr密封来排除;在这种情况下,直接扩展Expr的所有类必须与Expr相同的源文件。