В чем разница между типами Null и Null & T, где T <: AnyRef?

Посмотрите на следующий метод

def toOption[T <: AnyRef](value: T | Null): Option[T] = 
    value match {
        case null => None
        case content: T => Some(content)
    }

Если вы проверите переменную шаблона content, тип будет T | T & Null, что, как я полагаю, является результатом пересечения типа проверяемого value: T | Null и типа совпадающего шаблона element: T. Мое внимание привлекает второй операнд оператора |. Разве часть T & Null не должна быть упрощена компилятором до Null, когда T<:AnyRef оставляет T | Null как тип content? В чем разница между типами Null и T & Null?

Если определение пересечения A & B заключалось в том, что оно представляет значения, которые являются членами как A, так и B, тогда:

  • если значения, представленные T, включают null, то набор значений, которые являются членами как T, так и Null, будет содержать только значение null;
  • если бы значения, представленные T, не включали null, то набор значений, которые являются членами как T, так и Null, был бы пустым (Nothing).

Следовательно, T & Null = Null | Nothing = Null, когда T <: AnyRef.

Если определение пересечения A & B заключалось в том, что значение соответствует A & B, подразумевает, что оно соответствует как A, так и B, тогда:

  • если null соответствует T, то только null будет соответствовать как T, так и Null;
  • если null не соответствует T, то никакое значение не будет соответствовать ни T, ни Null.

Поэтому T & Null = Null | Nothing = Null когда T <: AnyRef.

Я что-то упустил или в компиляторе ошибка?


Обновлено: я забыл упомянуть, что использовал intelliJ для проверки. Позже, для подтверждения, я использовал макрос для отображения, который по мнению компилятора имеет тип content, и результат был T. Сильно отличается от того, что показывает проверка IntelliJ. Я винил компилятор в ошибке инспектора IntelliJ. Извини.

Вот макрос:

inline def typeOf[A](a: A): String = ${typeOfMacro[A]('{a})}

def typeOfMacro[A : Type](a: Expr[A])(using q: Quotes): Expr[String] = 
    Expr(q.reflect.TypeRepr.of[A].show)

В любом случае, scala REPL не упрощает A & Null до Null когда A <: AnyRef.

scala> val text: String & Null = null
val text: String & Null = null

Так что вопрос по-прежнему актуален.

Любопытно, что REPL также не упрощает A & Nothing до Nothing.


Редактировать 2: Согласно теории типов, не существует типа X такого, что A & X совпадает с X для любого A. Так что упрощение, которого я ожидал, было бы неверным. REPL дает правильный тип. Итак, ответ на мой вопрос: да, я что-то упустил.

Scastie/metals не показывает мне этот странный тип, но меня согревает проверка времени выполнения: scastie.scala-lang.org/BalmungSan/9vrLo5lGRzuvSNmu1rVagA/4

Luis Miguel Mejía Suárez 10.05.2024 15:50

Да, предупреждение о проверке типа возникает из-за отсутствия в Scala функции, которой обладает TypeScript: сужения типа, зависящего от потока управления. Это очень прискорбно. Я был бы признателен Scala за поддержку в ближайшем будущем. Добавление @unchecked везде утомительно и чревато ошибками.

Readren 10.05.2024 22:04

Это не имеет ничего общего с «сужением типа, зависящего от потока управления». Это просто стирание типа, потому что на самом деле вы выполняете проверку класса, поэтому полный тип теряется во время выполнения.

Luis Miguel Mejía Suárez 10.05.2024 22:35

Я думаю, да. Если бы поддерживалось сужение типа, зависящего от потока управления, то тип value во втором case был бы T и никакого присвоения типа не требовалось.

Readren 10.05.2024 22:42

Справедливо, но это уже другой вопрос. Спецификация языка гласит, что типы объединения должны использоваться посредством проверок классов, поэтому я обычно их избегаю.

Luis Miguel Mejía Suárez 10.05.2024 22:45
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
5
126
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вопросов довольно много, поэтому начну с азов.

Scala основана на исчислении DOT. В Scala каждый тип T является сегментом типа [Lower, Upper]. Типы Scala представляют собой решетку (относительно <:, &, |): https://en.wikipedia.org/wiki/Lattice_(order)

T <: AnyRef не подразумевает T >: Null, и наоборот.

T <: AnyRef означает сегмент типа [Nothing, AnyRef]. T >: Null означает [Null, Any].

(Nothing <: AnyVal <: Any, Nothing <: Null <: AnyRef <: Any, Nothing != Null != AnyRef != AnyVal != Any.)

AnyRef — это супертип всех ссылочных типов. Null — подтип всех ссылочных типов. Null населен только null.

Nothing — подтип всех типов (включая Null: Nothing <: Null, Nothing != Null). В нем нет (нормальных) ценностей. Но то, что Nothing не населен (нормальными) ценностями, не означает, что t: Nothing вообще ничем не населен. Существуют термины такого типа: T >: Nothing, есть абстрактные типы/параметры типа, пересекающиеся с этим типом: T <: AnyRef (и в программировании на уровне типа типы важнее, значения, существуют они или нет, менее важны), нет поэтому нормальные значения (расхождения, нижние значения), такие как

@tailrec
def infiniteRecursion(): Nothing = infiniteRecursion()

def throwException(): Nothing = throw new RuntimeException("error")
def foo[T <: AnyRef](t: => T): Unit = ()

foo[Nothing](infiniteRecursion()) // compiles, runs, successfully finishes
foo[Nothing](throwException())  // compiles, runs, successfully finishes
def foo[T <: AnyRef](t: T): Unit = ()

foo[Nothing](infiniteRecursion()) // compiles, runs forever
foo[Nothing](throwException()) // compiles, runs, throws exception

Если T >: Null подразумевает foo[Nothing](infiniteRecursion()), то foo[Nothing](throwException()), T <: AnyRef не будут компилироваться.

Если вам нужны оба T >: Null и def foo[T >: Null <: AnyRef] = ..., просто укажите обе границы типа:

T & Null

Разве часть Null не должна быть упрощена компилятором до T <: AnyRef, когда T >: Null

Нет, не должно. Должно быть, когда T >: Null <: AnyRef (включая Null).

В чем разница между типами T & Null и T = Nothing?

Если T & Null = Nothing & Null = Nothing != Null, то T >: Null. Если T & Null = Null, то T <: Null. Если T (т. е. [Nothing, Null] принадлежит отрезку T & Null = T), то T. Если T & Null — это просто какой-то тип, то <: T — это просто некоторые типы <: Null и T (фактически, максимальный такой тип), обычно отличающиеся от Null и Nat.

Если определение перекрестка...

  • если ценности...
  • если ценности...

Думаю, год назад мы уже обсуждали, что типы не являются наборами (значений):

Почему признак, аннотируемый самим собой, не может быть присвоен типу самого себя?

https://cs.stackexchange.com/questions/91330/what-exactly-is-the-semantic-difference-between-set-and-type

https://math.stackexchange.com/questions/489369/difference-between-a-type-and-a-set

https://planetmath.org/11typetheoryversussettheory

Если два типа имеют равные наборы значений этих типов, это не делает типы равными.

Например, типы ниже Succ[N], _0, _1, _2, Nat, ... являются абстрактными и не имеют значений. Но это совершенно разные типы. Если бы типы были множествами, то все эти типы Succ[N], _0, _1, _2, X, ... были бы одним и тем же множеством (а именно пустым множеством).

type Nat
type _0 <: Nat
type Succ[N <: Nat] <: Nat

trait Add[N <: Nat, M <: Nat] {
  type Out <: Nat
}
object Add {
  type Aux[N <: Nat, M <: Nat, Out0 <: Nat] = Add[N, M] { type Out = Out0 }

  implicit def zeroAdd[M <: Nat]: Aux[_0, M, M] = null
  implicit def succAdd[N <: Nat, M <: Nat](implicit
    add: Add[N, M]
  ): Aux[Succ[N], M, Succ[add.Out]] = null
}

type _1 = Succ[_0]
type _2 = Succ[_1]
type _3 = Succ[_2]
type _4 = Succ[_3]
type _5 = Succ[_4]

implicitly[Add.Aux[_2, _3, _5]] // compiles

trait ToInt[N <: Nat] {
  def apply(): Int
}
object ToInt {
  implicit val zeroToInt: ToInt[_0] = () => 0
  implicit def succToInt[N <: Nat](implicit toInt: ToInt[N]): ToInt[Succ[N]] =
    () => toInt() + 1
}

trait AddToInt[N <: Nat, M <: Nat] {
  def apply(): Int
}
object AddToInt {
  implicit def mkAddToInt[N <: Nat, M <: Nat, Sum <: Nat](implicit
    add: Add.Aux[N, M, Sum],
    toInt: ToInt[Sum]
  ): AddToInt[N, M] = () => toInt()
}

def addToInt[N <: Nat, M <: Nat](implicit addToInt: AddToInt[N, M]): Int =
  addToInt()

addToInt[_2, _3] // 5

Согласно теории типов, не существует типа A & X такого, что X совпадает с A для любого X.

На самом деле, в системе типов Scala Nothing есть T.

> в системе типов Scala X — это Nothing < Если бы это было правдой, то компилятор мог бы упростить A & Nothing до Nothing, но этого не происходит. Если я прав, Nothing — это хак Scala для решения некоторых ситуаций. Но не является реальным типом согласно теории типов. A и B должны иметь все члены A и B, поэтому утверждение «Nothing & A = Nothing для любого A» истинно. Ничто не должно иметь членов всех возможных типов, тем не менее, у него их нет. Или, по крайней мере, компилятор жалуется при доступе к элементу переменной типа Nothing.

Readren 13.05.2024 23:36

@Readren "тогда компилятор мог бы упростить A & Nothing до Nothing, но это не так" Конечно, так и есть: scastie.scala-lang.org/DmytroMitin/wLBUOMnTQnSqjAH9f0Os6w/2 "Но это не настоящий тип" Конечно, это настоящий тип. «согласно теории типов» Вы действительно читали это в каком-то учебнике по теории типов? en.wikipedia.org/wiki/Bottom_type «Nothing должны содержать члены всех возможных типов» Уже обсуждалось: stackoverflow.com/questions/1728541/…

Dmytro Mitin 14.05.2024 00:38

@Readren «только для поддержки исключений». Не только исключения, но и бесконечная рекурсия (во время компиляции не так легко определить, конечна ли рекурсия). Nothing — это минимальный элемент решетки типов, как Any — максимальный элемент. Nothing важен для вывода типа: stackoverflow.com/a/64969830/5249621

Dmytro Mitin 14.05.2024 00:40

>Вы действительно читали это в каком-то учебнике< Эту ложь мне сказал чатGPT. :С

Readren 14.05.2024 03:39

Другие вопросы по теме