问题描述
问题
当我使用支持类型级编程的库时,我经常发现自己写如下评论(来自 一个例子 由 Paul Snively 在 Strange Loop 2012):
//但是这些无效序列不会编译://isValid(_3 :: _1 :: _5 :: _8 :: _8 :: _2 :: _8 :: _6 :: _5 :: HNil)//isValid(_3 :: _4 :: _5 :: _8 :: _8 :: _2 :: _8 :: _6 :: HNil)
或者这个,来自 Shapeless 存储库中的">示例:
/*** 如果我们想确认列表唯一包含 `Foo` 或任何* `Foo` 的子类型,我们可以先使用 `unifySubtypes` 向上转换任何* 列表中`Foo`的子类型到`Foo`.** 以下不会编译,例如:*///stuff.unifySubtypes[Foo].unique[Foo]
这是一种非常粗略的方式来表明有关这些方法的行为的一些事实,我们可以想象想要使这些断言更加正式——用于单元或回归测试等.
为了举例说明为什么这在像 Shapeless 这样的库的上下文中可能有用,几天前我写了以下内容作为对 这个问题:
import shapeless._隐式类 Uniqueable[L
这里的目的是编译:
('a' :: 'b :: HNil).unique[Char]
虽然这不会:
('a' :: 'b' :: HNil).unique[Char]
我惊讶地发现 HList
的类型级 unique
的这种实现不起作用,因为 Shapeless 会很高兴地找到一个 FilterAux后一种情况下的代码>实例.换句话说,即使您可能希望它不会编译,以下内容也会编译:
implicitly[FilterAux[Char :: Char :: HNil, Char, Char :: HNil]]
在这种情况下,我看到的是一个错误——或者至少是一些错误——并且它已经修复.>
更一般地说,我们可以想象想要检查隐含在我关于FilterAux
应该如何与单元测试之类的东西一起工作的期望中的那种不变量——很奇怪因为这听起来像是在谈论像这样测试类型级代码,以及最近关于类型与测试的相对优点的所有争论.
我的问题
问题是我不知道有任何类型的测试框架(适用于任何平台)允许程序员断言某些东西不能编译.
对于 FilterAux
情况,我可以想象的一种方法是使用旧的 implicit-argument-with-null-default 技巧:
def assertNoInstanceOf[T](隐式实例:T = null) = assert(实例 == null)
哪个可以让您在单元测试中编写以下内容:
assertNoInstanceOf[FilterAux[Char :: Char :: HNil, Char, Char :: HNil]]
不过,以下内容会更加方便和富有表现力:
assertDoesntCompile(('a' :: 'b' :: HNil).unique[Char])
我想要这个.我的问题是,是否有人知道任何支持远程类似功能的测试库或框架——最适合 Scala,但我会满足于任何要求.
不是框架,而是 Jorge Ortiz (@JorgeO) 提到了他在 2012 年在 NEScala 的 Foursquare 的 Rogue 库的测试中添加的一些实用程序,这些实用程序支持非编译测试:您可以找到示例 这里.很长一段时间以来,我一直想在无形中添加这样的东西.
最近,Roland Kuhn (@rolandkuhn) 添加了类似的机制,这次使用的是 Scala 2.10运行时编译,到 测试 Akka 类型通道.
当然,这些都是动态测试:如果不应该编译的东西发生了,它们会在(测试)运行时失败.无类型宏可能提供静态选项:即.宏可以接受无类型树,对其进行类型检查并在成功时抛出类型错误).这可能是在无形的宏观天堂分支上进行试验的东西.但显然不是 2.10.0 或更早版本的解决方案.
更新
自从回答了这个问题,另一种方法,由于 Stefan Zeiger (@StefanZeiger),已经浮出水面.这个很有趣,因为就像上面提到的无类型宏一样,它是一个编译时而不是(测试)运行时检查,但它也与 Scala 2.10.x 兼容.因此,我认为它比 Roland 的方法更可取.
我现在为 2.9.x 使用 Jorge 的方法,用于 2.10.x 使用 Stefan 的方法 和 使用无类型宏方法的宏天堂.可以找到相应测试的示例 这里是 2.9.x,此处用于 2.10.x 和 这里是宏观天堂.
无类型宏测试是最干净的,但 Stefan 的 2.10.x 兼容方法紧随其后.
The problem
When I'm working with libraries that support type-level programming, I often find myself writing comments like the following (from an example presented by Paul Snively at Strange Loop 2012):
// But these invalid sequences don't compile:
// isValid(_3 :: _1 :: _5 :: _8 :: _8 :: _2 :: _8 :: _6 :: _5 :: HNil)
// isValid(_3 :: _4 :: _5 :: _8 :: _8 :: _2 :: _8 :: _6 :: HNil)
Or this, from an example in the Shapeless repository:
/**
* If we wanted to confirm that the list uniquely contains `Foo` or any
* subtype of `Foo`, we could first use `unifySubtypes` to upcast any
* subtypes of `Foo` in the list to `Foo`.
*
* The following would not compile, for example:
*/
//stuff.unifySubtypes[Foo].unique[Foo]
This is a very rough way of indicating some fact about the behavior of these methods, and we could imagine wanting to make these assertions more formal—for unit or regression testing, etc.
To give a concrete example of why this might be useful in the context of a library like Shapeless, a few days ago I wrote the following as a quick first attempt at an answer to this question:
import shapeless._
implicit class Uniqueable[L <: HList](l: L) {
def unique[A](implicit ev: FilterAux[L, A, A :: HNil]) = ev(l).head
}
Where the intention is that this will compile:
('a' :: 'b :: HNil).unique[Char]
While this will not:
('a' :: 'b' :: HNil).unique[Char]
I was surprised to find that this implementation of a type-level unique
for HList
didn't work, because Shapeless would happily find a FilterAux
instance in the latter case. In other words, the following would compile, even though you'd probably expect it not to:
implicitly[FilterAux[Char :: Char :: HNil, Char, Char :: HNil]]
In this case, what I was seeing was a bug—or at least something bug-ish—and it has since been fixed.
More generally, we can imagine wanting to check the kind of invariant that was implicit in my expectations about how FilterAux
should work with something like a unit test—as weird as it may sound to be talking about testing type-level code like this, with all the recent debates about the relative merit of types vs. tests.
My question
The problem is that I don't know of any kind of testing framework (for any platform) that allows the programmer to assert that something must not compile.
One approach that I can imagine for the FilterAux
case would be to use the old implicit-argument-with-null-default trick:
def assertNoInstanceOf[T](implicit instance: T = null) = assert(instance == null)
Which would let you write the following in your unit test:
assertNoInstanceOf[FilterAux[Char :: Char :: HNil, Char, Char :: HNil]]
The following would be a heck of a lot more convenient and expressive, though:
assertDoesntCompile(('a' :: 'b' :: HNil).unique[Char])
I want this. My question is whether anyone knows of any testing library or framework that supports anything remotely like it—ideally for Scala, but I'll settle for anything.
Not a framework, but Jorge Ortiz (@JorgeO) mentioned some utilities he added to the tests for Foursquare's Rogue library at NEScala in 2012 which support tests for non-compilation: you can find examples here. I've been meaning to add something like this to shapeless for quite a while.
More recently, Roland Kuhn (@rolandkuhn) has added a similar mechanism, this time using Scala 2.10's runtime compilation, to the tests for Akka typed channels.
These are both dynamic tests of course: they fail at (test) runtime if something that shouldn't compile does. Untyped macros might provide a static option: ie. a macro could accept an untyped tree, type check it and throw a type error if it succeeds). This might be something to experiment with on the macro-paradise branch of shapeless. But not a solution for 2.10.0 or earlier, obviously.
Update
Since answering the question, another approach, due to Stefan Zeiger (@StefanZeiger), has surfaced. This one is interesting because, like the untyped macro one alluded to above, it is a compile time rather than (test) runtime check, however it is also compatible with Scala 2.10.x. As such I think it is preferable to Roland's approach.
I've now added implementations to shapeless for 2.9.x using Jorge's approach, for 2.10.x using Stefan's approach and for macro paradise using the untyped macro approach. Examples of the corresponding tests can be found here for 2.9.x, here for 2.10.x and here for macro paradise.
The untyped macro tests are the cleanest, but Stefan's 2.10.x compatible approach is a close second.
这篇关于测试某些东西不能编译的断言的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!