Zrozumienie, do czego służy słowo kluczowe „typ” w Scali

144

Jestem nowy w Scali i nie mogłem znaleźć wiele informacji na temat typesłowa kluczowego. Próbuję zrozumieć, co może oznaczać to wyrażenie:

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

FunctorType jest jakimś aliasem, ale co to znaczy?

Core_Dumped
źródło

Odpowiedzi:

148

Tak, alias typu FunctorType jest tylko skrótem dla

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

Aliasy typów są często używane, aby reszta kodu była prosta: możesz teraz pisać

def doSomeThing(f: FunctorType)

który zostanie zinterpretowany przez kompilator jako

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

Pomaga to uniknąć definiowania wielu niestandardowych typów, które są na przykład krotkami lub funkcjami zdefiniowanymi w innych typach.

Istnieje również kilka innych przypadków ciekawe zastosowanie dla type, jak opisano na przykład w tym rozdziale o Programowanie w Scala .

Roland Ewald
źródło
198

W rzeczywistości typesłowo kluczowe w Scali może zrobić znacznie więcej niż tylko aliasowanie skomplikowanego typu do krótszej nazwy. Wprowadza członków typu .

Jak wiesz, klasa może mieć członków pól i członków metod. Cóż, Scala pozwala również na to, aby klasa miała członków typu.

W twoim konkretnym przypadku typejest rzeczywiście wprowadzenie aliasu, który pozwoli ci napisać bardziej zwięzły kod. System typów po prostu zastępuje alias rzeczywistym typem podczas sprawdzania typu.

Ale możesz też mieć coś takiego

trait Base {
  type T

  def method: T
}

class Implementation extends Base {
  type T = Int

  def method: T = 42
}

Podobnie jak każdy inny element członkowski klasy, elementy członkowskie typu mogą być również abstrakcyjne (po prostu nie określasz, jaka jest ich wartość) i można je przesłonić w implementacjach.

Składowe typu można postrzegać jako podwójne elementy ogólne, ponieważ wiele rzeczy, które można zaimplementować za pomocą typów ogólnych, można przetłumaczyć na elementy członkowskie typu abstrakcyjnego.

Więc tak, mogą być używane do aliasingu, ale nie ograniczaj ich tylko do tego, ponieważ są one potężną cechą systemu typów Scali.

Zobacz tę doskonałą odpowiedź, aby uzyskać więcej informacji:

Scala: typy abstrakcyjne a typy ogólne

Marius Danila
źródło
44
Należy pamiętać, że użycie type wewnątrz klasy tworzy składową typu zamiast aliasu. Jeśli więc potrzebujesz tylko aliasu typu, zdefiniuj go w obiekcie towarzyszącym.
Rüdiger Klaehn
9

Podobała mi się odpowiedź Rolanda Ewalda, ponieważ opisał ją za pomocą bardzo prostego przypadku użycia aliasu typu, a po więcej szczegółów przedstawił bardzo fajny tutorial. Ponieważ jednak w tym poście wprowadzono inny przypadek użycia nazwany typami składowymi , chciałbym wspomnieć o najbardziej praktycznym przypadku użycia, który bardzo mi się spodobał: (ta część jest wzięta stąd :)

Typ abstrakcyjny:

type T

T powyżej mówi, że ten typ, który będzie używany, jest jeszcze nieznany iw zależności od konkretnej podklasy zostanie zdefiniowany. Najlepszym sposobem zawsze na zrozumienie koncepcji programowania jest podanie przykładu: Załóżmy, że masz następujący scenariusz:

Bez abstrakcji typów

Tutaj otrzymasz błąd kompilacji, ponieważ metoda eat w klasach Cow i Tiger nie przesłania metody eat w klasie Animal, ponieważ ich typy parametrów są różne. To Trawa w klasie Krowa i Mięso w klasie Tygrys vs. Jedzenie w klasie Zwierzę, która jest superklasa i wszystkie podklasy muszą być zgodne.

Wracając do abstrakcji typów, korzystając z poniższego diagramu i dodając po prostu abstrakcję typu, możesz zdefiniować typ danych wejściowych zgodnie z samą podklasą.

Z Abstrakcyjnym Typem

Teraz spójrz na następujące kody:

  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

Kompilator jest zadowolony i ulepszamy nasz projekt. Możemy karmić naszą krowę krową. Odpowiednie jedzenie i kompilator uniemożliwiają nam karmienie krowy pokarmem odpowiednim dla Tygrysa. Ale co, jeśli chcemy odróżnić rodzaj krowy1 OdpowiedniFood i krowa2 SuitabeFood. Innymi słowy, byłoby bardzo przydatne w niektórych scenariuszach, gdyby ścieżka, po której docieramy do typu (oczywiście przez obiekt), miała w zasadzie znaczenie. Dzięki zaawansowanym funkcjom w scali możliwe jest:

Typy zależne od ścieżki: obiekty Scala mogą mieć typy jako elementy członkowskie. Znaczenie typu zależy od ścieżki, której używasz, aby uzyskać do niego dostęp. Ścieżka jest określana przez odniesienie do obiektu (inaczej instancji klasy). Aby zaimplementować ten scenariusz, musisz zdefiniować klasę Grass wewnątrz krowy, tj. Cow to klasa zewnętrzna, a Grass to klasa wewnętrzna. Struktura będzie wyglądać następująco:

  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 = {}
  }

Teraz, jeśli spróbujesz skompilować ten kod:

  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

W linii 4 zobaczysz błąd, ponieważ Grass jest teraz wewnętrzną klasą Cow, dlatego aby utworzyć instancję Grass, potrzebujemy obiektu krowa, który określa ścieżkę. Zatem 2 krowy dają początek 2 różnym ścieżkom. W tym scenariuszu krowa2 chce jeść tylko jedzenie specjalnie dla niego stworzone. Więc:

cow2 eat new cow2.SuitableFood

Teraz wszyscy są szczęśliwi :-)

Mehran
źródło
5

Oto przykład, jak używać „type” jako aliasu:

type Action = () => Unit

Powyższa definicja definiuje Action jako alias typu procedur (metod), które pobierają pustą listę parametrów i zwracają Unit.

sofiene zaghdoudi
źródło