scalabook

Форк
0
216 строк · 10.6 Кб

Трейты

Если провести аналогию с Java, то Scala trait

похож на интерфейс в Java 8+.

trait

-ы могут содержать:

  • абстрактные методы и поля
  • конкретные методы и поля
  • могут иметь параметры конструктора, как и классы

В базовом использовании trait

может использоваться как интерфейс, определяющий только абстрактные члены, которые будут реализованы другими классами:

trait Employee:
def id: Int
def firstName: String
def lastName: String

traits

также могут содержать определенные методы:

trait HasLegs:
def numLegs: Int
def walk(): Unit
def stop() = println("Stopped walking")
trait HasTail:
def tailColor: String
def wagTail() = println("Tail is wagging")
def stopTail() = println("Tail is stopped")

Классы и объекты могут расширять несколько traits

, что позволяет с их помощью создавать небольшие модули.

class IrishSetter(name: String) extends HasLegs, HasTail:
val numLegs = 4
val tailColor = "Red"
def walk() = println("I’m walking")
override def toString = s"$name is a Dog"

В классе IrishSetter

реализованы все абстрактные параметры и методы, поэтому можно создать его экземпляр:

val d = IrishSetter("Big Red")
// d: IrishSetter = Big Red is a Dog

Класс также может переопределять методы trait

-ов при необходимости.

Аргументы trait

оцениваются непосредственно перед его инициализацией.

Особенности при расширении trait с параметрами конструктора

Одна потенциальная проблема с параметрами trait

заключается в том, как предотвратить двусмысленность. Например, можно попробовать расширить Greeting
дважды с разными параметрами:

trait Greeting(val name: String):
def msg = s"How are you, $name"
class C extends Greeting("Bob"):
println(msg)
class D extends C, Greeting("Bill")
// error:
// trait Greeting is already implemented by superclass C,
// its constructor cannot be called again

На самом деле эта программа не скомпилируется, потому что она нарушает второе правило для параметров trait

:

  1. Если класс C
    расширяет параметризованный trait
    T
    , а его суперкласс — нет, то C
    должен передать аргументы в T
    .
  2. Если класс C
    расширяет параметризованный trait
    T
    и его суперкласс тоже, то C
    не должен передавать аргументы в T
    .
  3. trait
    -ы никогда не должны передавать аргументы родительским trait
    -ам.

Вот трейт, расширяющий параметризованный трейт Greeting

.

trait FormalGreeting extends Greeting:
override def msg = s"How do you do, $name"

Правильный способ создания класса E

, расширяющего оба - Greeting
и FormalGreeting
(в любом порядке) - такой:

class E extends Greeting("Bob"), FormalGreeting
(new C).msg
// How are you, Bob
// res1: String = "How are you, Bob"
(new E).msg
// res2: String = "How do you do, Bob"

Trait-ы с параметрами контекста

Правило "требуется явное расширение" ослабляется, если отсутствующий trait

содержит только параметры контекста. В этом случае ссылка на трейт неявно вставляется как дополнительный родитель с выводимыми аргументами. Например, вот вариант Greeting
, где адресат является параметром контекста типа ImpliedName
:

case class ImpliedName(name: String):
override def toString = name
trait ImpliedGreeting(using val iname: ImpliedName):
def msg = s"How are you, $iname"
trait ImpliedFormalGreeting extends ImpliedGreeting:
override def msg = s"How do you do, $iname"
class F(using iname: ImpliedName) extends ImpliedFormalGreeting

Определение F

в последней строке неявно расширяется до

class F(using iname: ImpliedName) extends
Object,
ImpliedGreeting(using iname),
ImpliedFormalGreeting(using iname)

Обратите внимание на вставленную ссылку на ImpliedFormalGreeting

- родительский trait ImpliedGreeting
, которая не упоминалась явно.

given ImpliedName = ImpliedName("Bob")
(new F).msg
// res4: String = "How do you do, Bob"

Transparent traits

Trait-ы используются в двух случаях:

  • как примеси для других классов и trait-ов
  • как типы констант, определений или параметров

Некоторые trait-ы используются преимущественно в первой роли, и обычно их нежелательно видеть в выводимых типах. Примером может служить trait Product

, который компилятор добавляет в качестве примеси к каждому case class-у или case object-у. В Scala 2 этот родительский trait иногда делает выводимые типы более сложными, чем они должны быть. Пример:

trait Kind
case object Var extends Kind
case object Val extends Kind
val x = Set(if false then Val else Var)

Здесь предполагаемый тип x

равен Set[Kind & Product & Serializable]
, тогда как можно было бы надеяться, что это будет Set[Kind]
. Основания для выделения именно этого типа следующие:

  • тип условного оператора, приведенного выше, является типом объединения Val | Var
    .
  • тип объединения расширяется в выводе типа до наименьшего супертипа, который не является типом объединения.

В примере - это тип Kind & Product & Serializable

, так как все три trait-а являются trait-ами обоих Val
и Var
. Таким образом, этот тип становится предполагаемым типом элемента набора.

Scala 3 позволяет помечать trait примеси как transparent

, что означает, что он может быть подавлен при выводе типа. Вот пример, который повторяет строки приведенного выше кода, но теперь с новым transparent trait S
вместо Product
:

transparent trait S
trait Kind
object Var extends Kind, S
object Val extends Kind, S
val x = Set(if true then Val else Var)
// x: Set[Kind] = ...

Теперь x

предположил тип Set[Kind]
. Общий transparent trait S
не появляется в выводимом типе.

Прозрачные trait-ы

Trait-ы scala.Product, java.io.Serializable и java.lang.Comparable автоматически считаются transparent

. Другие трейты превращаются в transparent trait
с помощью модификатора transparent
.

Как правило, transparent trait

— это trait-ы, влияющие на реализацию наследуемых классов, и trait-ы, которые сами по себе обычно не используются как типы. Два примера из стандартной библиотеки коллекций:

  • IterableOps, который предоставляет реализации методов для Iterable.
  • StrictOptimizedSeqOps, который оптимизирует некоторые из этих реализаций для последовательностей с эффективной индексацией.

Как правило, любой trait, расширяемый рекурсивно, является хорошим кандидатом на объявление transparent

.

Правила вывода типов говорят, что transparent trait

удаляются из пересечений, где это возможно.


Ссылки:

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.