Понимание ключевого слова 'type' в Scala

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

type FunctorType = (LocalDate, HolidayCalendar, Int, Boolean) => LocalDate

FunctorType - это какой-то псевдоним, но что он означает?

Ответ 1

Да, псевдоним типа FunctorType является просто сокращением для

(LocalDate, HolidayCalendar, Int, Boolean) => LocalDate

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

def doSomeThing(f: FunctorType)

который будет интерпретироваться компилятором как

def doSomeThing(f: (LocalDate, HolidayCalendar, Int, Boolean) => LocalDate)

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

Есть также несколько других интересных примеров использования для type, как описано, например, в этой главе программирования в Scala.

Ответ 2

На самом деле ключевое слово type в Scala может сделать гораздо больше, чем просто наложение сложного типа на более короткое имя. Он вводит члены типа.

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

В вашем конкретном случае type действительно вводит псевдоним, который позволяет писать более сжатый код. Система типов просто заменяет псевдоним фактическим типом при выполнении проверки типа.

Но у вас также может быть что-то вроде этого

trait Base {
  type T

  def method: T
}

class Implementation extends Base {
  type T = Int

  def method: T = 42
}

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

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

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

Для получения более подробной информации см. этот отличный ответ:

Scala: абстрактные типы против дженериков

Ответ 3

Просто пример того, как использовать "тип" как псевдоним:

type Action = () => Unit

В приведенном выше определении определяется действие как псевдоним типа процедур (методов), которые принимают пустой список параметров и возвращают Unit.

Ответ 4

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

Абстрактный тип:

type T

T выше говорит, что этот тип, который будет использоваться, пока неизвестен, и в зависимости от конкретного подкласса он будет определен. Лучший способ всегда понять концепции программирования - привести пример. Предположим, у вас есть следующий сценарий:

Without Type Abstraction

Здесь вы получите ошибку компиляции, потому что метод eat в классах Cow и Tiger не переопределяет метод eat в классе Animal, потому что их типы параметров различны. Это Трава в классе Корова и Мясо в классе Tiger vs. Food в классе Animal, который является суперклассом, и все подклассы должны соответствовать.

Теперь вернемся к абстракции типов, используя следующую диаграмму и просто добавляя абстракцию типов, вы можете определить тип ввода в соответствии с самим подклассом.

With Abstract Type

Теперь посмотрите на следующие коды:

  val cow1: Cow = new Cow
  val cow2: Cow = new Cow

  cow1 eat new cow1.SuitableFood
  cow2 eat new cow1.SuitableFood

  val tiger: Tiger = new Tiger
  cow1 eat new tiger.SuitableFood // Compiler error

Компилятор доволен, и мы улучшаем наш дизайн. Мы можем кормить нашу корову коровой. Подходящие корма и компилятор мешают нам кормить коров едой, подходящей для тигра. Но что, если мы хотим сделать различие между типом коровы1 Подходящая еда и корова2 Подходящая еда? Другими словами, в некоторых сценариях было бы очень удобно, если бы путь, которым мы достигаем, к типу (конечно, через объект) действительно имел значение. Благодаря расширенным возможностям в Scala возможно:

Типы, зависящие от пути: объекты Scala могут иметь типы в качестве членов. Значение типа зависит от пути, который вы используете для доступа к нему. Путь определяется ссылкой на объект (он же экземпляр класса). Чтобы реализовать этот сценарий, вам нужно определить класс Grass внутри Cow, т.е. Cow - это внешний класс, а Grass - внутренний класс. Структура будет такой:

  class Cow extends Animal {
    class Grass extends Food
    type SuitableFood = Grass
    override def eat(food: this.SuitableFood): Unit = {}
  }

  class Tiger extends Animal {
    class Meat extends Food
    type SuitableFood = Meat
    override def eat(food: this.SuitableFood): Unit = {}
  }

Теперь, если вы попытаетесь скомпилировать этот код:

  1. val cow1: Cow = new Cow
  2. val cow2: Cow = new Cow

  3. cow1 eat new cow1.SuitableFood
  4. cow2 eat new cow1.SuitableFood // compilation error

В строке 4 вы увидите ошибку, поскольку Grass теперь является внутренним классом Cow, поэтому для создания экземпляра Grass нам нужен объект cow, и этот объект cow определяет путь. Таким образом, 2 объекта коровы дают начало двум различным путям. В этом сценарии cow2 хочет есть только пищу, специально созданную для него. Так:

cow2 eat new cow2.SuitableFood

Теперь все счастливы :-)