什么是Scala的“强大”类型系统?

30
当讨论Scala时,其类型系统总是被提及为其主要特点之一。它被称为强大,也是语言名称的主要原因(Scala代表“可扩展语言”)。请问有人能够解释Scala的类型系统如何运作/为什么独特,并说明它如何促进了该语言的可扩展性吗?

Scala 类型系统的所有必要知识都在这里:链接 - Daniel Pereira
2
"Scala" 是 "可伸缩语言" 的简称,而不是 "可伸缩架构"。 - missingfaktor
5个回答

46
我认为现有的答案都不合适。Scala有很多便利,但它们与类型系统强大无关,因为它们涉及到类型。事实上,类型推断与类型系统的强大是直接冲突的--如果它不够强大,就可以拥有完全的类型推断(就像Haskell一样)。
所以,
  • Scala具有具有成员的类。(显而易见,但我在这里尝试详尽。)
  • Scala类的方法(“def”)成员可以有零个或多个参数列表,每个列表可以有零个或多个参数,最后一个可以是可变参数。
  • 参数可以按值或按名称传递。
  • 参数有名称,可能有默认值。
  • Scala具有“var”和“val”成员(在实践中也是方法)。
  • Scala具有“lazy val”成员。
  • Scala具有“type”成员(类型别名),可以指定为固定类型或类型边界。
  • Scala具有抽象类和成员(所有上述成员都可以是抽象的)。
  • Scala具有内部类、特质和对象(Scala的内部类与Java的不同)。
  • Scala的成员,加上内部内容,可以被覆盖。
  • Scala具有类型继承。
  • Scala具有特质,提供类型线性化的多重继承。
  • Scala的特质的方法成员可能具有抽象覆盖(可堆叠,类似于方面)。
  • Scala具有单例类型。
  • Scala具有伴随类/对象(与范围相关)。
  • Scala对于类、特质、单例和成员有私有、受保护和公共访问权限。
  • Scala的私有和受保护访问范围可以限制到任何封闭的包、类、特质或单例,加上“this”。
  • Scala具有自类型。
  • Scala具有类型参数。
  • Scala的类型参数可以是协变、逆变以及不变的。
  • Scala具有类型构造器。
  • Scala具有高阶类型。
  • Scala具有存在类型。
  • Scala具有结构类型。
  • Scala拥有隐式参数。
  • Scala拥有函数类型,不过由于它们只是一个类再加上语法糖,我认为它并不属于这个列表。但是,函数类型是视图界限的一部分,所以也许它应该归在这里。
  • Scala有一个顶部(几乎每个人都有)和一个底部(像其他静态类型的fp语言一样)。
  • Scala的“unit”是一个具有值的类型(与其他地方的“void”不同)。
  • 接下来,与Scala的隐式相关的特性,这是其包含在上面的原因。

    • Scala有视图界限,这是一个作为另一个类型限制的隐式参数。
    • Scala有上下文界限,这是一个作为另一个限制的隐式参数。
    • 通常情况下,隐式参数和类型推断可以结合起来构建任意复杂的类型参数证明。

    与最后一条评论相关的是,隐式和类型推理结合在一起,使得Scala的类型系统是Turing完备的。也就是说,您可以将任意程序编码为类型,并在编译器在编译时“运行”。通过SKI演算表明证明可参见这里,此外还有一个“错误”的无限循环类型作为进一步证明。

    上面列出的功能列表在许多方面都非常强大和令人印象深刻。但是,Scala将隐式和类型推理结合起来,在编译时生成静态证明(例如视图界限和上下文界限),这使得Scala的类型系统变得独特。据我所知,没有其他语言可以做到这一点,尽管肯定有其他语言通过其他方式提供证明能力。


    1
    这是一个很好的答案,尽管我怀疑任何不了解Scala的人都无法完全欣赏它。无论如何,我很喜欢它。顺便说一下,有一个小错别字:“contra-variantm” -> “contra-variant”。 - romusz
    @romusz 谢谢,已修复。是的,我同意你的看法。只有亲身体验过,才能理解它有多酷。为了吸引人们,Scala 必须依靠眼花缭乱的语法糖、类型推断和它所拥有的强大库(这些库都是由类型系统支持的)。 - Daniel C. Sobral
    @Andreas 类型系统的能力取决于其表达能力。其中一些东西只是一些功能,但相当多的东西使人能够表达在不那么强大的类型系统中无法表达的事物。此外,我并不是说类型推断不重要 - 只是它不是类型本身的一部分。 - Daniel C. Sobral
    1
    @wberry OO也太难了。我承认尽可能让事情变得“尽可能简单”是更好的,但这并不一定能使它变得更简单。例如,考虑List上的sum方法。Scala的类型系统保证只有在List元素为数字时才会调用它。这不是Java可以做到的类型检查。我认为C++也可以做到,但是,再次提醒,C++的模板也是图灵完备的。 - Daniel C. Sobral
    2
    @wberry 好的,这里是汉诺塔编译时代码,希望能帮到你。 :-) - Daniel C. Sobral
    显示剩余5条评论

    20
    Scala的类型系统相较于Java有以下几个优点:
    1. 类型通常可以被推断,而不需要显式地指定。这更多是一种便利,但它促进了使用复杂的类型。

      val map = new Map[String, (String, String)]()

      而不是

      Map<String, Tuple<String, String>> map = new HashMap<String, Tuple<String, String>>()

    2. 函数可以在类型系统中简单地表达。如果您想了解其强大之处,请考虑 Guava 库作为 Java 的解决方案。它非常受限且冗长(但仍有用)。

      val double = (x: Int) => x * 2

      而不是(使用 Guava)

      Function<Integer, Integer> double = new Function<Integer, Integer>() { @Override public Integer apply(Integer value) { return value * 2; }}

    3. 元组是 Scala 中的一种类型,绕过了 Java 只能返回单个值的问题。
    4. Scala 支持类型 Variances,因此您可以指定当 Cat 是 Thing 的子类型时,SomeObject 是 SomeObject 的子类型(或反之亦然)。在 Java 中,泛型不是协变的,这经常会引起问题。
    5. Scala 支持使用 traits 一种有限的多重继承形式。与可以在 Java 中实现多个接口不同的是,特质可以定义方法和变量。
    6. 数组被透明地处理,就像任何其他类一样。
    7. 您可以通过隐式定义向现有类添加 方法。例如,您可以向整数数组添加 "sum" 方法。
    class IntArray(value: Array[Int]) { def sumIt = value.reduceRight(_+_) }
    implicit def pimpArray(xs: Array[Int]) = new IntArray(xs)
    Array(1,2,3).sumIt
    

    这是另一个很好的资源,可以了解上述主题的一些内容:http://www.codecommit.com/blog/scala/scala-for-java-refugees-part-5


    Java 中存在使用点方差。请参见 http://en.wikipedia.org/wiki/Covariance_and_contravariance_%28computer_science%29#Java - Kim Stebel
    你的求和例子将无法正常工作,因为已经有一个可用的 sum 方法,它将与你定义的方法产生冲突。 - soc
    总体来说,回答不错,但对于上面的第3点:重要的是不要混淆返回元组和返回“多个值”。在这些情况下,函数返回类型为tuple_N的单个值。在Java中解决这个问题的方法也是相同的,只是程序员必须为这些值定义一个包装类。 - Connor Doyle
    @ConnorDoyle,你说得有道理。我觉得创建一个新类太重了,以至于Java不能返回多个值。虽然创建一个具有智能命名成员的类(而不是_1和_2)通常是一个好主意,但在许多情况下它只是过度设计了。无论如何,我完全承认两种语言只能从技术上返回一个值。 - schmmd
    @soc,感谢您指出这一点。我编辑了我的答案,使它更加牵强(sum -> sumIt)。 - schmmd
    这是一个有趣的答案,但如果与C#进行比较,唯一提到C#没有的是traits。从其他来源得知,Scala的类型系统远不止于此... - Roman Starkov

    6
    除了schmmd的出色回答之外,Scala的类型系统还具有更重要的特点:
    • object是Java中static成员变量和方法的清晰替代品,例如,一个object具有自己的类型并可作为参数传递
    • type声明:您可以为复杂类型定义别名,例如type FactorMap[A] = Map[A, Set[Int]]
    • 抽象类型成员作为泛型样式类型的替代方案
    • 自身类型
    • 结构类型
    • 用于柯里化的多个参数列表
    • 隐式参数和转换,连同视图界限。这导致了“pimp my library”模式,并可用于模拟Haskell风格的类型类
    • 高阶类型

    最后一点是我最喜欢的之一。例如,您无法在Java中编写简单的通用函子接口。你会需要

    public interface Function<A,B> {
       public B apply(A a);
    }
    
    //not valid Java
    public interface Functor<C> {
       public <A,B> C<B> map(Function<A,B> fn, C<A> ca);
    }
    

    如果您用像List这样的具体类型替换C,它就能正常工作。在Java中,您可以通过编写`List'来抽象容器的内容,但是无法抽象容器本身。相信我,我试图寻找漏洞(结果是这个)。在Scala中,这很容易:

    trait Functor[C[_]] {
       def map[A,B](fn: A => B, ca: C[A]):C[B]
    }
    
    object ListFunctor extends Functor[List] {
       def map[A,B](fn: A => B, ca: List[A]):List[B] = ca.map(fn)
    }
    

    3

    0

    我不知道你是否了解Java,但是想象一下Scala的类型系统就像这样:

    • 消除Java对类型和泛型的人为限制
    • 将函数式语言的常见特征添加到其中
    • 在oop /继承方面进行创新

    我本想写更多,但我的键盘刚坏了,抱歉!


    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接