Che cos'è un tipo di tipo superiore in Scala?


275

Puoi trovare quanto segue sul Web:

  1. Tipo di tipo superiore == tipo costruttore?

    class AClass[T]{...} // For example, class List[T]

    Alcuni sostengono che si tratta di un tipo di tipo più elevato, poiché si estrae dai tipi che sarebbero conformi alla definizione.

    I tipi di tipo superiore sono tipi che prendono altri tipi e ne costruiscono un nuovo

    Questi però sono anche conosciuti come costruttore di tipi . (Ad esempio, in Programmazione in Scala ).

  2. Tipo di tipo superiore == tipo costruttore che utilizza il tipo costruttore come parametro di tipo?

    Nel documento Generics of a Higher Kind , puoi leggere

    ... tipi astratti su tipi astratti su tipi ("tipi di tipo superiore") ... "

    che suggerisce questo

    class XClass[M[T]]{...} // or
    
    trait YTrait[N[_]]{...} // e.g. trait Functor[F[_]]

    è un tipo di tipo superiore.

Quindi, con questo in mente, è difficile distinguere tra costruttore di tipo , tipo di tipo superiore e costruttore di tipo che considera i costruttori di tipo come parametro di tipo , quindi la domanda sopra.


Aggiunta la Functor di Landei come esempio.
Lutz,

Risposte:


284

Permettetemi di rimediare a iniziare un po 'di questa confusione accendendo con un po' di ambiguità. Mi piace usare l'analogia con il livello di valore per spiegarlo, poiché le persone tendono ad avere più familiarità con esso.

Un costruttore di tipi è un tipo che è possibile applicare agli argomenti di tipo per "costruire" un tipo.

Un costruttore di valori è un valore che è possibile applicare agli argomenti valore per "costruire" un valore.

I costruttori di valori sono generalmente chiamati "funzioni" o "metodi". Si dice anche che questi "costruttori" siano "polimorfici" (perché possono essere usati per costruire "cose" di "forma" variabile), o "astrazioni" (poiché si astraggono su ciò che varia tra le diverse istanze polimorfiche).

Nel contesto dell'astrazione / polimorfismo, il primo ordine si riferisce al "monouso" dell'astrazione: astratti su un tipo una volta, ma quel tipo stesso non può astrarre su nulla. I generici Java 5 sono di primo ordine.

L'interpretazione di primo ordine delle suddette caratterizzazioni di astrazioni sono:

Un costruttore di tipi è un tipo che è possibile applicare agli argomenti di tipo corretto per "costruire" un tipo corretto.

Un costruttore di valori è un valore che è possibile applicare agli argomenti del valore corretto per "costruire" un valore corretto.

Per sottolineare che non vi è alcuna astrazione (suppongo che potresti chiamarlo "ordine zero", ma non l'ho visto usato da nessuna parte), come il valore 1o il tipo String, di solito diciamo che qualcosa è un valore o tipo "corretto".

Un valore corretto è "immediatamente utilizzabile", nel senso che non è in attesa di argomenti (non si basa su di essi). Pensali come valori che puoi facilmente stampare / ispezionare (serializzare una funzione è barare!).

Un tipo corretto è un tipo che classifica i valori (compresi i costruttori di valori), i costruttori di tipi non classificano alcun valore (devono prima essere applicati agli argomenti di tipo giusto per produrre un tipo corretto). Per creare un'istanza di un tipo, è necessario (ma non sufficiente) che sia un tipo corretto. (Potrebbe essere una classe astratta o una classe a cui non hai accesso.)

"Ordine superiore" è semplicemente un termine generico che significa un uso ripetuto di polimorfismo / astrazione. Significa la stessa cosa per tipi e valori polimorfici. Concretamente, un'astrazione di ordine superiore si sottrae a qualcosa che si sottrae a qualcosa. Per i tipi, il termine "di tipo superiore" è una versione speciale del più generale "ordine superiore".

Pertanto, la versione di ordine superiore della nostra caratterizzazione diventa:

Un costruttore di tipi è un tipo che è possibile applicare agli argomenti di tipo (tipi propri o costruttori di tipi) per "costruire" un tipo corretto (costruttore).

Un costruttore di valori è un valore che è possibile applicare agli argomenti valore (valori propri o costruttori di valori) per "costruire" un valore corretto (costruttore).

Quindi, "ordine superiore" significa semplicemente che quando dici "astrarre su X", lo intendi davvero! Ciò Xche viene sottratto non perde i suoi "diritti di astrazione": può astrarre tutto ciò che vuole. (A proposito, qui uso il verbo "abstract" per indicare: tralasciare qualcosa che non è essenziale per la definizione di un valore o tipo, in modo che possa essere variata / fornita dall'utente dell'astrazione come argomento .)

Ecco alcuni esempi (ispirati alle domande di Lutz via e-mail) di valori e tipi propri, di primo ordine e di ordine superiore:

                   proper    first-order           higher-order

values             10        (x: Int) => x         (f: (Int => Int)) => f(10)
types (classes)    String    List                  Functor
types              String    ({type λ[x] = x})#λ   ({type λ[F[x]] = F[String]})#λ

Dove le classi utilizzate sono state definite come:

class String
class List[T]
class Functor[F[_]]

Per evitare l'indirizzamento indiretto attraverso la definizione delle classi, è necessario in qualche modo esprimere funzioni di tipo anonimo, che non sono espresse direttamente in Scala, ma è possibile utilizzare tipi strutturali senza troppe spese generali sintattiche (lo stile è dovuto a https://stackoverflow.com / users / 160378 / retronym afaik ):

In alcune ipotetiche versioni future di Scala che supportano funzioni di tipo anonimo, è possibile abbreviare quest'ultima riga dagli esempi a:

types (informally) String    [x] => x              [F[x]] => F[String]) // I repeat, this is not valid Scala, and might never be

(A titolo personale, mi dispiace di aver mai parlato di "tipi di tipo superiore", dopo tutto sono solo tipi! Quando hai assolutamente bisogno di chiarire le idee, ti suggerisco di dire cose come "parametro parametro tipo", "membro tipo costruttore" , o "alias di tipo costruttore", per sottolineare che non stai parlando solo di tipi corretti.)

ps: A complicare ulteriormente le cose, "polimorfico" è ambiguo in un modo diverso, dal momento che un tipo polimorfico a volte significa un tipo quantificato universalmente, come ad esempio Forall T, T => Tun tipo appropriato, poiché classifica i valori polimorfici (in Scala, questo valore può essere scritto come tipo strutturale {def apply[T](x: T): T = x})



5
L'articolo di "Type Polymorphism Polymorphism" di Adriaan è ora disponibile su adriaanm.github.com/research/2010/10/06/…
Steven Shaw,

Continuo a leggerlo come un parente superiore e ad immaginare uno spirito affine
Janac Meena,

110

(Questa risposta è un tentativo di decorare la risposta dei Mori di Adriaan con alcune informazioni grafiche e storiche.)

I tipi di tipo superiore fanno parte di Scala dal 2.5.

  • Prima di allora Scala, come finora Java, non permetteva di usare il costruttore di tipo ("generici" in Java) da usare come parametro di tipo per un costruttore di tipo. per esempio

     trait Monad [M[_]]

    non era possibile.

    In Scala 2.5 il sistema dei tipi era stato esteso dalla capacità di classificare i tipi ad un livello superiore (noto come polimorfismo dei costruttori di tipi ). Queste classificazioni sono conosciute come tipi.

    Tipo e tipo realtion, ** derivato ** da "Generics of a Higher Kind" (Immagine derivata da Generics of a Higher Kind )

    La conseguenza è che quel tipo di costruttore (ad es. List) Potrebbe essere usato proprio come altri tipi nella posizione dei parametri di tipo dei costruttori di tipo e così sono diventati tipi di prima classe da Scala 2.5. (Simile alle funzioni che sono valori di prima classe in Scala).

    Nel contesto di un sistema di tipi che supporta tipi superiori, possiamo distinguere tipi , tipi come Into List[Int]dai tipi di primo ordine come Liste tipi di tipo superiore come Functoro Monad(tipi che si astraggono su tipi che si astraggono su tipi).

    Il sistema di tipi di Java dall'altra parte non supporta i tipi e quindi non ha tipi di "tipo superiore".

    Quindi questo deve essere visto sullo sfondo del sistema di tipi di supporto.

  • Nel caso di Scala vedi spesso esempi di un tipo di costruttore come

     trait Iterable[A, Container[_]]

    con il titolo "Tipi di tipo superiore", ad esempio in Scala per programmatori generici, sezione 4.3

    Questo a volte è missleading, perché molti si riferiscono a Containercome tipo superiore kinded e non Iterable, ma ciò che è più precisa,

    l'uso di Containercome parametro costruttore tipo di un tipo di tipo superiore (ordine superiore) qui Iterable.


80

Il tipo di tipi ordinari come Inte Char, le cui istanze sono valori, è *. Il tipo di costruttori di tipo unario come Maybeè * -> *; costruttori di tipo binario come Eitherhave ( curry ) * -> * -> *e così via. Puoi visualizzare tipi come Maybee Eithercome funzioni a livello di tipo: prendono uno o più tipi e restituiscono un tipo.

Una funzione è di ordine superiore se ha un ordine maggiore di 1, dove l'ordine è (informalmente) la profondità di annidamento, a sinistra, delle frecce delle funzioni:

  • Ordine 0: 1 :: Int
  • Ordine 1: chr :: Int -> Char
  • Ordine 2: fix :: (a -> a) -> a,map :: (a -> b) -> [a] -> [b]
  • Ordine 3: ((A -> B) -> C) -> D
  • Ordine 4: (((A -> B) -> C) -> D) -> E

Quindi, per farla breve, un tipo di tipo superiore è solo una funzione di livello superiore di livello superiore.

  • Ordine 0: Int :: *
  • Ordine 1: Maybe :: * -> *
  • Ordine 2: Functor :: (* -> *) -> Constraint—higher-kinded: converte i costruttori di tipo unario in vincoli di typeclass

Ok ho capito, quale sarebbe quindi un esempio in Scala per (* ⇒ *) ⇒ * e (* ⇒ *) ⇒ (* ⇒ *)? Il Functor di Landei rientrerebbe nella prima categoria o meglio nella seconda?
Lutz,

1
@lutz: sarebbe nella prima categoria: Functorproduce un tipo corretto (beh, tratto, ma stessa idea) Functor[F[_]]da un costruttore di tipi F.
Jon Purdy,

1
@Jon: un post molto penetrante, grazie. Il convertitore di tipi può (* => *) => (* => *)essere espresso in Scala? In caso contrario, in un'altra lingua?
Eugen Labun,

@JonPurdy Il confronto tra * ⇒ * ⇒ *con il curry è molto utile. Grazie!
Lifu Huang,

(* ⇒ *) ⇒ (* ⇒ *)può anche essere scritto (* ⇒ *) ⇒ * ⇒ *. Può essere espresso in Scala come Foo[F[_], T]. Questo è il tipo di un tipo come (in Haskell) newtype Twice f a = Twice (f (f a))(ad esempio, Twice Maybe IntMaybe (Maybe Int), Twice [] Char[[Char]]) o qualcosa di più interessante come la monade libera data Free f a = Pure a | Free (f (Free f a)).
Jon Purdy,

37

Direi: un tipo di tipo superiore si sottrae a un costruttore di tipi. Ad esempio, considera

trait Functor [F[_]] {
   def map[A,B] (fn: A=>B)(fa: F[A]): F[B]
}

Ecco Functorun "tipo di tipo superiore" (come usato nel documento "Generics of a Higher Kind" ). Non è un costruttore di tipo concreto ("di primo ordine") come List(che si sottrae solo a tipi propri). Si estrae da tutti i costruttori unari (di "primo ordine") (come indicato con F[_]).

O per dirla in un altro modo: in Java, abbiamo chiaramente i costruttori di tipi (ad esempio List<T>), ma non abbiamo "tipi di tipo superiore", perché non possiamo astrarre su di essi (ad esempio non possiamo scrivere l' Functorinterfaccia sopra definita - almeno non direttamente ).

Il termine "polimorfismo di ordine superiore (costruttore di tipo)" viene utilizzato per descrivere i sistemi che supportano "tipi di tipo superiore".


Questo è quello che pensavo, ma sembra contraddire la risposta di Jon, in cui un "costruttore di tipo concreto" è già un "tipo di tipo superiore".
Lutz,

3
Sì. Secondo la risposta di Jon (a quanto ho capito) List<T>in Java sarebbe un costruttore di tipo unario in quanto ha chiaramente il tipo * -> *. Quello che manca nella risposta di Jon è che devi essere in grado di astrarre sull'intera "cosa (e non solo sulla seconda *come in Java) per chiamarla un tipo più gentile.
Landei,

@Landai: il documento Scala per programmatori generici nella sezione 4.3 suggerisce che il tratto Iterable [A, Container [_]] sia di tipo di tipo superiore (anche se non è chiaro se si intende Iterator o Container) dove dall'altro lato vero690 in la sezione 2.3.1 usa il termine costruttore di tipo di tipo superiore per qualcosa del tipo (* -> *) -> * (operatore di tipo parametrizzato con costruttore di tipo di ordine superiore) che assomiglia all'Iteratore o al tratto di Functor.
Lutz,

1
Probabilmente è giusto, ma penso che stiamo iniziando a spaccare i capelli qui. Il punto importante per quanto riguarda i tipi di tipo superiore è che non sono coinvolti solo costruttori di tipo (ordina un polimorfismo di un tipo di costruttore), ma che siamo in grado di astrarre sul tipo concreto di quei costruttori di tipo (polimorfismo di tipo di ordine superiore). Abbiamo la capacità di astrarre su tutto ciò che vogliamo senza restrizioni (per quanto riguarda tipi e costruttori di tipi), il che rende meno interessante nominare tutte le possibili versioni di quella funzione. E mi fa male al cervello.
Landei,

2
In generale è importante distinguere definizioni e riferimenti qui. La definizione def succ(x: Int) = x+1introduce il "costruttore di valori" (vedi la mia altra risposta per cosa intendo con questo) succ(nessuno si riferirebbe a questo valore come succ (x: Int)). Per analogia, Functorè il tipo (effettivamente di tipo più elevato) definito nella tua risposta. Ancora una volta, non dovresti riferirti ad esso come Functor[F[_]](cosa è F? Cosa è _? Non hanno portata! Sfortunatamente, lo zucchero sintattico per gli esistenziali confonde le acque qui F[_]abbreviando F[T forSome {type T}])
Adriaan Moors

1

Scala REPL fornisce il :kindcomando quale

scala> :help kind

:kind [-v] <type>
Displays the kind of a given type.

Per esempio,

scala> trait Foo[A]
trait Foo

scala> trait Bar[F[_]]
trait Bar

scala> :kind -v Foo
Foo's kind is F[A]
* -> *
This is a type constructor: a 1st-order-kinded type.

scala> :kind -v Foo[Int]
Foo[Int]'s kind is A
*
This is a proper type.

scala> :kind -v Bar
Bar's kind is X[F[A]]
(* -> *) -> *
This is a type constructor that takes type constructor(s): a higher-kinded type.

scala> :kind -v Bar[Foo]
Bar[Foo]'s kind is A
*
This is a proper type.

Il :helpfornisce definizioni chiare quindi penso che vale la pena di distacco qui nella sua interezza (Scala 2.13.2)

scala> :help kind

:kind [-v] <type>
Displays the kind of a given type.

    -v      Displays verbose info.

"Kind" is a word used to classify types and type constructors
according to their level of abstractness.

Concrete, fully specified types such as `Int` and `Option[Int]`
are called "proper types" and denoted as `A` using Scala
notation, or with the `*` symbol.

    scala> :kind Option[Int]
    Option[Int]'s kind is A

In the above, `Option` is an example of a first-order type
constructor, which is denoted as `F[A]` using Scala notation, or
* -> * using the star notation. `:kind` also includes variance
information in its output, so if we ask for the kind of `Option`,
we actually see `F[+A]`:

    scala> :k -v Option
    Option's kind is F[+A]
    * -(+)-> *
    This is a type constructor: a 1st-order-kinded type.

When you have more complicated types, `:kind` can be used to find
out what you need to pass in.

    scala> trait ~>[-F1[_], +F2[_]] {}
    scala> :kind ~>
    ~>'s kind is X[-F1[A1],+F2[A2]]

This shows that `~>` accepts something of `F[A]` kind, such as
`List` or `Vector`. It's an example of a type constructor that
abstracts over type constructors, also known as a higher-order
type constructor or a higher-kinded type.
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.