Хороший стиль: введите псевдонимы и подклассифицируйте пустые классы/черты

В Twitter Эффективные Scala - псевдонимы типов, они говорят:

Не используйте подклассы, когда будет выполняться псевдоним.

trait SocketFactory extends (SocketAddress => Socket)

a SocketFactory - это функция, которая создает Socket. Использование типа псевдоним

type SocketFactory = SocketAddress => Socket

лучше. Теперь мы можем предоставить функциональные литералы для значений типа SocketFactory, а также использовать функциональный состав:     val addrToInet: SocketAddress = > Long val inetToSocket: Long = > Socket

val factory: SocketFactory = addrToInet andThen inetToSocket

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

Мы говорим о том, что:

trait Base
trait T1 extends Base // subclassing
type T2 = Base        // type alias

Очевидно, вы не можете использовать псевдоним типа в качестве замены, когда класс/свойство имеет тело или хранит информацию.

Таким образом, использование псевдонимов типов (T2), а не расширение с помощью признака или класса (T1), имеет следующие преимущества:

  • Как говорится выше, мы можем создавать функциональные литералы.
  • Мы не будем создавать файл .class, для компилятора будет меньше (теоретически).

Однако он имеет следующие недостатки:

  • Чтобы быть доступным в том же пространстве имен (пакет), вам необходимо определить тип в объекте пакета, который, вероятно, будет находиться в другом файле с сайта-пользователя.
  • Вы не можете перетащить 'Open Type' ctrl-shift-T в псевдоним в Eclipse, но вы можете открыть декларацию (F3) в Eclipse. Вероятно, это будет исправлено в будущем.
  • Вы не можете использовать псевдоним типа с другого языка, например Java.
  • Если псевдоним типа параметризуется, стирание предотвращает совпадение шаблона с тем же способом, что и для признаков.

Четвертый пункт является самым серьезным для меня:

trait T1[T]
trait T2 extends T1[Any]
type T3 = T1[Any]

class C2 extends T2

val c = new C2
println("" + (c match { case t: T3 => "T3"; case _ => "any" }))
println("" + (c match { case t: T2 => "T2"; case _ => "any"  }))

Это дает:

T3
T2

Компилятор дает предупреждение о первом совпадении шаблонов, которое явно не работает должным образом.

Итак, наконец, вопрос. Существуют ли другие преимущества или недостатки использования псевдонимов типов, а не расширения признака/класса?

Ответ 1

Я думаю, что они на самом деле являются ключевыми типами псевдонимов и черт характера. Список различий продолжается и продолжается:

  • Сокращенные синтаксисы для псевдонимов типов (например, x => x+7 будут работать как type I2I = Int => Int), а не черты.
  • Черты могут нести дополнительные данные, алиасы типов не могут.
  • Implicits работает для псевдонимов типов, но не с чертами.
  • Черты обеспечивают безопасность и соответствие типа таким образом, что псевдонимы типов не имеют.
  • Типичные псевдонимы имеют строгие правила о переопределении в подклассах; т.е. т.е. т.д. (все идет).

среди других.

Это потому, что вы делаете совершенно разные вещи в двух случаях. Типичные псевдонимы - это просто способ сказать: "Хорошо, когда я печатаю Foo, я на самом деле имею в виду" Бар ". Они такие же. Получилось? После этого вы можете заменить имя Foo на Bar везде и всегда, когда захотите. Единственное ограничение заключается в том, что как только вы решите, какой тип вы не можете изменить.

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

Итак, теперь, когда мы установили, что они действительно разные, возникает вопрос, как их использовать. И ответ довольно прост: если вам нравится существующий класс так же, как и он, за исключением того, что вам не нравится имя, используйте псевдоним типа. Если вы хотите создать свой собственный новый объект, отличный от других, используйте черту (или подкласс). Первый - в основном для удобства, в то время как последний - для безопасности или возможностей добавленного типа. Я не думаю, что какое-либо правило, говорящее, чтобы использовать одно, а не другое, действительно фиксирует суть - понимайте функции обоих и используйте каждый, когда это те функции, которые вы хотите.

(И тогда существуют экзистенциальные типы, которые предоставляют аналогичную возможность для дженериков... но оставьте это для другого вопроса.)

Ответ 2

Они отличаются друг от друга тем, что псевдоним типа определяет отношение равенства типа (т.е. T1 <: T2 & T1 > : T2), в то время как расширение признака определяет строгое отношение подтипа (то есть T1 <: T2 &! (T1 > : T2)). Используйте их с умом.