Хороший стиль: введите псевдонимы и подклассифицируйте пустые классы/черты
В 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)). Используйте их с умом.