我正在尝试创建一个满足接口Iterable
的类“ Gprogram”(以便可以在我的Gprogram中迭代Gcommand)。但是,我只能使用类型为Iterable<Gcommand|Null,Nothing>
的{{1}}使其可迭代。
问题是:当我尝试使用Iterable<Gcommand,Nothing>
时,出现此错误:
指定的表达式必须可分配给声明的类型的“ next” 严格进行null检查的“迭代器”:“ null | Gcommand | finished”不是 可分配给“ Gcommand |完成”(分配的类型包含 'null')
此错误涉及以下代码段:
Iterable<Gcommand,Nothing>
摘自此处的完整实现:
next() => index>=size
then finished
else gcommands[index++];
在我看来,问题似乎是类型检查器无法意识到shared class Gprogram( Gcommand+ gcommands ) satisfies Iterable<Gcommand|Null,Nothing> {
shared actual default Iterator<Gcommand|Null> iterator() {
if (gcommands.size > 0) {
return object
satisfies Iterator<Gcommand|Null> {
variable Integer index = 0;
value size = gcommands.size;
next() => index>=size
then finished
else gcommands[index++];
string => gcommands.string + ".iterator()";
};
}
else {
return emptyIterator;
}
}
}
方法永远不能返回null(意识到这将涉及对整数值进行推理,而类型检查器则无法做到这一点) )。因此,所有的希望都消失了,对吧..
一个棘手的问题仍然存在:next
如何设法做我不能做的事情?让我们看一下List
类的iterator
的实现:
List
其中 shared actual default Iterator<Element> iterator() {
if (size>0) {
return object
satisfies Iterator<Element> {
variable Integer index = 0;
value size = outer.size;
next() => index>=size
then finished
else getElement(index++);
string => outer.string + ".iterator()";
};
}
else {
return emptyIterator;
}
}
函数如下所示:
getElement
可以看出 Element getElement(Integer index) {
if (exists element = getFromFirst(index)) {
return element;
}
else {
assert (is Element null);
return null;
}
}
非常有能力返回Null值。但是,怎么可能getElement
对List
接口的满意却没有提到Nulls,就像我的实现被迫这样做一样呢?相关的“满意”陈述式位于Iterable
的超类型List
中。看到这里:
Collection
看,妈!否shared interface Collection<out Element=Anything>
satisfies {Element*} {
!
答案 0 :(得分:4)
是的,的确,由于Ceylon不是依赖类型的语言,因此Ceylon编译器在检查查找表达式(方括号)的可空性时并不需要考虑整数。
更好的方法(实际上是可行的)是使用else
运算符:
next() => gcommands[index++] else finished;
或者可以说是更好的getOrDefault
方法:
next() => gcommands.getOrDefault(index++, finished);
List
返回null
的原因是因为它的断言变窄:
assert (is Element null);
与任何类型狭窄的断言一样,在断言之后,null
的类型从Null
缩小为Null&Element
。
但是,有趣的是要注意,与您每天的缩小条件不同,该值不影响局部值,而是影响匿名类(即object
声明),即null
。 / p>
有趣的是,对null
的引用类型会缩小为Null&Element
,而不是Null
类型本身会改变超类型。
也就是说,如果您有一个类型为Null
的表达式,它将仍然无法分配给Element
。
例如,考虑以下声明:
Foo foo<Foo>()
{
Null n = null;
assert(is Foo null);
// return n; // Doesn’t work.
return null; // Works.
}
在这里,n
的类型为Null
,而null
的类型为Null&Foo
。后者可分配给Foo
,并且可以无错误地返回。但是,第一个不是,并且会产生错误。
这是由于inability to narrow type’s supertypes and subtypes(与缩小值的类型相对应,这是可能的)。
缩小null
“有效”类型的原因(而不是简化为Nothing
)是因为类型参数本身与Object
和Null
不相关类型,位于Anything
下面的类型层次结构中自己的分支中。
这是因为类型参数可以在运行时实现为Anything
,Null
(或\Inull
),Object
或{{1}的任何子类型}。
实际上,Object
中的getElement
方法的行为与此变体相同:
List
但是语言模块中的版本性能更高,因为Element getElement(Integer index)
{
assert(is Element element = getFromFirst(index)).
return element;
}
比exists
快。仅当列表包含is Element
个元素时,语言模块中的版本才会执行慢速运行时类型检查。