Java 8及以后的选择性
#java #jvm #typetheory

这是对价值观的可选性的理论观察。我们将讨论处理“十亿美元错误”的不同方法。我将使用非常简单的JVM示例。因此,为了更好地感受到我的痛苦,至少建议基本的Java知识。

请注意,我不假装在这里有用。这些只是我对此事的结构性想法。但是,如果有人发现它们有价值或至少很有趣,我会很高兴。毫无疑问,让我们开始!

我:8岁之前的爪哇

在Java 7及以下,所有对象都是无效的:

<数学=“ block”> b> t = l U U II:Haskell

从理论上讲,编译器 can 可区分可确定的值。在Java 7时,此类编译器的一个例子是GHC(格拉斯哥Haskell编译器)。在haskell中,默认情况下的类型不可自然。对于可能不存在的值,宣布了特殊的Maybe类型:

data Maybe a = Nothing | Just a

在这样的系统中,我们有编译器保证不能传递Nothing而不是值,因为它们是两种不同的类型。以Int为例:

<数学=“ block”> i n t = { x < mtext> £ 2 2 29 x 2 2 2 2 29 1 } < /mo> < /mtd> n o t < /mi> h i n g â> i n \begin{aligned} & Int = \lbrace ~x~ |~ -2^{29} \le x \le 2^{29}-1 ~\rbrace \cr & Nothing \notin Int \end{aligned}

The same koude8 function as before:

daima3

And later in the code we try to pass koude6 to it:

daima4

Our code doesn't compile: koude10. Wow!

III:Java 8

今天,Java World中的所有开发人员都知道处理无效类型的“最好的方法”。新的(EH,在2023年并不是什么新鲜)和Shiny Optional班。由于我们已经知道Maybe类型,因此我们可以看到两者之间的明显相似性。让我们在简单的示例中尝试使用它:

Optional<Integer> square(Optional<Integer> o) {
    return o.map(i -> i * i);
}

,然后:

square(null);

huh:NullPointerException: Cannot invoke "java.util.Optional.map(java.util.function.Function)" because "<parameter1>" is null

这里明显的问题是我们希望我们的Optional<Integer>为:

<数学=“ block”> >> n a l mi> t t e g e r =“ false”> { e m p t i IV:Scala 2

当Java Devs想要Haskell的东西时,它们会去哪里?正确的。他们检查了Scala。

Option更接近Haskell Maybe,因为它是代数总额。就哈斯克尔而言,它是None | Some a。因此,您可以与它进行模式匹配和其他很酷的事情。但是...

def square(o: Option[Int]): Option[Int] = 
  o match {
    case Some(i) => Some(i * i)
    case None => None
  }

square(null) // java.lang.ExceptionInInitializerError: Caused by: scala.MatchError: null

由于同样的问题而引起的运行时异常:

<数学=“ block”> >> mstyle mathcolor =“ red”> 。 > i n t Option~Int = \textcolor{red}{\lbrace null \rbrace \cup} \lbrace None \rbrace \cup Int

V:Valhalla项目

几年前,当我第一次检查Project Valhalla时,该代码像:

inline class Point {
    private int x;
    private int y;

    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public int x() { return x; }
    public int y() { return y; }

    public Point add(Point other) {
        return new Point(x + other.x, y + other.y);
    }
}

point.add(null); // error: compilation failed: 
                 // incompatible types: <null> cannot be converted to Point

编译器保证终于在那里!

但是,在最新版本中,即使Point本身看起来更整洁:

value record Point(int x, int y) {
    public Point add(Point other) {
        return new Point(x + other.x, y + other.y);
    }
}

我们回到了我们的好ol'npe:

point.add(null); // NullPointerException: Cannot read field "x" because "<parameter1>" is null

坦率地说,我不知道“为什么”,这可能是为了埋葬在与瓦尔哈拉有关的讨论中的某个地方。但可悲的事实是,即使有瓦尔哈拉(Valhalla)到位,我们仍然没有保证编译器。

vi:也许的问题

目前,看起来Haskell的Maybe是对的,就像雨一样。但是它有以下问题:

<数学=“ block”> i n t 留作mtext> i n t Int \textcolor{red}{\not\subset} Maybe~ Int

koude21 should form a {Nothing}Int\lbrace Nothing \rbrace \cup Int set, but it doesn't. Due to this, theoretically compatible changes become incompatible in Haskell. Let's say we have such a (strange) square function:

daima11

At some point in time, we decide to return koude7:

daima3

Or accept koude21:

daima13

Both cases are an ease of requirements, so theoretically should be backward compatible. But in Haskell they aren't. Compilation is broken for our clients.

  • The idea for this chapter was stolen from the koude24 rich hickey的谈话。

vii:工会方式

有什么比Haskell更好吗?对于我们的用例 - “是”。

返回JVM,我们可以找到所需的类型。 Kotlin的Int?

<数学=“ block”> i n t ?>?> = } Int? = \lbrace null \rbrace \cup Int

Firstly, non-nullable koude7 gives us compiler guarantees that it is actually { x  231x2311 }\lbrace~ x~ |~ -2^{31} \le x \le 2^{31}-1 ~\rbrace :

daima14

Secondly, ease of requirements works without breaking our clients (they'll only get warnings from the compiler):

daima15

Guaranteeing to return value:

daima16

Or accepting nulls as well as values:

daima17

Doesn't break clients. For example, koude27 works for all three functions.

VII.A:比例3

当我考虑出版或不发表时,Scala 3发行了(是的,这是几年前写作的初稿)。 Dotty对工会类型有内置的支持和选择的flag -Yexplicit-nulls,以实现无效安全。

我以前来自Scala 2 Now(版本3.2.2)的示例给出了一个编译时错误:Found: Null, Required: Option[Int]

向后兼容也存在:

square(3).nn + 1 // works for all examples below

def square(i: Int): Int | Null = i match
  case 0 => null
  case _ => i * i

def square(i: Int): Int = i * i

def square(i: Int | Null): Int | Null = i match
  case null => null
  case _ => i * i

是的!对了,我们想要的。

虽然...将Int? -> Int?功能的Kotlin代码与Scala中的上述Int | Null -> Int | Null定义进行比较。单线转换为匹配/案例表达式。 Scala缺少像?.?:这样的操作员,这使得与无效类型的工作尴尬。另外,由于该功能是新的和可选的(双关语意图),因此在Scala代码库周围散布的数量级较小。因此,就目前而言,我将使用笔向Kotlin提出一点,并使用铅笔给Scala。也就是说,在Scala World中,关于适当的无效安全的未来看起来很光明。


这里的结论是什么?老实说,我不知道。正如我一开始所说,这些只是我对此事的想法。是的,Kotlin正确的性能是正确的。这是否意味着Kotlin比其他讨论的语言要好得多?否。这是否意味着您应该切换到Kotlin?或许。至少,我建议您尝试一下,然后做出自己的加权决定。

在以下文章(如果发表的话)中,我计划通过利用诸如抽象和构图之类的力量来讨论科特林实施的弊端。感谢您的阅读!