我知道trait Foo[T]
表示T
是参数化类型。
但有时我可以看到trait Foo[T1,T2]
或trait Foo[T1,T2,R]
,我找不到任何描述类型括号内多种类型含义的内容,请您在这种情况下指出用法吗?根据我的推测,Foo [T1,T2]只是意味着,它定义了两个类型参数,它不必采用T1
并返回T2
。
当我今天阅读playframework文档时,我再次发现自己对此问题感到困惑。在文档中,它说:
BodyParser [A]基本上是一个Iteratee [Array [Byte],A],意思是 它接收大块的字节(只要Web浏览器上传一些 数据)并计算类型A的值作为结果。
这个解释听起来像,第二个类型括号内的类型参数是返回类型。
我还记得trait Function2 [-T1, -T2, +R] extends AnyRef
表示采用T1
和T2
的函数,返回R
。
为什么他们将返回类型放在括号中?这是否意味着括号中的所有最后一个参数都是返回类型?或者他们刚刚为返回类型定义了一个新类型R?
答案 0 :(得分:7)
根据我的推测,Foo [T1,T2]只是意味着,它定义了两个类型参数,它不必是
T1
并返回T2
。
类型参数只不过是“我需要任何类型,但我不想知道具体类型是什么”,其中“我”是编写代码的程序员。类型参数可以像任何其他类型一样使用,例如Int,String或Complex - 唯一的区别是它们在使用它们之前是不可知的。
请参阅类型Map[A, +B]
。当您第一次阅读本文时,您无法知道A和B的用途,因此您必须阅读文档:
从类型A的键到类型B的值的映射。
它解释了类型及其含义。没有什么可以了解或理解的。它们只是两种类型。可以将Map调用为类似Map[Key, Value]
的内容,但在源代码内部,当类型参数只有一个或两个字母时更好。这样可以更容易区分类型参数和具体类型。
这是指定类型参数含义的文档。如果没有文档,您必须自己查看源代码并找到它们的含义。例如,您必须使用Function2 [-T1, -T2, +R]
执行此操作。文档只告诉我们:
2个参数的功能。
好的,我们知道三个类型参数中的两个是函数所期望的参数,但第三个参数是什么?我们来看看来源:
def apply(v1:T1,v2:T2):R
啊,现在我们知道T1
和T2
是参数,R是返回类型。
类型参数也可以在方法签名中找到,例如map:
class List[+A] {
..
def map[B](f: (A) ⇒ B): List[B]
}
当您将其与List一起使用时,这就是地图的样子。 A
可以是任何类型 - 它是列表包含的元素的类型。 B
是另一种任意类型。当您知道地图的作用时,您就会知道B
的作用。否则你必须先了解地图。 map需要一个可以将List的每个元素转换为另一个元素的函数。因为您知道A
代表List的元素,所以您可以从自己派生B
必须是A
转换为的类型。
回答所有其他问题:这不应该在一个答案中完成。 StackOverflow上还有很多其他问题和答案,它们也可以回答您的问题。
当您在Foo[T1, T2]
中看到某些类型参数时,您不应该开始哭泣。想想:“好吧,我有一个期待T1和T2的Foo,如果我想知道他们做什么,我必须阅读文档或来源。”
答案 1 :(得分:4)
类型括号内的多个类型意味着对多种类型进行类型参数化。举个例子
trait Pair[A, B]
这是一对值为A
的值,另一个值为B
。
<强>更新强>
我认为你过多地解释了类型参数的语义。由多个参数参数化的类型就是这样,仅此而已。特定类型参数在类型参数列表中的位置不会以任何方式使其特殊。具体而言,类型参数列表中的最后一个参数不需要代表“返回类型”。
您引用的play框架中的句子解释了此特定类型的类型参数的语义。它没有推广到其他类型。对Function
类型也是如此:这里最后一个类型参数恰好表示'返回类型'。但是,对于其他类型,情况不一定如此。上面的类型Pair[A, B]
就是这样一个例子。这里B
是该对的第二个组成部分的类型。这里根本没有“回归类型”的概念。
参数化类型的类型参数可以出现在参数化类型定义内的任何位置,其中可以出现“常规”类型。也就是说,类型参数只是在实例化参数化类型本身时绑定到实际类型的类型的名称。
考虑以下类Tuple的定义:
class Tuple[A, B](a: A, b: B)
它被实例化为一个Int和String元组的类型,如下所示:
type TupleIntString = Tuple[Int, String]
与
基本相同class TupleIntString(a: Int, b: String)
对于官方来源,请查看Scala Language Specification。具体来说,第4节中的第3.4节“基本类型和成员定义”说:“参数化类型C [T_1,...,T_n]的基本类型是类型C的基本类型,其中每次出现的类型为C的类型参数a_i已被相应的参数类型T_i替换。“
答案 2 :(得分:4)
我认为你的问题实际上可以在三个不同的问题中解决:
一个典型的例子是从一种对象到另一种对象的映射。如果希望键的类型与值的类型不同,但保持通用,则需要两个类型参数。因此,Map [A,B]获取泛型类型A的键并映射到泛型类型B的值。想要从书签到页面的映射的用户将其声明为Map [Bookmark,Page]。只有一个类型参数不允许这种区分。
根据我的推测,Foo [T1,T2]只是意味着,它定义了两个类型参数,它不必采用T1并返回T2。
不,所有类型参数都是平等的公民,尽管它们在功能对象的那个方向上有意义。见下文。
它们限制了类型参数可以绑定的内容。 Scala by Example教程有一个很好的解释。请参见第8.2节“方差注释”。
为什么他们将返回类型放在括号中?这是否意味着全部 括号中的最后一个参数是返回类型?或者他们刚刚发生 为返回类型定义了一个新类型R?
Scala by Example教程在第8.6节“函数”中对此进行了解释。
答案 3 :(得分:1)
它们的作用有点类似于类中的那些(即多个类型参数),因为traits毕竟是类(没有任何构造函数)意味着作为mixin添加到其他类中。
Scala spec为具有多个参数的Trait提供以下示例:
考虑一个抽象类Table,它实现从一种键A到一种值B的映射 该类有一个方法设置为在表中输入新的键/值对,方法get返回与给定键匹配的可选值。
最后,有一个方法应用,就像get,除了它返回一个给定的默认值,如果表没有找到给定的键。该类实现如下。
abstract class Table[A, B](defaultValue: B) {
def get(key: A): Option[B]
def set(key: A, value: B)
def apply(key: A) = get(key) match {
case Some(value) => value
case None => defaultValue
}
}
这是Table类的具体实现。
class ListTable[A, B](defaultValue: B) extends Table[A, B](defaultValue) {
private var elems: List[(A, B)]
def get(key: A) = elems.find(._1.==(key)).map(._2)
def set(key: A, value: B) = { elems = (key, value) :: elems }
}
这是一个阻止并发访问其get和set操作的特征 父类
trait Synchronized Table[A, B] extends Table[A, B] {
abstract override def get(key: A): B =
synchronized { super.get(key) }
abstract override def set((key: A, value: B) =
synchronized { super.set(key, value) }
}
请注意,
SynchronizedTable
不会将参数传递给其超类Table
, 尽管Table
是用形式参数定义的 另请注意,SynchronizedTable
的get和set方法中的超级调用静态引用类Table
中的抽象方法。这是合法的,只要调用方法标记为抽象覆盖(第5.2节)。最后,以下mixin组合创建了一个同步列表 字符串作为键,整数作为值,默认值为0:
object MyTable extends ListTable[String, Int](0) with SynchronizedTable
对象
MyTable
从SynchronizedTable
继承其get和set方法 这些方法中的超级调用被重新绑定以引用ListTable
中的相应实现,这是SynchronizedTable
中的实际超类型。MyTable
。