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.

È stato utile?

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.

TypeScript!

Bene, ok ... Quindi è un superset javascript e forse non costituisce un "linguaggio", ma questo tipo di digitazione anatra statica è vitale in TypeScript.

inserisci qui la descrizione dell'immagine

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.

Vedi " Duck Typing "controllato staticamente in Scala

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 ).

Crystal è un linguaggio tipicamente anatra. Esempio :

def add(x, y)
  x + y
end

add(true, false)

La chiamata a aggiungi provoca questo errore di compilazione:

Error in foo.cr:6: instantiating 'add(Bool, Bool)'

add(true, false)
^~~

in foo.cr:2: undefined method '+' for Bool

  x + y
    ^

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 .

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top