Scala:为什么我的 Scala 测试自定义匹配器无法编译?
Scala: Why does my Scala Test custom matcher not compile?
我正在做书中的练习 Learning Scala,有一个问题问:
Create a container class that has an instance of itself plus an
instance of a parameterized type. The constructor should take a
variable number of the instances (e.g., strings or ints or any other
parameterized type), which can be implemented with vararg parameters.
我得出以下结论:
sealed abstract class MyList[A]
sealed class MyNil[A] extends MyList[A]
sealed case class Cons[A](h: A, t: MyList[A]) extends MyList[A]
object MyNil {
def apply[A] = new MyNil[A]
}
object MyList {
def apply[A](items: A*): MyList[A] = {
items match {
case h :: t => Cons[A](h, apply(t: _ *))
case _ => MyNil[A]
}
}
}
到目前为止,还不错。问题在于测试此代码。以下使用 Scala 测试和自定义匹配器的测试无法编译,更具体地说是 isEqual
方法。你能帮我理解为什么吗?
class MyListSpec extends FlatSpec with Matchers {
"MyList" should "be instantiated as expected" in {
val inputAndOutput = Table(
("ip", "op"),
(Nil, MyNil[Int]),
(List(1), Cons[Int](1, MyNil[Int])),
(List(1, 2), Cons[Int](1, Cons[Int](2, MyNil[Int]))),
(List(1, 2, 3), Cons[Int](1, Cons[Int](2, Cons[Int](3, MyNil[Int]))))
)
forAll(inputAndOutput) { (ip, op) =>
val o = MyList(ip: _ *)
println(o)
o should equal(op)
}
}
def equal[Int](right: MyList[Int]) = new MyListMatcher(right)
}
class MyListMatcher[Int, A <: MyList[Int]](val right: A) extends Matcher[A] {
override def apply(left: A): MatchResult = {
MatchResult(
isEqual(left),
s"""MyList $left did not match "$right"""",
s"""MyList $left matched "$right""""
)
}
def isEqual[A <: MyList[Int]](left: A) = {
left match {
case _: MyNil[Int] if right.isInstanceOf[MyNil[Int]] => println("Both are Nil."); true
case Cons[Int](h, t) if right.isInstanceOf[Cons[Int]] => {
println("Both are Cons.")
left == right
}
case _ => println("No match."); false
}
}
}
编辑 1:
根据要求包含来自 IntelliJ 的所有编译错误消息:
Error:(31, 40) inferred type arguments [Nothing,name.abhijitsarkar.scala.MyList[Int]] do not conform to class MyListMatcher's type parameter bounds [Int,A <: name.abhijitsarkar.scala.MyList[Int]]
def equal[Int](right: MyList[Int]) = new MyListMatcher(right)
^
Error:(31, 58) type mismatch;
found : name.abhijitsarkar.scala.MyList[Int]
required: A
def equal[Int](right: MyList[Int]) = new MyListMatcher(right)
^
Error:(27, 9) overloaded method value should with alternatives:
(endWithWord: org.scalatest.words.EndWithWord)(implicit ev: <:<[name.abhijitsarkar.scala.MyList[scala.Int],String])MyListSpec.this.ResultOfEndWithWordForString <and>
(startWithWord: org.scalatest.words.StartWithWord)(implicit ev: <:<[name.abhijitsarkar.scala.MyList[scala.Int],String])MyListSpec.this.ResultOfStartWithWordForString <and>
(includeWord: org.scalatest.words.IncludeWord)(implicit ev: <:<[name.abhijitsarkar.scala.MyList[scala.Int],String])MyListSpec.this.ResultOfIncludeWordForString <and>
(notExist: org.scalatest.words.ResultOfNotExist)(implicit existence: org.scalatest.enablers.Existence[name.abhijitsarkar.scala.MyList[scala.Int]])Unit <and>
(existWord: org.scalatest.words.ExistWord)(implicit existence: org.scalatest.enablers.Existence[name.abhijitsarkar.scala.MyList[scala.Int]])Unit <and>
(containWord: org.scalatest.words.ContainWord)org.scalatest.words.ResultOfContainWord[name.abhijitsarkar.scala.MyList[scala.Int]] <and>
(haveWord: org.scalatest.words.HaveWord)MyListSpec.this.ResultOfHaveWordForExtent[name.abhijitsarkar.scala.MyList[scala.Int]] <and>
(beWord: org.scalatest.words.BeWord)MyListSpec.this.ResultOfBeWordForAny[name.abhijitsarkar.scala.MyList[scala.Int]] <and>
(inv: org.scalactic.TripleEqualsSupport.TripleEqualsInvocationOnSpread[name.abhijitsarkar.scala.MyList[scala.Int]])(implicit ev: Numeric[name.abhijitsarkar.scala.MyList[scala.Int]])Unit <and>
[U](inv: org.scalactic.TripleEqualsSupport.TripleEqualsInvocation[U])(implicit constraint: org.scalactic.Constraint[name.abhijitsarkar.scala.MyList[scala.Int],U])Unit <and>
(notWord: org.scalatest.words.NotWord)org.scalatest.words.ResultOfNotWordForAny[name.abhijitsarkar.scala.MyList[scala.Int]] <and>
[TYPECLASS1[_], TYPECLASS2[_]](rightMatcherFactory2: org.scalatest.matchers.MatcherFactory2[name.abhijitsarkar.scala.MyList[scala.Int],TYPECLASS1,TYPECLASS2])(implicit typeClass1: TYPECLASS1[name.abhijitsarkar.scala.MyList[scala.Int]], implicit typeClass2: TYPECLASS2[name.abhijitsarkar.scala.MyList[scala.Int]])Unit <and>
[TYPECLASS1[_]](rightMatcherFactory1: org.scalatest.matchers.MatcherFactory1[name.abhijitsarkar.scala.MyList[scala.Int],TYPECLASS1])(implicit typeClass1: TYPECLASS1[name.abhijitsarkar.scala.MyList[scala.Int]])Unit <and>
(rightMatcherX1: org.scalatest.matchers.Matcher[name.abhijitsarkar.scala.MyList[scala.Int]])Unit
cannot be applied to (name.abhijitsarkar.scala.MyListMatcher[Int(in class MyListMatcher),A])
o should equal(op)
^
Error:(46, 21) name.abhijitsarkar.scala.Cons[Int] does not take parameters
case Cons[Int](h, t) if right.isInstanceOf[Cons[Int]] => {
^
回答我自己的问题,下面的测试代码有效。问题是在方法 isEqual
.
中不包含 Int
类型参数和 Cons
大小写
class MyListSpec extends FlatSpec with Matchers {
"MyList" should "be instantiated as expected" in {
val inputAndOutput = Table(
("ip", "op"),
(Nil, MyNil[Int]),
(List(1), Cons[Int](1, MyNil[Int])),
(List(1, 2), Cons[Int](1, Cons[Int](2, MyNil[Int]))),
(List(1, 2, 3), Cons[Int](1, Cons[Int](2, Cons[Int](3, MyNil[Int]))))
)
forAll(inputAndOutput) { (ip, op) =>
val o = MyList(ip: _ *)
println(o)
o should equal(op)
}
}
def equal(right: MyList[Int]) = new MyListMatcher(right)
}
class MyListMatcher(val right: MyList[Int]) extends Matcher[MyList[Int]] {
override def apply(left: MyList[Int]): MatchResult = {
MatchResult(
isEqual(left, right),
s"""MyList $left did not match "$right"""",
s"""MyList $left matched "$right""""
)
}
def isEqual(left: MyList[Int], right: MyList[Int]): Boolean = {
left match {
case _: MyNil[Int] if right.isInstanceOf[MyNil[Int]] => println("Both are Nil."); true
case Cons(h, t) if right.isInstanceOf[Cons[Int]] => {
println("Both are Cons.")
val r = right.asInstanceOf[Cons[Int]]
h == r.h && isEqual(t, r.t)
}
case _ => println("No match."); false
}
}
}
你的代码的问题是这里的这一行:
def equal[Int](right: MyList[Int]) = new MyListMatcher(right)
TL;DR
改变这个:
class MyListMatcher[Int, A <: MyList[Int]](val right: A) extends Matcher[A] {
为此:
class MyListMatcher[A <: MyList[Int]](val right: A) extends Matcher[A] {
完整说明
您遇到的问题只是 Scala 编译器试图弄清楚您的新 MyListMatcher
实例应该是什么类型参数。
因为你的 MyListMatcher
class 是一个带有两个参数的类型构造函数(一个被硬编码为 Int ,另一个是从构造函数参数推断出来的),Scala 编译器看到了这个并且假设第一个参数是 Nothing 因为它没有在 equal
方法中指定。
只需删除第一个 Int 类型参数,您的问题就会消失
我正在做书中的练习 Learning Scala,有一个问题问:
Create a container class that has an instance of itself plus an instance of a parameterized type. The constructor should take a variable number of the instances (e.g., strings or ints or any other parameterized type), which can be implemented with vararg parameters.
我得出以下结论:
sealed abstract class MyList[A]
sealed class MyNil[A] extends MyList[A]
sealed case class Cons[A](h: A, t: MyList[A]) extends MyList[A]
object MyNil {
def apply[A] = new MyNil[A]
}
object MyList {
def apply[A](items: A*): MyList[A] = {
items match {
case h :: t => Cons[A](h, apply(t: _ *))
case _ => MyNil[A]
}
}
}
到目前为止,还不错。问题在于测试此代码。以下使用 Scala 测试和自定义匹配器的测试无法编译,更具体地说是 isEqual
方法。你能帮我理解为什么吗?
class MyListSpec extends FlatSpec with Matchers {
"MyList" should "be instantiated as expected" in {
val inputAndOutput = Table(
("ip", "op"),
(Nil, MyNil[Int]),
(List(1), Cons[Int](1, MyNil[Int])),
(List(1, 2), Cons[Int](1, Cons[Int](2, MyNil[Int]))),
(List(1, 2, 3), Cons[Int](1, Cons[Int](2, Cons[Int](3, MyNil[Int]))))
)
forAll(inputAndOutput) { (ip, op) =>
val o = MyList(ip: _ *)
println(o)
o should equal(op)
}
}
def equal[Int](right: MyList[Int]) = new MyListMatcher(right)
}
class MyListMatcher[Int, A <: MyList[Int]](val right: A) extends Matcher[A] {
override def apply(left: A): MatchResult = {
MatchResult(
isEqual(left),
s"""MyList $left did not match "$right"""",
s"""MyList $left matched "$right""""
)
}
def isEqual[A <: MyList[Int]](left: A) = {
left match {
case _: MyNil[Int] if right.isInstanceOf[MyNil[Int]] => println("Both are Nil."); true
case Cons[Int](h, t) if right.isInstanceOf[Cons[Int]] => {
println("Both are Cons.")
left == right
}
case _ => println("No match."); false
}
}
}
编辑 1: 根据要求包含来自 IntelliJ 的所有编译错误消息:
Error:(31, 40) inferred type arguments [Nothing,name.abhijitsarkar.scala.MyList[Int]] do not conform to class MyListMatcher's type parameter bounds [Int,A <: name.abhijitsarkar.scala.MyList[Int]]
def equal[Int](right: MyList[Int]) = new MyListMatcher(right)
^
Error:(31, 58) type mismatch;
found : name.abhijitsarkar.scala.MyList[Int]
required: A
def equal[Int](right: MyList[Int]) = new MyListMatcher(right)
^
Error:(27, 9) overloaded method value should with alternatives:
(endWithWord: org.scalatest.words.EndWithWord)(implicit ev: <:<[name.abhijitsarkar.scala.MyList[scala.Int],String])MyListSpec.this.ResultOfEndWithWordForString <and>
(startWithWord: org.scalatest.words.StartWithWord)(implicit ev: <:<[name.abhijitsarkar.scala.MyList[scala.Int],String])MyListSpec.this.ResultOfStartWithWordForString <and>
(includeWord: org.scalatest.words.IncludeWord)(implicit ev: <:<[name.abhijitsarkar.scala.MyList[scala.Int],String])MyListSpec.this.ResultOfIncludeWordForString <and>
(notExist: org.scalatest.words.ResultOfNotExist)(implicit existence: org.scalatest.enablers.Existence[name.abhijitsarkar.scala.MyList[scala.Int]])Unit <and>
(existWord: org.scalatest.words.ExistWord)(implicit existence: org.scalatest.enablers.Existence[name.abhijitsarkar.scala.MyList[scala.Int]])Unit <and>
(containWord: org.scalatest.words.ContainWord)org.scalatest.words.ResultOfContainWord[name.abhijitsarkar.scala.MyList[scala.Int]] <and>
(haveWord: org.scalatest.words.HaveWord)MyListSpec.this.ResultOfHaveWordForExtent[name.abhijitsarkar.scala.MyList[scala.Int]] <and>
(beWord: org.scalatest.words.BeWord)MyListSpec.this.ResultOfBeWordForAny[name.abhijitsarkar.scala.MyList[scala.Int]] <and>
(inv: org.scalactic.TripleEqualsSupport.TripleEqualsInvocationOnSpread[name.abhijitsarkar.scala.MyList[scala.Int]])(implicit ev: Numeric[name.abhijitsarkar.scala.MyList[scala.Int]])Unit <and>
[U](inv: org.scalactic.TripleEqualsSupport.TripleEqualsInvocation[U])(implicit constraint: org.scalactic.Constraint[name.abhijitsarkar.scala.MyList[scala.Int],U])Unit <and>
(notWord: org.scalatest.words.NotWord)org.scalatest.words.ResultOfNotWordForAny[name.abhijitsarkar.scala.MyList[scala.Int]] <and>
[TYPECLASS1[_], TYPECLASS2[_]](rightMatcherFactory2: org.scalatest.matchers.MatcherFactory2[name.abhijitsarkar.scala.MyList[scala.Int],TYPECLASS1,TYPECLASS2])(implicit typeClass1: TYPECLASS1[name.abhijitsarkar.scala.MyList[scala.Int]], implicit typeClass2: TYPECLASS2[name.abhijitsarkar.scala.MyList[scala.Int]])Unit <and>
[TYPECLASS1[_]](rightMatcherFactory1: org.scalatest.matchers.MatcherFactory1[name.abhijitsarkar.scala.MyList[scala.Int],TYPECLASS1])(implicit typeClass1: TYPECLASS1[name.abhijitsarkar.scala.MyList[scala.Int]])Unit <and>
(rightMatcherX1: org.scalatest.matchers.Matcher[name.abhijitsarkar.scala.MyList[scala.Int]])Unit
cannot be applied to (name.abhijitsarkar.scala.MyListMatcher[Int(in class MyListMatcher),A])
o should equal(op)
^
Error:(46, 21) name.abhijitsarkar.scala.Cons[Int] does not take parameters
case Cons[Int](h, t) if right.isInstanceOf[Cons[Int]] => {
^
回答我自己的问题,下面的测试代码有效。问题是在方法 isEqual
.
Int
类型参数和 Cons
大小写
class MyListSpec extends FlatSpec with Matchers {
"MyList" should "be instantiated as expected" in {
val inputAndOutput = Table(
("ip", "op"),
(Nil, MyNil[Int]),
(List(1), Cons[Int](1, MyNil[Int])),
(List(1, 2), Cons[Int](1, Cons[Int](2, MyNil[Int]))),
(List(1, 2, 3), Cons[Int](1, Cons[Int](2, Cons[Int](3, MyNil[Int]))))
)
forAll(inputAndOutput) { (ip, op) =>
val o = MyList(ip: _ *)
println(o)
o should equal(op)
}
}
def equal(right: MyList[Int]) = new MyListMatcher(right)
}
class MyListMatcher(val right: MyList[Int]) extends Matcher[MyList[Int]] {
override def apply(left: MyList[Int]): MatchResult = {
MatchResult(
isEqual(left, right),
s"""MyList $left did not match "$right"""",
s"""MyList $left matched "$right""""
)
}
def isEqual(left: MyList[Int], right: MyList[Int]): Boolean = {
left match {
case _: MyNil[Int] if right.isInstanceOf[MyNil[Int]] => println("Both are Nil."); true
case Cons(h, t) if right.isInstanceOf[Cons[Int]] => {
println("Both are Cons.")
val r = right.asInstanceOf[Cons[Int]]
h == r.h && isEqual(t, r.t)
}
case _ => println("No match."); false
}
}
}
你的代码的问题是这里的这一行:
def equal[Int](right: MyList[Int]) = new MyListMatcher(right)
TL;DR
改变这个:
class MyListMatcher[Int, A <: MyList[Int]](val right: A) extends Matcher[A] {
为此:
class MyListMatcher[A <: MyList[Int]](val right: A) extends Matcher[A] {
完整说明
您遇到的问题只是 Scala 编译器试图弄清楚您的新 MyListMatcher
实例应该是什么类型参数。
因为你的 MyListMatcher
class 是一个带有两个参数的类型构造函数(一个被硬编码为 Int ,另一个是从构造函数参数推断出来的),Scala 编译器看到了这个并且假设第一个参数是 Nothing 因为它没有在 equal
方法中指定。
只需删除第一个 Int 类型参数,您的问题就会消失