Почему аннотируемая черта self не может быть назначена типу self?

trait Base

trait Plugin { base: Base =>
    def asBase: Base & Plugin = this
}

class Mix extends Base, Plugin

val plug: Plugin = new Mix
val baseA: Base= plug.asBase
val baseB: Base = plug // snorts with "Found: Plugin. Required: Base

Почему? Если я прав, принцип подстановки Лисков соблюдается, потому что все экземпляры Plugin относятся к конкретному типу, который представляет собой смесь, включающую подтип Base. Следовательно, объекты типа Base можно заменить объектами типа Plugin без ущерба для корректности программы.

Scastie воспроизводит проблему: scastie.scala-lang.org/BalmungSan/D0ZAd9EIQ1KIUxn52IGl3Q/2

Luis Miguel Mejía Suárez 25.04.2023 22:27
Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
4
1
69
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

Принцип Лисков гласит

https://en.wikipedia.org/wiki/Liskov_substitution_principle

Требование к подтипу: пусть ϕ(x) будет доказуемым свойством объектов x типа T. Тогда ϕ(y) должно быть истинным для объектов y типа S, где S является подтипом T.

Предусловия не могут быть усилены в подтипе.

Постусловия не могут быть ослаблены в подтипе.

Инварианты должны сохраняться в подтипе.

Принцип Лискова о подтипах. Принцип Лискова теперь неприменим, потому что Plugin не является подтипом Base

implicitly[Plugin <:< Base] // doesn't compile

(<:< слабее, чем <:https://stackoverflow.com/a/75762663/5249621, но для нас это нормально; если бы Plugin был подтипом Base, т.е. Plugin <: Base, то, кроме того, implicitly[Plugin <:< Base] был бы).

Я думаю, вы путаете либо trait Plugin { base: Base => } с trait Plugin extends Base, либо подклассы (наследование) с подтипами.

В чем разница между классом и типом в Scala (и Java)?

В чем разница между Типом и Классом?

https://typelevel.org/blog/2017/02/13/more-types-than-classes.html

Как отличить параметрические типы?

В чем разница между типом уточнения и анонимным подклассом в Scala 3?

trait Plugin extends Base означает, что Plugin является подклассом Base. В частности, это означает, что Plugin является подтипом Base, поэтому все подтипы Plugin являются подтипами Base. Но trait Plugin { base: Base => } означает, что все подклассы Plugin должны быть подклассами Base. Это не означает, что все подтипы Plugin должны быть подтипами Base (более того, это не означает, что Plugin является подтипом Base). Действительно, легко объявить тип

type SubPlugin <: Plugin

который не является подтипом Base

implicitly[SubPlugin <:< Base] // doesn't compile

все экземпляры Plugin относятся к конкретному типу, который представляет собой смесь, включающую подтип Base.

Даже если верно, что все значения (экземпляры ООП) Plugin являются значениями (экземплярами) Base, это не делает Plugin подтипом Base, и это не означает, что все термины типа Plugin являются терминами типа Base. Например, термин x в val x: Plugin = ??? не является термином типа Base.

Вы не должны думать о типе как (только) о наборе всех значений типа (особенно это становится важным в программировании на уровне типов, где значения вообще не так интересны). Наборы могут быть моделью (в логическом смысле: https://en.wikipedia.org/wiki/Model_theory) для типов. Но наборы и типы разные

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

Например, если мы определяем два абстрактных типа

type A

type B

то нет значений этих типов. Таким образом, эти множества были бы равны в теоретико-множественном смысле (пустое множество). Но эти типы разные. В теории гомотопических типов (HoTT, https://en.wikipedia.org/wiki/Homotopy_type_theory) 1 = 2 и 1 = 3 — это два типа без каких-либо значений, но два разных типа.

Следовательно, объекты типа Base можно заменить объектами типа Plugin без ущерба для корректности программы.

Всегда есть программы, которые не могут дать сбой во время выполнения, но отклоняются во время компиляции. Например, if true then 1 else "a" не может дать сбой во время выполнения, но может быть отклонен языком без подтипа, например. Haskell (но не Scala, который думает, что это Any). Scala отклоняет val x: Int = if (true) 1 else "a", хотя это также не может привести к сбою во время выполнения. Достаточно богатая система типов не может быть полноценной и полной одновременно. Обычно надежность предпочтительнее полноты

Что такое звуковой язык программирования?

https://math.stackexchange.com/questions/105575/какая-есть-разница-между-полнотой-и-надежностью-в-логике-первого-порядка

Вы можете спросить, почему trait Plugin { base: Base => } не всегда означает то же, что и trait Plugin extends Base. Что ж, это дизайнерское решение создателей системы типов Scala. В частности, если бы это было одно и то же, то два разных признака trait A {this: B =>}, trait B {this: A =>} имели бы один и тот же тип.

Также в trait Plugin extends BaseBase должен быть классом/чертой. Но в trait Plugin { base: Base => }Base может быть типом.

Почему тип scala self не является подтипом его требования

В чем разница между я-типами и подклассами черт?

Разница между наследованием признаков и аннотацией собственного типа

Что более идиоматично в Scala: trait TraitA extends TraitB или trait TraitA { self: TraitB => }

Хотя я согласен, что разница между классом VS типа очень важна. Я считаю это ограничением компилятора TBH. Если я не упустил пример, в котором вы можете получить Plugin, который также не является Base, тогда компилятор должен иметь возможность генерировать отношения синтетического подтипа.

Luis Miguel Mejía Suárez 26.04.2023 14:40

@LuisMiguelMejíaSuárez Какая тогда должна быть разница между trait Plugin { base: Base => } и trait Plugin extends Base? Зачем тогда вам иметь trait Plugin { base: Base => }, если у вас уже есть trait Plugin extends Base?

Dmytro Mitin 26.04.2023 14:57

Это зависит от того, в каком контексте вы ставите этот вопрос. Если все, что вас волнует, это семантика подтипов, то да, вы правы. Но с точки зрения моделирования/дизайна идея self типов (ИМХО) состоит в том, чтобы позволить пользователю решить, какую конкретную реализацию смешивать, но заставить его решить. Нормальное подтипирование не обязательно обеспечивает это, по крайней мере, не так хорошо. Например, я обычно делаю микс-ин sealed, потому что я не хочу, чтобы пользователи могли добавлять новые реализации, просто выберите одну из немногих, которые я им предоставлю.

Luis Miguel Mejía Suárez 26.04.2023 15:02

@LuisMiguelMejíaSuárez Хорошо, я вижу, при наследовании мы указываем родителя (Base) в месте определения Plugin, при самотипировании мы должны дополнительно указать родителя (Base) в месте определения Plugin наследника. Вероятно, вам придется сделать Base запечатанным в обоих случаях.

Dmytro Mitin 26.04.2023 17:07

Итак, посмотрите на этот пример: scastie.scala-lang.org/BalmungSan/O3WnacSERJWqPWQEmfnA8Q/2 как видите b4 не будет компилироваться, потому что не смешивается с плагином. Нет никакого способа (насколько мне известно) получить такое же поведение без самотипов.

Luis Miguel Mejía Suárez 26.04.2023 17:46

@LuisMiguelMejíaSuárez Не b4 компилируется в случае наследования, потому что b4 не смешивает плагин? scastie.scala-lang.org/DmytroMitin/OlQ6L1aIRROZm5WRfd1p7Q/1

Dmytro Mitin 26.04.2023 18:09

Кстати github.com/lampepfl/dotty/issues/7374

Dmytro Mitin 26.04.2023 18:38

Конечно, но вы можете переопределить его самостоятельно: scastie.scala-lang.org/BalmungSan/B5Fs4D9aSwiKDtAygEuLuA И я не хочу, чтобы пользователи могли это делать, но выберите одну из моих реализаций плагина. (для справки, я делал это как 1 раз за всю свою жизнь, это не обычный дизайн API, TBH)

Luis Miguel Mejía Suárez 26.04.2023 18:46

@DmytroMitin Как видно из метода asBase, компилятор знает, что все экземпляры Plugin можно присвоить Plugin & Base. Итак, было бы хорошей идеей добавить в язык возможность получения типа, который будет иметь каждый экземпляр самоаннотированного класса вне определения? Например, Plugin.withSelfType, который будет иметь тип Plugin & Base. Таким образом, отношения между Plugin и Base не должны повторяться в разных местах кода.

Readren 27.04.2023 18:25

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