Ci sono linguaggi di tipo anatra statici?
-
08-07-2019 - |
Domanda
Posso specificare interfacce quando dichiaro un membro ?
Dopo aver pensato a questa domanda per un po ', mi è venuto in mente che un linguaggio tipicamente anatre potrebbe effettivamente funzionare. Perché le classi predefinite non possono essere associate a un'interfaccia al momento della compilazione? Esempio:
public interface IMyInterface
{
public void MyMethod();
}
public class MyClass //Does not explicitly implement IMyInterface
{
public void MyMethod() //But contains a compatible method definition
{
Console.WriteLine("Hello, world!");
}
}
...
public void CallMyMethod(IMyInterface m)
{
m.MyMethod();
}
...
MyClass obj = new MyClass();
CallMyMethod(obj); // Automatically recognize that MyClass "fits"
// MyInterface, and force a type-cast.
Conosci qualche lingua che supporti tale funzione? Sarebbe utile in Java o C #? È fondamentalmente imperfetto in qualche modo? Capisco che potresti sottoclassare MyClass e implementare l'interfaccia o utilizzare il modello di progettazione dell'adapter per ottenere lo stesso risultato, ma quegli approcci sembrano solo un inutile codice boilerplate.
Soluzione
Le lingue tipicamente statiche, per definizione, controllano i tipi in tempo di compilazione , non tempo di esecuzione . Uno degli ovvi problemi con il sistema sopra descritto è che il compilatore controllerà i tipi quando viene compilato il programma, non in fase di esecuzione.
Ora, potresti creare più intelligenza nel compilatore in modo che possa derivare tipi, piuttosto che avere il programmatore esplicitamente dichiarare tipi; il compilatore potrebbe essere in grado di vedere che MyClass
implementa un metodo MyMethod ()
e gestire questo caso di conseguenza, senza la necessità di dichiarare esplicitamente le interfacce (come suggerisci). Tale compilatore potrebbe utilizzare l'inferenza di tipo, come Hindley-Milner .
Naturalmente, alcune lingue tipicamente statiche come Haskell fanno già qualcosa simile a quello che suggerisci; il compilatore Haskell è in grado di inferire i tipi (il più delle volte) senza la necessità di dichiararli esplicitamente. Ma ovviamente Java / C # non ha questa capacità.
Altri suggerimenti
Una risposta completamente nuova a questa domanda, Go ha esattamente questa funzione . Penso che sia davvero bello & amp; intelligente (anche se sarò interessato a vedere come si svolge nella vita reale) e complimenti a pensarci.
Come documentato nella documentazione ufficiale (come parte del Tour of Go, con codice di esempio) :
Le interfacce sono implementate implicitamente
Un tipo implementa un'interfaccia implementando i suoi metodi. C'è nessuna dichiarazione esplicita di intenti, nessun "attrezzo" parola chiave.
Le interfacce implicite disaccoppiano la definizione di un'interfaccia dalla sua implementazione, che potrebbe quindi apparire in qualsiasi pacchetto senza predisposizione.
Che ne dici di usare i template in C ++?
class IMyInterface // Inheritance from this is optional
{
public:
virtual void MyMethod() = 0;
}
class MyClass // Does not explicitly implement IMyInterface
{
public:
void MyMethod() // But contains a compatible method definition
{
std::cout << "Hello, world!" "\n";
}
}
template<typename MyInterface>
void CallMyMethod(MyInterface& m)
{
m.MyMethod(); // instantiation succeeds iff MyInterface has MyMethod
}
MyClass obj;
CallMyMethod(obj); // Automatically generate code with MyClass as
// MyInterface
In realtà non ho compilato questo codice, ma credo che sia praticabile e una banale C ++ - izzazione del codice originale proposto (ma non funzionante).
Non vedo il punto. Perché non essere esplicito che la classe implementa l'interfaccia e ci sia riuscita? L'implementazione dell'interfaccia è ciò che dice agli altri programmatori che questa classe dovrebbe comportarsi nel modo in cui tale interfaccia definisce. Il semplice fatto di avere lo stesso nome e la stessa firma su un metodo non fornisce alcuna garanzia che l'intento del progettista fosse quello di eseguire azioni simili con il metodo. Potrebbe essere, ma perché lasciarlo per l'interpretazione (e l'uso improprio)?
Il motivo per cui puoi " cavartelo " ciò con successo nei linguaggi dinamici ha più a che fare con TDD che con il linguaggio stesso. Secondo me, se la lingua offre la possibilità di fornire questo tipo di guida ad altri che usano / visualizzano il codice, dovresti usarlo. In realtà migliora la chiarezza e vale i pochi personaggi extra. Nel caso in cui non si abbia accesso a tale scopo, un adattatore ha lo stesso scopo di dichiarare esplicitamente come l'interfaccia si collega all'altra classe.
F # supporta la tipizzazione anatra statica, anche se con un trucco: devi usare i vincoli dei membri. I dettagli sono disponibili in questo post di blog .
Esempio dal blog citato:
let inline speak (a: ^a) =
let x = (^a : (member speak: unit -> string) (a))
printfn "It said: %s" x
let y = (^a : (member talk: unit -> string) (a))
printfn "Then it said %s" y
type duck() =
member x.speak() = "quack"
member x.talk() = "quackity quack"
type dog() =
member x.speak() = "woof"
member x.talk() = "arrrr"
let x = new duck()
let y = new dog()
speak x
speak y
La maggior parte delle lingue della famiglia ML supporta tipi strutturali con inferenza e schemi di tipi vincolati , che è la terminologia geniale del designer linguistico che sembra molto probabilmente cosa intendi con la frase "static duck" -typing " nella domanda originale.
Le lingue più popolari in questa famiglia che mi vengono in mente includono: Haskell, Objective Caml, F # e Scala. Quello che più corrisponde al tuo esempio, ovviamente, sarebbe Objective Caml. Ecco una traduzione del tuo esempio:
open Printf
class type iMyInterface = object
method myMethod: unit
end
class myClass = object
method myMethod = printf "Hello, world!"
end
let callMyMethod: #iMyInterface -> unit = fun m -> m#myMethod
let myClass = new myClass
callMyMethod myClass
Nota: alcuni dei nomi che hai usato devono essere cambiati per conformarsi alla nozione di semantica del caso identificativo di OCaml, ma per il resto, questa è una traduzione piuttosto semplice.
Inoltre, vale la pena notare che né l'annotazione di tipo nella funzione callMyMethod
né la definizione del tipo di classe iMyInterface
sono strettamente necessarie. Obiettivo Caml può dedurre tutto nel tuo esempio senza alcuna dichiarazione di tipo.
Bene, ok ... Quindi è un superset javascript e forse non costituisce un "linguaggio", ma questo tipo di digitazione anatra statica è vitale in TypeScript.
Boo è sicuramente un linguaggio di tipo anatra statico: http://boo.codehaus.org/Duck + Digitando
Un estratto:
Boo è un linguaggio tipicamente statico, come Java o C #. Questo significa che il tuo fischio le applicazioni funzioneranno più velocemente di quelli codificati in altri digitati staticamente lingue per .NET o Mono. Ma usando una lingua tipicamente statica a volte ti costringe a un inflessibile e stile di codifica dettagliato, con il dichiarazioni di tipo talvolta necessarie (come " x as int " ;, ma non lo è spesso necessario a causa del tipo di fischio Inferenza) e talvolta necessario cast di tipi (vedi Tipi di casting). Boo supporto per Type Inference e alla fine i generici aiutano qui, ma ...
A volte è opportuno arrendersi la rete di sicurezza fornita da statica digitando. Forse vuoi solo esplorare un'API senza preoccuparsi troppo firme del metodo o forse lo sei creando codice che parla con l'esterno componenti come oggetti COM. O modo in cui la scelta dovrebbe essere tua no mio.
Insieme ai tipi normali come oggetto, int, stringa ... boo ha a tipo speciale chiamato "papera". Il termine si ispira alla programmazione ruby funzione di digitazione anatra della lingua (" Se sì cammina come un'anatra e ciondola come una anatra, deve essere un'anatra ").
Le nuove versioni di C ++ si muovono nella direzione della tipizzazione anatra statica. Puoi un giorno (oggi?) Scrivere qualcosa del genere:
auto plus(auto x, auto y){
return x+y;
}
e non verrebbe compilato se non ci sono chiamate di funzione corrispondenti per x + y
.
Per quanto riguarda le tue critiche:
Un nuovo " CallMyMethod " viene creato per ogni tipo diverso che passi ad esso, quindi non è proprio l'inferenza di tipo.
Ma è un'inferenza di tipo (puoi dire foo (bar)
dove foo
è una funzione basata su modelli) e ha lo stesso effetto, tranne che è più efficiente in termini di tempo e occupa più spazio nel codice compilato.
Altrimenti, dovresti cercare il metodo durante il runtime. Dovresti trovare un nome, quindi verificare che il nome abbia un metodo con i parametri giusti.
O dovresti archiviare tutte quelle informazioni sulle interfacce corrispondenti e cercare in ogni classe che corrisponde a un'interfaccia, quindi aggiungere automaticamente quell'interfaccia.
In entrambi i casi, ciò consente di rompere implicitamente e accidentalmente la gerarchia di classi, il che è negativo per una nuova funzionalità perché va contro le abitudini di ciò a cui sono abituati i programmatori di C # / Java. Con i modelli C ++, sai già di essere in un campo minato (e stanno anche aggiungendo funzionalità ("concetti") per consentire restrizioni sui parametri del modello).
I tipi strutturali in Scala fanno qualcosa del genere.
Un design pre-release per Visual Basic 9 aveva il supporto per la tipizzazione anatra statica usando interfacce dinamiche ma hanno tagliato la funzione * per la spedizione puntuale.
D ( http://dlang.org ) è un linguaggio compilato staticamente e fornisce la dattilografia tramite wrap () e unwrap () ( http://dlang.org/phobos-prerelease/std_typecons.html # .unwrap ).
Nell'ultima versione del mio linguaggio di programmazione Heron supporta qualcosa di simile attraverso una coercizione strutturale operatore chiamato as
. Quindi invece di:
MyClass obj = new MyClass();
CallMyMethod(obj);
Scriveresti:
MyClass obj = new MyClass();
CallMyMethod(obj as IMyInterface);
Proprio come nel tuo esempio, in questo caso MyClass
non deve implementare esplicitamente IMyInterface
, ma se lo facesse il cast potrebbe avvenire implicitamente e il come
operatore potrebbe essere omesso.
Ho scritto qualcosa in più sulla tecnica che chiamo esplicito sotto-tipo strutturale in questo articolo .