Подтвердить что ты не робот

Что и когда следует использовать Scala для ключевого слова forSome?

В чем разница между List[T] forSome {type T} и List[T forSome {type T}]? Как читать их на "английском языке"? Как мне получить ключевое слово forSome? Каковы практические применения forSome? Какие полезные практические и более сложные, чем простые T forSome {type T} обычаи?

4b9b3361

Ответ 1

Внимание: (обновление 2016-12-08). Ключевое слово forSome очень вероятно уходит с Scala 2.13 или 2.14, в соответствии с Мартином Одерским, говорящим на ScalaX 2016. Замените его на путь зависимых типов или с атрибутами анонимного типа (A[_]). Это возможно в большинстве случаев. Если у вас есть край, где это невозможно, отредактируйте свой код или ослабьте ограничения типа.

Как читать "forSome" (неофициальным способом)

Обычно, когда вы используете общий API, API гарантирует вам, что он будет работать с любым типом, который вы предоставляете (вплоть до некоторых заданных ограничений). Поэтому, когда вы используете List[T], API списка гарантирует, что он будет работать с любым типом T, который вы предоставляете.

С forSome (так называемые экзистенциально квантованные параметры типа) это наоборот. API предоставит вам тип (а не вы), и он гарантирует, что он будет работать с этим типом, который он вам предоставил. Семантика заключается в том, что конкретный объект даст вам что-то типа T. Тот же объект также примет то, что он вам предоставил. Но никакой другой объект не может работать с этими T, и ни один другой объект не может предоставить вам что-то типа T.

Идея "экзистенциально квантифицированная" такова: существует (по крайней мере) один тип T (в реализации) для выполнения контракта API. Но я не скажу вам, какой тип он есть.

forSome можно прочитать аналогично: для некоторых типов T выполняется контракт API. Но это не обязательно для всех типов T. Поэтому, когда вы предоставляете некоторый тип T (а не тот, который скрыт в реализации API), компилятор не может гарантировать, что вы получили правильный T. Поэтому он выдает ошибку типа.

Применяется к вашему примеру

Итак, когда вы видите List[T] forSome {type T} в API, вы можете прочитать его следующим образом: API предоставит вам List неизвестного типа T. Он с радостью примет этот список и будет работать с ним. Но он не скажет вам, что такое T. Но вы знаете, по крайней мере, что все элементы списка имеют один и тот же тип T.

Второй - немного сложнее. Опять же API предоставит вам List. И он будет использовать какой-то тип T и не скажет вам, что такое T. Но для каждого элемента можно выбрать другой тип. API реального мира установит некоторые ограничения для T, поэтому он может фактически работать с элементами списка.

Заключение

forSome полезен, когда вы пишете API, где каждый объект представляет собой реализацию API. Каждая реализация предоставит вам некоторые объекты и вернет эти объекты обратно. Но вы не можете смешивать объекты из разных реализаций и не можете сами создавать объекты. Вместо этого вы всегда должны использовать соответствующие функции API для получения некоторых объектов, которые будут работать с этим API. forSome обеспечивает очень строгий тип инкапсуляции. Вы можете прочитать forSome следующим образом:

Контракт API сбрасывает true для некоторых типов. Но вы не знаете, который набирает ее. Следовательно, вы не можете предоставить свой собственный тип и вы не можете создавать свои собственные объекты. Вы должны использовать предоставленные через API, который использует forSome.

Это довольно неформально и может быть ошибочным в некоторых случаях. Но это должно помочь вам понять концепцию.

Ответ 2

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

Нет реальной значимой разницы между List[T] forSome { type T } и List[T forSome { type T }], но мы можем видеть разницу между двумя следующими типами:

class Foo[A]

type Outer = List[Foo[T]] forSome { type T }
type Inner = List[Foo[T] forSome { type T }]

Мы можем прочитать первый как "список foos T, для некоторого типа T". Для всего списка существует один T. Вторая, с другой стороны, может быть прочитана как "список foos, где каждый foo имеет значение T для некоторого T".

Другими словами, если у нас есть список outer: Outer, мы можем сказать, что "существует некоторый тип T такой, что outer является списком foos T", где для список типа Inner, мы можем только сказать, что "для каждого элемента списка существует некоторое T такое, что этот элемент является foo of T". Последнее слабее - это меньше говорит о списке.

Итак, например, если у нас есть следующие два списка:

val inner: Inner = List(new Foo[Char], new Foo[Int])
val outer: Outer = List(new Foo[Char], new Foo[Int])

Первый будет компилировать только тонкий - каждый элемент списка является Foo[T] для некоторого T. Второй не будет компилироваться, так как не существует T, так что каждый элемент списка является Foo[T].