Java / C#类型系统有哪些不足之处?

时间:2009-05-19 18:42:16

标签: c# java haskell type-systems

它经常听说Haskell(我不知道)有一个非常有趣的类型系统..我对Java非常熟悉,而且对C#有点熟悉,有时碰巧我正在打击类型系统所以一些设计适应或以某种方式更好地工作。

这让我想知道......

由于Java / C#类型系统的不足,以某种方式出现了什么问题? 你是如何处理它们的?

8 个答案:

答案 0 :(得分:8)

阵列坏了。

  Object[] foo = new String[1];
  foo[0] = new Integer(4);

给你java.lang.ArrayStoreException

你要小心处理它们。

可空性是另一个大问题。 NullPointerExceptions随处可见。除了切换语言之外,你真的无法对它们做任何事情,或者尽可能使用避免它们的惯例(正确地初始化字段等)。

更一般地说,Java / C#的类型系统不是很有表现力。 Haskell可以给你的最重要的事情是,通过它的类型你可以强制执行这些函数没有副作用。编译时证明部分程序只是被评估的表达式,使程序更可靠,可组合,更容易推理。 (忽略这个事实,Haskell的实现为你提供了绕过它的方法)。

将它与Java相比较,调用方法几乎可以做任何事情!

Haskell也有模式匹配,它为您提供了创建程序的不同方式;你有关于哪些函数运行的数据,通常是递归的。在模式匹配中,您可以破坏数据以查看它是什么类型,并根据它进行操作。例如你有一个列表,它可以是空的,也可以是头尾。如果要计算长度,可以定义一个函数:如果list为空,length = 0,否则length = 1 + length(tail)。

如果你真的想了解更多,有两个很好的在线资源:

Learn you a HaskellReal World Haskell

答案 1 :(得分:5)

我不喜欢原始(本机)类型(intbooleandouble)与其对应的类包装器(Integer之间存在差异, Java中的BooleanDouble)。

这通常非常烦人,特别是在编写通用代码时。本机类型不能通用化,您必须实例化一个包装器。泛型应该使您的代码更抽象,更容易重用,但在Java中,它们带来了限制,显然没有理由。

private static <T> T First(T arg[]) {
    return arg[0];
}

public static void main(String[] args) {        
    int x[] = {1, 2, 3};
    Integer y[] = {3, 4, 5};

    First(x); // Wrong
    First(y); // Fine
}

在.NET中,即使存在单独的值和引用类型,也没有这样的问题,因为它们严格意识到“一切都是对象”。

答案 2 :(得分:3)

关于泛型的这个问题表明了java类型系统的表现力的不足 Higher-kinded generics in Java

答案 3 :(得分:2)

我不喜欢这样的事实,即类不是一流的对象,你不能做一些奇特的事情,例如让静态方法成为接口的一部分。

答案 4 :(得分:1)

Java / .net类型系统的一个根本弱点是它没有声明方法来指定对象的状态如何与其引用类型字段的内容相关,也没有指定允许方法持久引用的方法 - 类型参数。虽然在某种意义上,运行时能够使用一种类型Foo的字段ICollection<integer>来表示许多不同的东西,但类型系统不可能为不可变性等事物提供真正的支持。 ,等效性测试,克隆或任何其他此类功能,而不知道Foo是否代表:

  1. 对集合的只读引用,任何东西都不会发生变异;该类可以自由地与外部代码共享此类引用,而不会影响其语义。该引用仅封装不可变状态,并且可能不封装标识。
  2. 对类型可变但不会实际变异的集合的可写引用;该类只能与可信任的代码共享此类引用,而不会使其变异。如上所述,引用仅封装不可变状态,并且可能不封装标识。
  3. 宇宙中任何位于变异的集合的唯一引用。引用将封装可变状态,但不会封装标识(用另一个保存相同的项替换集合不会更改封闭对象的状态)。
  4. 对它变异的集合的引用,它认为它的内容是它自己的,但是外部代码保存了它希望附加到`Foo`当前状态的引用。该引用将封装身份和可变状态。
  5. 对某个其他对象所拥有的可变集合的引用,它希望将其附加到该另一个对象的状态(例如,如果持有`Foo`的对象应该显示其他某些集合的内容) 。该引用将封装身份,但不会封装可变状态。

假设有人想要将包含Foo的对象的状态复制到一个新的,分离的对象。如果Foo表示#1或#2,则可以在新对象中存储Foo中的引用副本,或者对保存相同数据的新对象的引用;复制引用会更快,但两个操作都是正确的。如果Foo表示#3,则正确的分离副本必须包含对从原始状态复制状态的新分离对象的引用。如果Foo代表#5,则正确的分离副本必须包含原始引用的副本 - 它必须 NOT 保持对新分离对象的引用。如果Foo表示#4,则不能单独复制包含它的对象的状态;有可能复制一堆互连的对象以产生一个新的状态,其状态与原始状态相同,但不可能单独复制对象的状态。

虽然类型系统不可能以声明方式指定对象之间可能存在的所有可能关系以及应该对它们做些什么,但是类型系统和框架应该可以正确地生成代码到在大多数情况下,如果它知道哪些字段包含身份,可变状态,两者或两者都不包含,则产生语义正确的等价测试,克隆方法,平滑互操作的可变,不可变和“可读”类型等。此外,框架应该可以最大限度地减少防御性复制和包装,在这种情况下,它可以确保传递的引用不会被赋予任何会改变它们的东西。

答案 5 :(得分:0)

(Re:C#具体。)

我很想tagged unions

同样在first-class objects上了解类,方法,属性等。

虽然我从未使用它们,但Python有type classes,它们基本上是表示类及其行为方式的类型。

不可为空的引用类型,因此不需要进行空值检查。它原本被认为是C#但被丢弃了。 (对此有stack overflow个问题。)

Covariance所以我可以将List<string>投放到List<object>

答案 6 :(得分:0)

这是次要的,但对于当前版本的Java和C#声明对象会破坏DRY principle

Object foo = new Object; 
Int x = new Int; 

答案 7 :(得分:0)

他们都没有元编程设施,比如说老C ++狗有。

使用“使用”复制和缺少typedef是一个违反DRY的例子,甚至可能导致用户引起的“别名”错误等等。 Java'模板'甚至不值得一提..