浏览Rust文档的标记部分和维基百科有关子类型和方差的文章多次没有它提高我对生命周期子类型关系的理解,我感到愚蠢.
我想我只是习惯于"典型的OOP风格"子类型关系,比如"Cat <:Animal",意思是"Cat是动物的子类型",其中"S是T的子类型"意味着"任何术语S都可以安全在预期类型为T的术语中使用".到现在为止还挺好.
但这如何适用于生命周期?它现在在Rust中定义的方式显然是(*)
(#1)'a <:'b <=>生命周期a不超过生命周期b.
你可能会想"当然这意味着什么!" 可能是因为<:看起来类似于小于运算符或可能因为"sub"让你想到子集而且更短的寿命肯定是更长寿命的一个子集.但是' 如果'a'不再是'b',那么它' '真的是'b' 的子类型吗?让我们尝试应用维基百科对子类型关系的定义:
(#2)'a <:'b <=>生命周期a可以安全地用于预期寿命为b的上下文中.
我遇到的问题是我无法调和这个问题.你是如何从#2到#1的?因为对我来说,这似乎是一个矛盾...如果你期望某事至少是活着的b并且你有一个比b短的东西,你显然不能在那些有生命周期的东西中使用它是必需的,可以吗?它只是我还是我们得到了生命时间的子类型关系错误?
编辑:(*)根据#rustIRC频道中的Ms2ger,情况就是如此.它也适用于Items迭代器中使用的逆变寿命标记的文档.
Edit2:已删除ContravariantLifetime和CovariantLifetime标记.我们现在已经PhantomData作为标记模块的替代品.
假设我有一个简单的类型类,其实例将为我提供某种类型的值:
trait GiveMeJustA[X] { def apply(): X }
Run Code Online (Sandbox Code Playgroud)
我有一些例子:
case class Foo(s: String)
case class Bar(i: Int)
implicit object GiveMeJustAFoo extends GiveMeJustA[Foo] {
def apply() = Foo("foo")
}
implicit object GiveMeJustABar extends GiveMeJustA[Bar] {
def apply() = Bar(13)
}
Run Code Online (Sandbox Code Playgroud)
现在我有一个类似(但不相关)的类型类,它做同样的事情,但在其类型参数中是协变的:
trait GiveMeA[+X] { def apply(): X }
Run Code Online (Sandbox Code Playgroud)
在它的伴随对象中,我们告诉编译器如何从非协变类型类的实例创建实例:
object GiveMeA {
implicit def fromGiveMeJustA[X](implicit giveMe: GiveMeJustA[X]): GiveMeA[X] =
new GiveMeA[X] { def apply() = giveMe() }
}
Run Code Online (Sandbox Code Playgroud)
现在我希望implicitly[GiveMeA[Foo]]编译得很好,因为只有一种方法可以获得GiveMeA[Foo]我们在这里得到的部分.但它没有(至少不在2.10.4或2.11.2):
scala> implicitly[GiveMeA[Foo]]
<console>:16: this.GiveMeA.fromGiveMeJustA is not a valid …Run Code Online (Sandbox Code Playgroud) 假设类B继承自类A.以下是合法的Java:
List<A> x;
List<? super B> y = x;
Run Code Online (Sandbox Code Playgroud)
就规范而言,这意味着List<A>assignsTo List<? super B>.但是,我无法找到说这是合法的规范部分.特别是,我认为我们应该有子类型关系
List<A> <: List<? super B>
Run Code Online (Sandbox Code Playgroud)
但是Java 8规范的4.10节将子类型关系S >1 T定义为直接超类型关系的传递闭包,并且它根据计算一组超类型的有限函数来定义直接超类型关系T.由于可能存在任意数量的s继承,因此输入List<A>可以产生无限制函数,因此spec的子类型定义似乎为超级通配符分解.关于"类和接口类型之间的子类型"的第4.10.2节确实提到了通配符,但它只处理通配符出现在潜在子类型中的另一个方向(此方向适合计算的直接超类型机制).List<? super B>BA
问题:规范的哪一部分说上面的代码是合法的?
动机是针对编译器代码的,因此仅仅理解它为何直观合法或者提出一种处理它的算法是不够的.由于Java中的一般子类型问题是不可判定的,我想处理与规范完全相同的情况,因此需要处理此案例的规范部分.
考虑以下一对函数定义,它们传递类型检查器:
a :: forall a. a
a = undefined
b :: Int
b = a
Run Code Online (Sandbox Code Playgroud)
即类型的表达式forall a. a可用于Int期望类型之一的类型.这在我看来很像子类型,但据称Haskell的类型系统缺少子类型.这些形式的可替代性有何不同?
这个问题并不具体forall a. a.其他例子包括:
id :: forall a. a -> a
id x = x
idInt :: Int -> Int
idInt = id
Run Code Online (Sandbox Code Playgroud) 我对通用子类型感到困惑.
在Java中,如果type A是B泛型类型的子类型C<A>并且C<B>是不变的.例如,ArrayList<Base>不是子类型ArrayList<Derived>.
但是,在Scala中,泛型类型C<A>,C<B>如果type A是子类型,则是协变的B.那么Scala中泛型类的属性是什么,而Java中却没有?
阅读"类型和编程语言",我对使用闭包和记录子类型的对象实现印象深刻(第18章).是否有任何特殊原因OCaml不支持记录子类型(我知道对象确实如此)?事实上,我找不到任何支持这种语言的语言.
我来自 Java 背景,我正在尝试围绕 Haskell 的类型系统进行研究。在 Java 世界中,Liskov 替换原则是基本规则之一,我试图了解是否(如果是,如何)这也是适用于 Haskell 的概念(请原谅我对 Haskell 的有限理解,我希望这个问题甚至是有道理的)。
例如,在 Java 中,公共基类Object定义了boolean equals(Object obj)所有 Java 类都继承的方法,并允许进行如下比较:
String hello = "Hello";
String world = "World";
Integer three = 3;
Boolean a = hello.equals(world);
Boolean b = world.equals("World");
Boolean c = three.equals(5);
Run Code Online (Sandbox Code Playgroud)
不幸的是,由于 Liskov 替换原则,Java 中的子类在它接受的方法参数方面不能比基类更具限制性,因此 Java 也允许一些永远不会为真的无意义比较(并且可能导致非常微妙的错误) :
Boolean d = "Hello".equals(5);
Boolean e = three.equals(hello);
Run Code Online (Sandbox Code Playgroud)
另一个不幸的副作用是,正如 Josh Bloch很久以前在Effective Java 中指出的那样,equals在存在子类型的情况下,基本上不可能按照其约定正确实现方法(如果在子类中引入额外的字段,实施将违反合同的对称性和/或传递性要求)。
现在,Haskell 的Eq 类型类是一个完全不同的动物:
Prelude> data Person = …Run Code Online (Sandbox Code Playgroud) 关于以下C++程序:
class Base { };
class Child : public Base { };
int main()
{
// Normal: using child as base is allowed
Child *c = new Child();
Base *b = c;
// Double pointers: apparently can't use Child** as Base**
Child **cc = &c;
Base **bb = cc;
return 0;
}
Run Code Online (Sandbox Code Playgroud)
GCC在最后一个赋值语句中产生以下错误:
error: invalid conversion from ‘Child**’ to ‘Base**’
Run Code Online (Sandbox Code Playgroud)
我的问题分为两部分:
reinterpret_cast.使用这些演员表意味着抛弃所有类型的安全.有没有什么我可以添加到类定义中来隐式地转换这些指针,或者至少以允许我使用的方式表达转换static_cast?try
{
throw Derived();
}
catch (Base&)
{
std::cout << "subtyping\n";
}
try
{
throw "lol";
}
catch (std::string)
{
std::cout << "coercion\n";
}
Run Code Online (Sandbox Code Playgroud)
输出:
subtyping
terminate called after throwing an instance of 'char const*'
Run Code Online (Sandbox Code Playgroud)
为什么异常处理对子类型有好处,但不是强制?
在他的演讲编译器是数据库中,Martin Odersky提出了一个有趣的方差角案例:
class Tree[-T] {
def tpe: T @uncheckedVariance
def withType(t: Type): Tree[Type]
}
Run Code Online (Sandbox Code Playgroud)
T被定义为逆变,因为将类型化的树(Tree[Type])视为无类型树(Tree[Nothing])的子类型是有用的,但不是相反.
通常,Scala编译器会抱怨将其T作为方法的返回类型出现tpe.这就是为什么Martin用@uncheckedVarianceannotion 关闭编译器的原因.
以下是转换为Kotlin的示例:
abstract class Tree<in T> {
abstract fun tpe(): T
abstract fun withType(t: Type): Tree<Type>
}
Run Code Online (Sandbox Code Playgroud)
正如预期的那样,Kotlin编译器抱怨T出现在"out"位置.Kotlin有类似的东西@uncheckedVariance吗?或者有更好的方法来解决这个特殊问题吗?