Почему компилятор Scala не может выдавать предупреждение о сопоставлении с образцом для незапечатанных классов / признаков?

10

Интересно, если я использую Un Seal traitили abstract classScala, а затем использую сопоставление с образцом, не знает ли компилятор во время компиляции для этого конкретного образца, какие возможные реализации этой черты / класса доступны? Так что, если это так, может ли он не выдавать предупреждения о совпадении с образцом, даже если trait/ abstract classне запечатан, потому что он знает, какие типы могут использоваться, проверяя все возможные зависимости / импорт?

Например, если у меня есть Option[A]и я сопоставляю шаблоны только для, Some[A]но не для None, компилятор будет жаловаться, потому что Optionзапечатан.

Если компилятор не может знать / решить это, то почему он не может? И если компилятор (теоретически) может это сделать, то каковы причины того, что он не используется в Scala? Существуют ли другие языки, поддерживающие такое поведение?

valenterry
источник
Непонятно, о чем ты спрашиваешь. Вы хотите, чтобы компилятор выдавал предупреждение, если выражение соответствия не охватывает все возможные входные данные? Возможно, пример прояснит ваш вопрос.
kdgregory
2
Если кто-то может представить новый подкласс, сопоставление с образцом никогда не будет исчерпывающим. Например, вы создаете некоторый абстрактный класс Fooс подклассами A, Bи C, и все ваши сопоставления с образцом соответствуют только этим трем. Ничто не мешает мне добавить новый подкласс, Dкоторый взорвет ваши соответствия шаблону.
Довал
@kdgregory Да, ты понял. Я добавил пример, чтобы сделать его более понятным.
valenterry
3
Проверка всех импортов недостаточна для обнаружения всех возможных подклассов. Другой подкласс может быть объявлен в отдельном файле класса, который позже загружается во время выполнения через java.lang.ClassLoader.
Амон

Ответы:

17

Выяснение всех подклассов класса называется анализом иерархии классов, и выполнение статического CHA на языке с динамической загрузкой кода эквивалентно решению проблемы остановки.

Кроме того, одной из целей Scala является отдельная компиляция и развертывание независимых модулей, поэтому компилятор просто не может знать, является ли класс подклассами в другом модуле, потому что он никогда не рассматривает более одного модуля. (В конце концов, вы можете скомпилировать модуль с интерфейсом какого-либо другого модуля, даже если этот модуль не существует в вашей системе!). Поэтому sealedнеобходимо, чтобы все подклассы были определены в одном модуле компиляции.

Это также одна из причин того, почему JVM могут так выгодно конкурировать с компиляторами C ++: компиляторы C ++ обычно являются статическими компиляторами, поэтому они не могут вообще выяснить, является ли метод переопределенным или нет, и, следовательно, не могут встроить его. JVM OTOH, как правило, являются динамическими компиляторами, им не нужно выполнять CHA, чтобы выяснить, переопределен ли метод или нет, они могут просто посмотреть на иерархию классов во время выполнения. И даже если на более позднем этапе выполнения программы появится новый подкласс, которого раньше не было, ничего страшного, просто перекомпилируйте этот кусок кода без вставки.

Примечание: все это относится только к Scala. JVM не имеет понятия о sealed, так что вполне возможно создать подклассы sealedклассов из другого языка JVM, поскольку нет способа передать это на другой язык. sealedСвойство записывается в ScalaSigаннотации, но компиляторы других Языков не принимать эти аннотаций во внимание, очевидно.

Йорг Миттаг
источник
3

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

А что ты покупаешь? Запах кода заключается в написании совпадений с образцами, которые необходимо часто менять при добавлении нового производного класса. Это нарушение принципа открытого / закрытого . Правильно используйте наследование, и вам не нужно будет писать подобные совпадения. И да, принцип открытия / закрытия также применим к функциональным языкам без наследования на основе классов. Фактически, между такими функциями, как классы типов, мультиметоды и просто обычные функции высшего порядка, функциональные языки значительно упрощают расширение без изменений.

Карл Билефельдт
источник
1
It can be done (at least for all classes known at compile time), it's just expensive.Но если программа не на 100% автономна (то есть зависит от внешних .jarфайлов), не могли бы вы пробраться в новый подкласс после компиляции через один из jars? Таким образом, компилятор может сказать вам: «Ваши сопоставления с образцами сейчас исчерпывающие, но это может измениться, если какая-либо из ваших зависимостей изменится», что довольно бесполезно, поскольку смысл наличия внешних зависимостей состоит в возможности их обновления без перекомпиляции!
Довал
Отсюда и отказ от ответственности. На практике, если вы достаточно тесно связаны между собой, чтобы требовать исчерпывающего сопоставления зависимостей, внешних или иных, вы все равно захотите перекомпилировать.
Карл Билефельдт
@Doval Затем вы должны использовать некоторую форму делегирования и позволить вызываемому классу решать, что делать, и инвертировать управление. Это то, для чего предназначалось ООП. Если вы не хотите этого, у вас есть текущая проблема.
Сани
@ArtB Я не понимаю, какое отношение имеет к этому делегирование или инверсия управления.
Доваль
Если вы хотите, чтобы он работал с людьми, которые могут добавлять его извне, тогда вызовите класс и переместите логику в эти классы.
Сани