scala如何让数组的元素一个一个输入?

scala如何让数组的元素一个一个输入?,第1张

val arr = new Array[Int](5) // 创建包含 5 个整数的数组

for (i <- 0 until arr.length) {

println(s"请输入第 ${i+1} 个元素:")

arr(i) = scala.io.StdIn.readInt()

}

这段代码会依次提示用户输入每个数组元素的值,并将其存储到数组中。请注意,until 方法用于生成一个包括起始值但不包括结束值的区间,因此 0 until arr.length 将生成一个范围为 [0, arr.length) 的整数序列,即 0, 1, 2, 3, 4,正好对应数组的 5 个元素。

「型变(Variance)」是一个令人费解的概念,但它却是理解类型系统的重要基石。本文首先讨论型变的基本概念,深入理解型变的基本形态。然后以 List, Option 为例讲解型变在 Scala 中的应用;最后通过 ScalaHamcrest 的实战,加深对此概念的理解和运用。

其中, Mutable 常常意味着 Nonvariant ,但是 Noncovariant 与 Mutable 分别表示两个不同的范畴。

「型变(Variance)」拥有三种基本形态:协变(Covariant), 逆变(Contravariant), 不变(Nonconviant),可以形式化地描述为:

Scala 的类型参数使用 + 标识「协变」, - 标识「逆变」,而不带任何标识的表示「不变」(Nonvariable)。

事实上,判定一个类型是否拥有型变能力的准则非常简单。

Supplier 是一个生成者,它生产 T 类型的实例。

Consumer 是一个消费者,它消费 T 类型的实例。

Function1 是一个一元函数,它既是一个生产者,又是一个消费者,但它是不可变的(Immutable)。其中,入参类型为 -T ,返回值类型为 +R ;对于参数类型,函数是逆变的,而对于返回值类型,函数则是协变的。

与 Function1 不同,虽然数组类型既是一个生产者,又是一个消费者。但是,它是一个可变的(Mutable)类型,因此它是不变的(Nonvariant)。

综上述,可以得到 2 个简单的结论。

幸运的是, Scala 编译器能够完成这个约束的检查。例如,

编译器将检测到编译错误。

例如,给定两个函数 F1, F2 。

则 F1 <: F2 成立。

Option 是一个递归的数据结构,它要么是 Some ,要么是 None 。其中, None 表示为空,是递归结束的标识。

使用 Scala ,可以很直观地完成 Option 的递归定义。

因为 Option 是不可变的(Immutable),因此 Option 应该设计为协变的,即 Option[+A] 。也就是说,对于任意的类型 A , Option[Nothing] <: Option[A] ,即 None <: Option[A] 都成立。

与 Option 类似, List 也是一个递归的数据结构,它由头部和尾部组成。其中, Nil 表示为空,是递归结束的标识。

使用 Scala ,可以很直观地完成 List 的递归定义。

因为 List 是不可变的(Immutable),因此 List 应该设计为协变的,即 List[+A] 。也就是说,对于任意的类型 A , List[Nothing] <: List[A] ,即 Nil <: List[A] 都成立。

可以在 List 中定义了 cons 算子,用于在 List 头部追求元素。

此时,编译器将报告协变类型 A 出现在逆变的位置上的错误。因此,在遵循「里氏替换」的基本原则,使用「下界(Lower Bound)」对 A 进行界定,转变为「不变的(Nonvariable)」的类型参数 A1 。

至此,又可以得到一个重要的结论。

List 的 cons 算子就是通过使用「下界」界定协变类型参数 A ,将其转变为不变的(Nonvariable)类型参数 A1 的。而对于「上界」,通过实现 ScalaHamcrest 的基本功能进行讲述,并完成整个型变理论知识的回顾和应用。

对于任意的类型 A , A =>Boolean 常常称为「谓词」;如果该谓词用于匹配类型 A 的某个值,也常常称该谓词为「匹配器」。

ScalaHamcrest 首先定义一个 Matcher ,并添加了 &&, ||, ! 的基本 *** 作,用于模拟谓词的基本功能。

对于函数 A =>Boolean ,类型参数 A 是逆变的。因此,为了得到支持型变能力的 Matcher ,应该将类型参数 A 声明为逆变。

但是,此时 &&, || 将报告逆变类型 A 出现在协变的位置上。为此,可以使用「上界」对 A 进行界定,转变为不变的(Nonvariant)类型 A1 。

基于 Matcher ,可以定义特定的原子匹配器。例如:

也可以定义 EqualTo 的原子匹配器,用于比较对象间的相等性。

与 EqualTo 类似,可以定义原子匹配器 Same ,用于比较对象间的一致性。

其中, A <: AnyRef类型 对 A 进行界定,排除 AnyVal 的子类误 *** 作 Same 。类似于类型上界,也可以使用其他的类型界定形式;例如,可以定义 InstanceOf ,对类型 A 进行上下文界定,用于匹配某个实例的类型。

有时候,基于既有的原子可以很方便地构造出新的原子。

也可以将各个原子或者组合器进行组装,形成威力更为强大的组合器。

特殊地,基于 AnyOf/AllOf ,可以构造很多特定的匹配器。

修饰也是一种特殊的组合行为,用于完成既有功能的增强和补充。

其中, Not, Is 是两个普遍的修饰器,可以修饰任意的匹配器;也可以定义针对特定类型的修饰器。例如,可以定义针对字符串 *** 作的原子匹配器和修饰匹配器。

如果要忽略大小写,则可以通过定义 IgnoringCase ,修饰既有的字符串的原子匹配器。

有时候,可以通过定义语法糖,提升用户感受。例如,可以使用 Not 替换 Not(EqualTo) , Is 替代 Is(EqualTo) ,不仅减轻用户的负担,而且还能提高表达力。

至此,还不知道 ScalaHamcrest 如何使用呢?可以定义一个实用方法 assertThat 。

其中, assert 定义于 Predef 之中。例如存在如下一个测试用例。

也可以使用 &&直接连接多个匹配器形成调用链,替代 AllOf 匹配器。

此处为了演示「型变」的作用, ScalaHamcrest 采用了 OO 与 FP 相结合的设计手法,在下一章讲解「Scala函数论」时, ScalaHamcrest 将采用纯函数式的设计手法实现,敬请关注。


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/bake/11960807.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-05-20
下一篇 2023-05-20

发表评论

登录后才能评论

评论列表(0条)

保存