1 ポイント 投稿者 GN⁺ 2024-08-04 | 1件のコメント | WhatsAppで共有
  • Java が型に null 性マーカー を付けて、Foo!null を拒否し、Foo? は意図的に許容する プレビュー言語機能 を準備中
  • マーカーのない Foo は null 性が 未指定 のままで既存コードと互換性があり、null 性の異なる型間の変換には警告やランタイム検査が伴うことがある
  • null を拒否するフィールドと配列はデフォルト値 null を使えないため、インスタンスフィールドは super(...) 呼び出し前に確定代入され、配列にはコンポーネント 初期値 が必要
  • nullFoo! に狭められると NullPointerException が発生し、配列・フィールドへの保存経路では ArrayStoreExceptionFieldStoreException により汚染を防ぐ
  • Valhalla の value class フラット化 のような最適化は、null 排除を信頼できるようにする基盤であり、標準ライブラリへの適用や既存コードの自動再解釈は現時点の目標ではない

Java の型に null 性を明示するプレビュー機能

  • Java の型に null 性マーカー を付けて、型の値集合に null が含まれるかどうかを表現する
  • Foo!null-restricted 型で、値集合から null を除外する
  • Foo?nullable 型で、値集合に null を意図的に含める
  • マーカーのない Foo は null 性が unspecified であり、null が発生し得るものの、その存在が意図的かどうかは分からない
  • この機能は --enable-preview コンパイルおよびランタイムフラグで有効化される プレビュー機能 である

目標と非目標

  • Java の参照型が null を想定するかどうかを表現し、null 性が異なる型間の変換に対して警告と検査を提供する
  • 既存の Java コードと 互換 であり、ソース互換性やバイナリ互換性を壊さずに段階的に導入できる必要がある
  • null を拒否する型の変数は最初に読み取られる前に初期化されなければならず、別個にコンパイルされたクラスに対してもランタイムで null 拒否が強制される
  • Valhalla の value class フラット化のようなランタイム最適化が null 制限型を信頼できるようにするため、メタデータと整合性保証を提供する
  • 現時点での非目標は次のとおり
    • 既存コードを自動的に再解釈しない
    • あらゆる可能な null をコンパイルエラーにはしない
    • int のような基本型に nullable 形式を追加しない
    • 現段階では標準ライブラリに言語拡張を適用しない

なぜ必要か

  • Java の String 変数には String オブジェクト参照または null を入れられるが、言語レベルでどちらを意図しているか表現する方法がない
  • 多くのプログラムは null がないことを前提にしているが、Javadoc の仕様や実装コードでそれを一貫して強制するには追加作業が必要になる
  • この前提が崩れると、null 値が実装コード内を流れ回り、本来のバグから離れた場所で例外を起こす可能性がある
  • 開発者が型の一部として null を拒否または許容する意図を表明すれば、コンパイル時フィードバックとランタイム検査により、予期しない null をより早く見つけやすくなる
  • Valhalla では value class 型変数を値の フラット化表現 に最適化できるが、null をエンコードするために追加ビットが必要なら、メモリ使用量が増えたり、格納最適化ができなくなったりする
  • Amber ではパターンマッチ候補の null 性が switch の exhaustiveness 判定に影響し、型パターンの null 性が null にマッチするかどうかに影響し得る

null 性マーカーの構文と型構造

  • null 性は型の固有の一部として扱われ、Foo?Foo は null 性が異なるため 別の型 である
  • 配列型と配列コンポーネント型の両方に null 性マーカーを付けられる
    • Foo?[]! は、配列自体が null-restricted で、コンポーネントが nullable Foo である型
    • 多次元配列では各角括弧の後ろにマーカーを付けられ、慣例的に左から右へ、外側から内側へ解釈する
  • パラメータ化型と型引数にも null 性マーカーを付けられる
    • Predicate!<Foo?> は null-restricted な Predicate で、型引数は nullable Foo
  • null 制限型または nullable 型を表すには、ソース中に ! または ?明示的に 現れなければならない
  • 将来的には、クラスやコンパイル単位内のすべての型をデフォルトで null-restricted と解釈し、? のみを例外として使う方式も検討され得るが、詳細は別作業である

フィールドと配列の初期化規則

  • 既存の Java では参照型フィールドと配列コンポーネントのデフォルト値は null だが、null-restricted なフィールドや配列コンポーネントの初期値としては不適切である
  • null-restricted なフィールドと配列は、読み取られる前にプログラムが常に初期化しなければならない
  • null-restricted なインスタンスフィールドにイニシャライザがない場合、各コンストラクタの明示的または暗黙的な super(...) 呼び出しの前に 確定代入 されていなければならない
    • Flexible Constructor Bodies JEP は、コンストラクタ先頭で必要な初期化コードを書けるようにする
    • この early construction context では、this を参照したり、未初期化フィールドを読み取る危険がある操作は許可されない
  • null-restricted なインスタンスフィールドにイニシャライザがある場合、各コンストラクタの開始時に super(...) 呼び出し前に実行される
    • this(...) を呼び出すコンストラクタは、従来規則どおりイニシャライザを実行しない特殊ケースである
  • null-restricted な静的フィールドは、クラス内のすべての静的イニシャライザと初期化ブロックが終了するまでに確定代入されていなければならない
    • 他クラスがクラス初期化中にそのフィールドを読み取ろうとすると、ランタイム検査が早期読み取りを検出して例外を投げる
  • null-restricted なコンポーネント型の配列は、配列生成式で各コンポーネントの 初期値 を提供しなければならない
    • 配列イニシャライザで値をすべて列挙できる
    • 新しい短縮構文も可能だが、構文はまだ TBD である

式の null 性と null 性変換

  • Java コンパイラは型検査の過程で、すべての式の null 性 を決定する
  • 変数参照の null 性は変数宣言から来て、メソッド呼び出しの null 性は参照先メソッドの戻り値型から来る
  • null リテラルは nullable である
  • それ以外のほとんどの参照型式は null-restricted である
    • リテラル、文字列連結、this、クラスインスタンス生成、配列生成、メソッド参照、ラムダ式が含まれる
  • null 性変換は代入、呼び出し、キャストの文脈で許可される
  • 拡大型 null 性変換 には次が含まれる
    • Foo!Foo?
    • Foo! → 未指定の Foo
    • Foo? → 未指定の Foo
    • 未指定の FooFoo?
  • 縮小 null 性変換 には次が含まれる
    • Foo?Foo!
    • 未指定の FooFoo!
  • 縮小 null 性変換は unboxing 変換と同様にコンパイラが自動実行するが、ランタイムでは動的検査が行われ、NullPointerException を起こし得る
  • null リテラルを null-restricted 型へ直接変換しようとする試みはコンパイル時エラーである

ランタイム検査と例外

  • ランタイムで null 値が null-restricted 型に縮小 null 性変換されると NullPointerException が発生する
  • ソースコードに明示的に見えない縮小 null 性変換も、実行時には発生し得る
  • null-restricted なコンポーネント型を持つ配列は、ソースコード上ではより一般的な型として扱われても、通常の配列格納検査で null 値を拒否する
    • この変換失敗は ArrayStoreException を引き起こす
  • コンパイル時点では null-restricted ではなかったフィールドが、後の別コンパイルで null-restricted になった場合、新しい field store check が null 格納を拒否する
    • この変換失敗は FieldStoreException を引き起こす
  • オーバーライド関係にあるメソッド呼び出しでは、上位メソッドのパラメータ呼び出し型への変換の後、オーバーライド先メソッドのパラメータ型への変換が続くことがある
  • メソッド戻り値も、宣言されたメソッド戻り値型へ変換された後、呼び出し地点で期待される戻り値型へ変換され得る

ジェネリクス、型引数、オーバーライド

  • 型変数の使用にも null 性マーカーを付けられ、T! は null-restricted 型、T? は nullable 型である
  • null-restricted および nullable な型変数型は、ジェネリックコード内部で特定の null 性を主張する
  • 型引数として使われる型も null 性を表現でき、型変数型に付いた null 性マーカーは、型引数で主張された null 性を上書きする
  • ジェネリック API の erased 実装内部では null 制限を強制できない
    • ただし、ジェネリック API 境界で発生する通常の暗黙キャストが、ランタイムで null-restricted な型引数を強制する
  • 相互運用性のため、型引数内の null 性は厳格には強制されない
    • Predicate<String!>Predicate<String> または Predicate<String?> に変換できる
    • このような unchecked nullness conversion は警告を引き起こし得る
  • 配列コンポーネント型の null 性変更も unchecked nullness conversion として許可され、どの条件でランタイム検査が行われるかは TBD である
  • メソッドシグネチャの同一性判定では null 性は無視される
    • パラメータや戻り値型の null 性が一致しなくても、一方のメソッドが他方をオーバーライドできる
    • 異なる API が独立に null 性マーカーを導入すると、このような不一致は一般的になり得る
  • null 性はメソッド適用可能性に影響せず、型引数推論失敗の原因にもならないが、ジェネリックメソッドの戻り値型に対して推論される null 性には影響し得る
    • 推論アルゴリズムの詳細は TBD である

コンパイラ警告とエラー

  • 型を null-restricted にすると、新たなコンパイル時エラーが発生し得る
    • その型のフィールドや配列が初期化されていない場合
    • null リテラルをその型へ変換しようとする場合
    • null リテラルを null-restricted 型式と比較する場合も、コンパイル時エラーになり得る
  • それ以外の状況では、null 性解析は補助的であり、コンパイル時エラーは生じない
  • javac はランタイムエラーを避けるための警告を提供し、IDE や他の解析ツールにも同様の方向性が推奨される
  • 想定される警告要因は次のとおり
    • ? 型に由来するものを含む縮小 null 性変換
    • ? 型の式をメンバーアクセスやその他の null-hostile な演算に使う場合
    • 型引数の null 性が境界と整合しない場合
    • メソッドパラメータや戻り値の null 性がオーバーライドされたメソッドと一致しない場合
    • 型の null 性を変える unchecked conversion

クラスファイル、リフレクション、補助的変更

  • ほとんどの null マーカー使用は class ファイルでは消去され、対応するランタイム変換はバイトコードに直接表現される
  • Signature 属性の構文は、型内で !? を許可するよう更新される
  • null 性はメソッドやフィールドの descriptor にはエンコードされない
  • フィールド汚染を防ぐため、新しい NullRestricted 属性が、そのフィールドが null 値を許容しないことを示す
    • このフィールドには ACC_STRICT も付与される必要があり、厳格初期化されなければならない
    • ベリファイアは、コンストラクタが super(...) 呼び出しを行う時点で、厳格初期化インスタンスフィールドがすべて代入済みかどうかを確認する
    • フィールドへの書き込みはすべて null 値を検査し、見つかった場合は FieldStoreException を投げる
  • null-restricted 配列の生成は anewarray 命令ではサポートされず、リフレクション API 呼び出しで行う必要がある
  • Foo!.classFoo?.class のリテラルはなく、対応する java.lang.Class インスタンスも存在しない
  • 新しい RuntimeType API は、配列およびフィールド格納検査でランタイムに強制される型集合を記述し、すべてのクラス型とインターフェイス型の null-restricted な変形を含む
  • Field API はフィールドの RuntimeType 取得をサポートし、この値は getType の結果と異なる場合がある
  • Array API は、コンポーネント型を RuntimeType で表す newInstance の派生版をサポートする
    • この派生版は配列コンポーネント初期値の提供も許可する
    • 初期値なしで null-restricted 配列を生成しようとする試みは拒否する
  • 従来のデシリアライズは null-restricted なフィールドや配列と互換性がなく、別の JEP で、未初期化の null-restricted フィールドや配列を露出しないシリアライズ機構を提供する予定である
  • javadoc が生成する文書には null 性マーカーが含まれる
  • java.lang.reflect.Typejavax.lang.model API は型表現に null 性をエンコードする

代替案と依存関係

  • Java エコシステムのさまざまな開発ツールは独自のコンパイル時 null 追跡を実装してきたが、Java 言語自体を変更しないため、構文は主に annotation に制限され、影響できる動作もコンパイル時検査に限られる
  • 他言語では型システムで null 性を追跡しており、多くの言語はデフォルトで null-restricted で、明示的な null チェックなしに null-restricted 型へ代入することをエラーと見なす
  • Java ではこの機能は選択的でなければならず、単一の大規模移行なしに段階的に利用できる必要がある
  • ランタイムでの null 性強制は、明示的なチェックや Objects.requireNonNull 呼び出しでも実装できる
    • しかし一貫して適用するのは煩雑で、追加の文書化が必要になり、プログラムの可読性も下がる
    • フィールドや配列のような変数格納先に直接適用する方法はない
  • 前提条件は Flexible Constructor Bodies (Second Preview) である
    • コンストラクタが super(...) 呼び出し前に文を実行してインスタンスフィールドに代入できるため、null-restricted フィールド初期化要件を可能にする
  • 今後の作業には次が含まれる
  • 追加で考えられる将来改善には、標準 API の一部への null 性マーカー適用、バイトコードにおける簡潔な null check 表現、null-restricted メソッドパラメータに対するより強い低レベル強制、特定文脈のすべての型を暗黙的に null-restricted と見なす言語メカニズムが含まれる

1件のコメント

 
GN⁺ 2024-08-04
Hacker News のコメント
  • このアプローチが、数年前に C# が導入した方式と違う点が興味深い。C# はプロジェクトで null 許容性を有効にすると、明示的に nullable と示さない限りすべての変数が non-null として宣言される。一方、この提案では既存の変数が事実上、nullable、明示的 nullable、明示的 non-nullable のいずれかになる
    Kotlin も JVM 言語だが、C# のように明示しなければ non-null と見なす。ただし Kotlin には後方互換性の負担がない。lateinit var によって、non-nullable 型を別のメソッドで初期化するまで空のままにでき、初期化前にアクセスすると専用の例外を投げる回避策も提供している
    なぜ3つの選択肢を用意したのか気になる。注釈のない変数は nullable のままにして、注釈のある変数だけを明示的 non-null にすればよいのではないかと思う。何も印を付けなくても自動的に nullable なら、あえて nullable を宣言したい理由が思い浮かばない
    C# の方式のほうが好みだが、この方式にはレガシーコードベースで null 許容性の問題をすべて解決しなくても使えるという利点がある。逆に C# は null 許容性の問題を即座にあぶり出すが、この提案ではこれまでどおり隠しておくことができる
    また「あるメソッドは、引数と戻り値の null 可否が一致していなくても別のメソッドをオーバーライドできる」という部分は奇妙だ。コールバックをオーバーライド/実装する際、元のメソッドが non-null 戻り値を指定しているのに null を返す足撃ち銃になりそうだ

    • 今の会社でレガシーな C# コードベースを nullable 参照型へ移行しているところだが、明示的な non-nullable マーカーがあれば、すでにレビューして注釈を付けた箇所がすぐ分かるので本当にありがたい
      時間の都合で今は一部の重要箇所を除いて注釈だけを有効にする方針を取っているが、意識的に決めた箇所をすぐ確認できるよう、JetBrains の NotNullAttributeCanBeNullAttribute をまだマーカーとして残している。後者は nullable に明示マーカーがあるので削除できるが、前者は C# 自体の機能と名前が衝突する
      そういう意味で、3つの選択肢はかなり望ましい。コードが何十万行もあると、素早く簡単に移行するのは難しいからだ
      別の社内プロジェクトでは、触るコードの周辺に #nullable enable を散りばめて、段階的に null 許容性の範囲を広げている。新しいコードは nullable コンテキストでなければならないという条件も置いている。これもすでに注釈済みの部分を明示するには悪くないが、はるかに小さいコードベースとチームだから可能な方式だ
    • Kotlin にも、直接定義することはできないが null 許容性が指定されていない型がある。Kotlin はこれを**プラットフォーム型(platform types)**と呼び、エラーや診断では String! のように感嘆符で表示する
      「プラットフォーム型」という用語と感嘆符の記号は紛らわしいが、それ以外では Kotlin の方式はかなりうまく機能している。Kotlin には最初から null 許容性があったため、プログラマーがプラットフォーム型を直接指定することはできないが、JVM や JavaScript のように null 許容性が不明確な基盤プラットフォームと互換性を保つには依然として必要だ
      このアプローチでは、デフォルト値はなお妥当だ。基本的には常に non-nullable であるべきで、そう考えない人は Tony Hoare から何も学んでいないということだ。同時に後方互換性も維持される。Kotlin は比較的容易だったが、Java と C# は既存ソースコードとの互換性も守らなければならない
      Java 方式も C# 方式も理想的ではない。C# 方式はコンパイラフラグによってコードの挙動を大きく変え、Java 方式はデフォルト値を最悪の選択肢にしてしまう
      それでも C# 側により傾く。「たぶん null 可」を最も簡単な選択肢にすると、大半のプログラマーがデフォルトでそれを選ぶだろうからだ。特に Java のように企業向け色の強い言語ではなおさらそう思う。リンターとコンパイラ警告は長期的には役に立つだろうが、大半の Java コードが null 許容性をきちんと注釈するまでには何年もかかりそうだ。C# ユーザーは短期的にはより苦しむだろうが、明確な null 許容性という目標にははるかに早く到達する可能性が高い
      https://kotlinlang.org/docs/java-interop.html#null-safety-an...
      https://www.infoq.com/presentations/Null-References-The-Bill...
    • 奇妙だという点には同意する。期待していた方式は、ソースコードレベルで T? は nullable、T! は non-nullable を意味し、新しくコンパイルされるソースの通常の T のデフォルト値は、ソースファイル単位、package-info ファイル単位、またはコンパイラスイッチ全体で指定するpragma に似た宣言を置くものだった
      その後の LTS Java リリースでグローバルなデフォルト値を変えればよい。そうすればプロジェクトの移行が容易になり、pragma を自動的に挿入/更新でき、必要なら以前のデフォルト値も維持できる
      さらに既存の JSR-305 @Nonnull 注釈を、クラスファイル内で non-nullable 型の出現を示す方式として活用していれば、古い JDK との双方向互換性も提供できたはずだ
    • 終盤に「プログラマーが明示的な ! 記号を書かなくても、特定のコンテキストのすべての型が暗黙的に null 制限されることを言語で表明するメカニズムを提供する」というセクションがある
      コンパイラフラグやモジュールタグ程度でもよさそうだ
    • 理由は、Java が他の言語よりずっと重視しているコードの後方互換性のためだと思う
      それでも、別途指定がなければ自動的に non-nullable と仮定するコンパイラフラグが出てくるだろう
  • よさそう。ようやく言語レベルで、何千もの不要な例外や null チェックを取り除く方法ができた。ただ、null かどうかを絞り込む自動変換は違和感がある
    提案書の例にある String? id(String! arg) { return arg; }String s = null;Object! o1 = s; // NPEObject o2 = id(s); // NPEObject o3 = (String!) s; // NPE では、少なくとも前の2つはコンパイルエラーになるべきではないかと思う
    最後は明示的なので微妙だが、むしろ if (s != null) の中ではコンパイラが実効型を String! と認識して String! ss = s; を許可する、という形がよさそう。そうすればエラーの可能性がない

    • 業界で Java を使ってきた立場からすると、動的チェックは明示的に要求したときだけ行われ、それ以外はすべて静的に処理されるほうがよい。Bean Validation は、オブジェクトが一時的に不正な状態になり得るとしても、明示的に検証する瞬間、あるいはフレームワークが自分のコードに入ってくる前に検証する瞬間には有効である、という点でうまく機能している
      実際、最後のケースのキャストよりも Objects.requireNonNull(s) を使うほうが明示的なので好み。ただし、明示的なチェックを回避しつつ、最適化のために妨げられる場合だけを除外する Objects.unsafeForceNonNull(s) のようなものもあるとよい。unsafe メソッドがあれば、複雑な静的解析を追加しなくても自前で requireNonNull を実装できる
    • 「発生し得るすべての null 値をプログラムが明示的に処理するよう要求することは目標ではなく、処理されない null 値はコンパイル時の警告にはなり得るが、エラーではない」とされている
      残念ながら、これは実行時にしか確認されないだろう
    • 1つ目や2つ目の例がコンパイルエラーになるなら、すべてのライブラリ利用箇所に注釈を付けなければならないということになる。そのライブラリが null 型へ移行するまで、あるいは永遠に移行しないなら、ほぼすべての行がキャストだらけになるだろう。話にならない
      たとえば標準ライブラリは、少なくとも当面はnull 型へ移行しないと明示している
    • 問題の本質が API に null を導入することなら、それが言語の誤りなのかはよく分からない。冗長で、整理する必要があるのは理解できるが、結局のところ問題はそういうコードを書く開発者側にあるのではないかと思う
    • 2つ目がコンパイルエラーでなければ、レガシーコードが non-null 引数を受け取る関数を呼べるようになるが、それが本当に良いことかは分からない。実際に使ってみるまでは判断しにくい
  • パッケージ、少なくともファイル単位で、すべての変数をデフォルトで non-null として示す方法がぜひ必要に見える。そうでないと、安全性のためにほぼすべての変数に T! 構文を書こうという主張が強まり、ノイズばかり増えるだろう

    • 「将来考えられる改善」の下に、「プログラマが明示的な ! 記号を書かなくても、特定のコンテキスト内のすべての型が暗黙に null 制限されることを言語で表明するメカニズムの提供」がある
    • それほど悪くはなさそう。自分たちのプロジェクトには、自分たちが所有する Java コードのすべての変数は null ではない、という標準がすでにある。nullable 変数が必要なら @Null で注釈を付ける必要がある
      問題はライブラリと相互作用するコード境界でだけ起きる。この新しい構文も似たようなものになりそう
    • 攻めたリンターなら、マークのない型を non-null と見なし、必要な箇所だけに null 許容性の表記を要求できそうだ
  • 「現時点では、言語改善を標準ライブラリに適用することは目標ではない」という部分は残念
    やむを得ず PHP を使った経験からすると、データについて事前に保証しておいた性質を、巨大な標準ライブラリと相互作用するたびに剥がしたり積み直したりしなければならないのは面倒だ
    Java もこうした表現力を標準ライブラリにもっと積極的に取り入れ、第一級市民にすべき

    • Java の最大の問題の1つは、漸進的に改善してきた結果、少し新しく改善されたコードが、次の漸進的機能ではまたレガシーコードになってしまう点だ。Optional を使うレガシーコードは、明示的な nullable/non-nullable 提案にとって負担になる。レコード型もデフォルトで non-nullable にできたはずなのに、と思う
    • 「現時点では」を「絶対にやらない」と読んでいるようだが、実際にはその移行自体が巨大な作業なので、別枠でやろうとしていることに近い
      また、2段階に分ければ、この機能をプレビュー機能としてより容易にリリースし、フィードバックを受けたうえで設計を確定できる。一度に全部やろうとすると、機能を実際に反復改善する余地がほとんどなくなる
    • 重要な表現は「現時点では」だと思う。機能が定着すれば目標になるはず
    • やむを得ずとは言うが、現代の PHP はかなり快適だ
  • Java にこの機能が入るといい。T? のような言語レベルでの明示的な選択可能性は、Kotlin と TypeScript で開発者の生活の質を大きく上げてくれた。Java には NullAway のようなツールがあるが、面倒だ
    言語レベルのサポートは Optional/Maybe よりずっと良いと思う。コードを map/flatMap のレールに乗せるより、実際のロジックに集中させてくれるからだ
    https://github.com/uber/NullAway

    • 30年以上のソース互換性を壊そうというのか? なぜそこまでする必要があるのか
      単に別の JVM 言語を使えばいいのではないかと思う
  • サイトが現在落ちているので、アーカイブリンクを置いておく: https://web.archive.org/web/20240802081039/https://bugs.open...

    • みんな大好きな「jira」に変えたというのも驚きではない
  • 「発生し得るすべての null 値をプログラムが明示的に処理することを要求するのが目標ではなく、処理されていない null 値はコンパイル時の警告にはなり得るが、エラーではない」というのは悪い判断だと思う
    Java は大半が静的型付け言語なのに、なぜまた別の動的な振る舞いを入れるのか分からない。こうした警告をエラーに昇格させる簡単な方法があることを願う

    • それはできない。そうすると既存のすべての Java プログラムが壊れる。強制すれば、すべてのプログラムがあらゆる場所の null 応答を処理するよう書き直されなければならない。今回の追加機能では、新しい型に関連する演算にだけ null 処理の強制を適用できるので、何も壊れない
      すべての場合に null/nil の処理を型システムが強制する言語のほうがはるかに良いのは確かだ。ただ、現在の Java はそういう言語ではない。それでもこれは大きな改善になるはず
  • こういう教訓を学ぶのが遅すぎるのが残念。デフォルトで non-nullable、デフォルトで不変、デフォルトで最も狭いスコープであるべき
    新しい設計では、「安全な道に落ちる」ようにするより、目先の利便性を選びすぎている。安全なデフォルト値には、はるかに慎重な設計とユーザー体験が必要だが、その結果、ほぼすべての言語・プラットフォーム・技術に足を撃つ銃が大量に生まれている。土木や電気工学には規格があるのに、ソフトウェアは30年くらいごとに新しい言語や技術で同じ教訓を学び直している

    • 残念ながら Java はまだデフォルト non-nullable というルールを学べていないようだ。この提案は新しい型を2つ導入するが、デフォルトの注釈なし型について nullable なのかと聞くと、相変わらず「さあね」と答える
    • Java がこういうことを学ぶのが遅いのが残念だという話。「私たち」と「Java」は同じではない
      Java は、細菌を殺すには水を沸騰させればよいと学んでいる第三世界の村のようだ
  • Facebook でやった仕事の大半は Hack を使うことだった。null であるかどうかは Hack の型システムの中核要素で、無駄なエラーを本当にたくさん解消してくれる
    もちろん、予期しない null をまったく受け取らないという意味ではない。この機能も後から言語に追加されたため、PHP 由来のレガシーな mixed 型がまだ多く、これは実質的に何でもありという意味だった
    まず nullable な配列はどうなるのか気になる。String![] の例はオブジェクトが null になり得る場合を示しているが、配列そのものはどうなのだろう? Java では String labels[] = null; は完全に合法だ。では String![]! labels; のように宣言する必要があるのだろうか?
    Hack では vec $foo は foo も要素も null ではなく、?vec $foo は要素は non-null だが foo は null 可能という意味になる。実際、null 配列を使う理由はほとんどないので、デフォルトは null 不可であるべきだ。ただし Java には、すべてのレガシーコードが null 可能性を前提としているという問題がある
    提案書の Object! o1 = sObject o2 = id(s)Object o3 = (String!) s の例では、2番と3番はコンパイルエラーであるべきではないかと思う
    最後に、Java のキャストより Hack の as 強制演算子のほうが好みだ。例えば foo($b) はコンパイルエラー、$b as A は null なら実行時エラー、$a as ?B は B ならキャストし、そうでなければ null を返す、という具合だ
    結局のところ、Java SDK にこれをかぶせられるのか、レガシーコードではどのような姿になるのかが問題になる
    https://docs.hhvm.com/hack/types/nullable-types

    • おそらく単に String![]? labels = null; になると思う
  • Kotlin の良いものが今や Java に全部入ってきているようだ
    それでも Lombok のようなものを相手にしなくて済む Kotlin で働き続けたい。Java レコードは良いけれど

    • それでも Java プログラマーは昔ながらのイディオムを使い続けるだろうし、古いコードベースに縛られているだろう。こうしたものが広く使われるまでには時間がかかるので、Kotlin プログラマーは心配しなくてよい
    • Java には他の言語から着想を得てきた長い歴史がある。後方互換性をおおむね維持しながらも、かなり良いペースで進化し続けているのは見ていて良い