在不涉及Null的情况下满足“ Iterable”界面

时间:2018-08-02 06:22:06

标签: ceylon

我正在尝试创建一个满足接口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

full source code

可以看出 Element getElement(Integer index) { if (exists element = getFromFirst(index)) { return element; } else { assert (is Element null); return null; } } 非常有能力返回Null值。但是,怎么可能getElementList接口的满意却没有提到Nulls,就像我的实现被迫这样做一样呢?相关的“满意”陈述式位于Iterable的超类型List中。看到这里:

Collection

full source code

看,妈!否​shared interface Collection<out Element=Anything> satisfies {Element*} {

1 个答案:

答案 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)是因为类型参数本身与ObjectNull不相关类型,位于Anything下面的类型层次结构中自己的分支中。

这是因为类型参数可以在运行时实现为AnythingNull(或\Inull),Object或{{1}的任何子类型}。

实际上,Object中的getElement方法的行为与此变体相同:

List

但是语言模块中的版本性能更高,因为Element getElement(Integer index) { assert(is Element element = getFromFirst(index)). return element; } exists快。仅当列表包含is Element个元素时,语言模块中的版本才会执行慢速运行时类型检查。