Come può una lingua tipizzata in modo statico supportare la digitazione anatra?

6

Capisco quali sono i tipi di sistemi dinamici e statici e quale è la digitazione anatra. Ma non capisco come si possa avere un linguaggio statico che supporti la digitazione anatra. A mio avviso, solo una lingua digitata in modo dinamico può supportare la digitazione anatra.

Questa risposta di StackOverflow spiega che "la tipizzazione di anatra è qualcosa che è completamente ortogonale alla tipizzazione statica, dinamica, debole o strong." Fornisce un esempio da C ++ per la digitazione di anatre in un linguaggio tipizzato staticamente, ma non sono un programmatore C ++ e non lo capisco.

Vorrei una spiegazione di come sia possibile che una lingua statica supporti la digitazione anatra.

    
posta Aviv Cohn 11.08.2014 - 22:20
fonte

7 risposte

11

Nella mia esperienza, è semplicemente un linguaggio che utilizza la tipizzazione statica con un Sistema di tipo strutturale . In pratica si applica "controlla come un'anatra, parla come un'anatra" e controlla il tipo di compilazione in modo che il programmatore non debba fornire annotazioni per specificare in modo specifico i sottotipi.

Questo ha un grande vantaggio quando stai cercando di incollare due (o più) librerie insieme. Con un sistema di tipo nominativo, se hai un'interfaccia in una libreria e un oggetto nell'altra, non si conoscono l'un l'altro e non possono sottotitoli - anche se l'oggetto soddisfa i requisiti dell'interfaccia . La digitazione strutturale lo rende ok.

Rendere statico il linguaggio significa solo che il compilatore esegue tale controllo in fase di compilazione, dandoti un errore precoce (e chiaramente) quando quell'oggetto in realtà non soddisfa l'interfaccia.

Esistono alcuni linguaggi esoterici che eseguono questa operazione, ma la maggior parte delle lingue strutturalmente digitate sono anche digitate dinamicamente e quasi tutte le lingue nominative nominate sono anche tipizzate staticamente.

    
risposta data 11.08.2014 - 22:29
fonte
5

Il significato abituale di un termine simile è solo la tipizzazione strutturale.

Nei sistemi strutturalmente tipizzati, le cose hanno un tipo statico. Tuttavia, quel tipo si basa sulla struttura effettiva del tipo piuttosto che su un nome di tipo particolare.

Ad esempio, supponiamo di avere il codice Python

def foo(bar):
  bar.baz()
  bar.quux(5)

Ora non è chiaro quale sia il tipo di bar , ma qualunque cosa sia sappiamo

  • Ha un metodo baz che non accetta argomenti
  • Ha un metodo quux che richiede 1 argomento intero

Ora in un sistema di tipo strutturale potremmo assegnare foo un tipo

foo : forall a b. r{baz : () -> a, quux : (int) -> b} -> Void

dove quella cosa divertente r significa

Any type r which the methods ...

Molte lingue implementano alcuni sottoinsiemi di funzionalità strutturalmente tipizzate, il C ++ implementa per esempio "tipizzazione strutturale" tramite modelli. Tuttavia questo è un approccio leggermente ad hoc.

Altre lingue implementano i tipi di riga. Questi sono solo record / structs strutturalmente digitati! Tipi in cui possiamo dire qualcosa come "vogliamo un record con almeno i campi ...". Credo che purescript li implementa.

Go ha qualcosa come i tipi strutturali con le sue "interfacce implicite". Queste sono solo interfacce che un tipo implementa automaticamente. Tuttavia non si tratta di tipi strutturali completi poiché non consente la gestione parametrica di un tipo strutturale, ovvero non c'è modo di dire qualcosa di simile

foo :: r{a : int} -> w{a : int} -> r
foo r w = w -- Type error!!

Poiché tutto viene "aggiornato" a un'interfaccia, anziché essere semplicemente trattato in modo opaco.

Si è parlato dell'aggiunta di questi alla vista Haskell -XOverloadedRecordFields , ma non sono a conoscenza di alcun progresso reale sulla tipizzazione strutturale nella sua generalità completa.

    
risposta data 11.08.2014 - 22:33
fonte
4

Tipicamente staticamente significa che i tipi sono controllati al momento della compilazione. È altrettanto facile per un compilatore verificare che un tipo abbia un metodo con un certo nome e firma, così come lo è per verificare che un tipo sia una parte di una gerarchia di eredità specifica. Il trucco sta nel trovare un modo conciso per specificare "questo argomento per questa funzione è qualsiasi tipo che ha un metodo con questo nome specifico."

Il metodo con cui ho più familiarità per farlo è usare una classe di tipo , che è una dichiarazione che fondamentalmente dice, "qualsiasi tipo che implementa tutte queste funzioni può essere riferito a usando questo nome."

Di solito devi dichiarare specificatamente un tipo come un'istanza di una classe di tipo, ma non deve essere nello stesso codice che è definito dal tipo stesso, il che significa che chiunque può aggiungere la propria classe di tipo dopo il fatto a tipi che non controllano. È una sorta di compromesso per la tipizzazione totale delle anatre, più simile, "se cammina come un'anatra e fa la cacca come un'anatra, allora chiunque può dichiararlo esplicitamente come una cosa simile all'anatra, e chiunque altro può trattarlo come un'anatra".

Tuttavia, non è così grande per consentire ai tipi di essere implicitamente aggiunti a una classe di tipi, ma perderti un po 'di controllo. Come Amon e Jozefg ha sottolineato che il linguaggio go ha interfacce che fondamentalmente agiscono come classi di tipi aggiunti implicitamente.

    
risposta data 11.08.2014 - 23:36
fonte
1

Sai Typescript ?

Può sembrare un esempio semplicistico, ma qui va: è un linguaggio che compila in Javascript. Ora, Javascript stesso è dinamico e digitato, ma dimentichiamo JS per un secondo.

Typescript supporta la digitazione statica, ovvero controlla se i tipi sono corretti durante la compilazione e sputa gli avvertimenti se pensa che potresti aver commesso un errore. (Puoi seguire il tutorial e ti mostrerà un esempio di questo)

Tuttavia, anche se supporta la digitazione statica, ha anche l'intera cosa di battitura a macchina di JS. Puoi utilizzare var tutto intorno al luogo, omettendo i tipi che Typescript aggiunge e il codice verrà compilato ed eseguito.

Quindi, è staticamente digitato, ma puoi anche fare il ciarlatano. Controllerà anche il ciarlatano per te se lo lasci fare.

La tipizzazione di anatra è per la praticità della digitazione e in alcune lingue per l'uso di generici (come C ++). La tipizzazione statica serve a controllare i tipi che hai specificato durante la compilazione, se stai lavorando su una lingua compilata.

    
risposta data 11.08.2014 - 23:36
fonte
0

Non deve essere. Puoi implementare il 95% di ish della digitazione in una lingua statica senza tanto lavoro. L'ultimo 5% è necessario per la digitazione dipendente ed è molto più complicato, ma è ancora un sistema tipizzato staticamente. Nello specifico, il restante 5% utilizza metodi che dipendono da altri valori, e quindi il codice che funziona ancora su dati che non supportano tutti i metodi che il codice chiama in modo condizionale.

Ecco un frammento di Haskell con cui stavo armeggiando e implementa qualcosa di molto come tipizzazione anatra statica:

{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, 
UndecidableInstances, IncoherentInstances, FlexibleContexts #-}

data Addon a b = Addon a b deriving Show

class Plugin a b where
    first :: b -> a
    set   :: a -> b -> b

instance Plugin a a where
    first = id
    set   = const

instance Plugin a (Addon a b) where
    first (Addon a _) = a
    set x (Addon a b) = Addon x b

instance (Plugin a b) => Plugin a (Addon c b)where
    first (Addon _ b) = first b
    set x (Addon a b) = Addon a $ set x b

class Runnable e b c where
    run :: e -> b -> c

instance Plugin a b => Runnable (a -> c) b c where
    run f p = f (first p)

instance (Plugin d b, Runnable e b c) => Runnable (d -> e) b c where
    run f p = run (f $ first p) p

-- The constructor function should NOT be polymorphic *at all*
apply constructor f x = set (constructor $ run f x) x

La classe di tipi Plugin esiste per esprimere il contenimento di tipi all'interno di altri tipi. Un tipo che è un'istanza di Plugin a b significa che è un tipo b per cui first b può essere un valore di tipo a . Le due istanze fornite sono tutto ciò che è necessario per derivare tutti i casi rilevanti e non sono necessarie ulteriori dichiarazioni di istanza.

La classe runnable Runnable ci consente di concatenare le istanze del Plugin e quindi di chiamare una funzione multi-argomento aleatoria su un singolo tipo di dati, purché contenga un tipo di componente valido per ogni argomento curried della funzione. Ancora una volta, non sono necessarie ulteriori dichiarazioni di istanza per l'utilizzo.

La funzione apply ci consente di prendere un tipo composito, di applicarci una funzione e di memorizzare il risultato all'interno di quel tipo composito (purché contenga già un valore del tipo che desideriamo memorizzare)

L'utilizzo sarebbe così:

data Increment = Inc Integer
data Counter   = Count Integer

counter1 = union (Inc 2) (Count 3)
counter2 = union (Inc 1) (Count 0)

increment (Inc i) (Count c) = i + c

E in effetti, compila e possiamo eseguirlo e osservare l'output:

*Main> apply Count increment counter1
Addon (Inc 2) (Count 5)
*Main> apply Count increment counter2
Addon (Inc 1) (Count 1)

Certo, abbiamo dovuto attivare alcune estensioni di tipo piuttosto brutte, ma a quelle estensioni non è richiesto di usare la libreria, solo per scriverla. L'argomento del costruttore da applicare non può essere polimorfo perché sovraccaricherebbe il sistema di inferenza del tipo e non sarebbe in grado di effettuare le derivazioni del tipo intermedio necessarie. Stavo cercando di aggirare il problema, ma non ho fatto alcun progresso in quella direzione.

    
risposta data 31.03.2015 - 03:21
fonte
-1

"Fornisce un esempio di C ++ per la digitazione anatra in un linguaggio tipizzato staticamente, ma non sono un programmatore C ++ e non lo capisco."

C ++ supporta la compilazione time duck typing aka templates. I modelli sono fondamentalmente generici super-alimentati. Ad esempio: -

template <class NUM> NUM add(NUM one , NUM two)
{
  return one + two;
}

Questa funzione accetta due parametri del tipo di modello NUM (che ho appena chiamato, non vi è alcun significato per poter usare qualsiasi cosa come nome del tipo). Al momento della compilazione il compilatore controlla per vedere come viene utilizzata la funzione.

es .: -

 add(2.0,2.0)

 add(1,2.0)

Se il compilatore rileva che non vi è alcun errore con i tipi usati (double, int), allora creerà una funzione per quel tipo che sostituisce il tipo di template.

ad esempio: -

double add(double one, double two)
int add(int one, int two)

Si chiama digitazione anatra in fase di compilazione. Per questo motivo, non devi preoccuparti delle volte se sono compatibili.

    
risposta data 27.11.2016 - 03:58
fonte
-2

Se il codice mostrato di seguito viene compilato in una lingua che somiglia in qualche modo a C, allora questo linguaggio fittizio mostrerebbe presumibilmente una digitazione "statica" (secondo Wikipedia, almeno):

typedef struct s1{
 int a;
 int b;
};

typedef struct s2{
 int a;
 int b;
};

s1 thing1;
s1.a=1;
s1.b=2;

s2 thing2;
thing2=thing1; //This is where a "real" C compiler would complain
    
risposta data 11.08.2014 - 22:33
fonte

Leggi altre domande sui tag