Question

Puis-je spécifier des interfaces lorsque je déclare un membre ?

Après avoir réfléchi à cette question pendant un moment, il m’a pensé qu’un langage à caractère statique pourrait s’avérer utile. Pourquoi les classes prédéfinies ne peuvent-elles pas être liées à une interface lors de la compilation? Exemple:

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.

Connaissez-vous des langues prenant en charge une telle fonctionnalité? Serait-ce utile en Java ou en C #? Est-ce fondamentalement défectueux en quelque sorte? Je comprends que vous puissiez sous-classer MyClass et implémenter l’interface ou utiliser le modèle de conception de l’adaptateur pour obtenir le même résultat, mais ces approches semblent tout simplement comme un code passe-partout inutile.

Était-ce utile?

La solution

Les langages à typage statique, par définition, vérifient les types à la compilation , et non à le temps d'exécution . L’un des problèmes évidents avec le système décrit ci-dessus est que le compilateur va vérifier les types lors de la compilation du programme, pas au moment de l’exécution.

Maintenant, vous pouvez créer plus d'intelligence dans le compilateur pour qu'il puisse dériver des types, plutôt que de laisser le programmeur explicitement déclarer ; le compilateur pourra peut-être voir que MyClass implémente une méthode MyMethod () , et gérer ce cas en conséquence, sans le besoin de déclarer explicitement les interfaces (comme vous le suggérez). Un tel compilateur pourrait utiliser une inférence de type, telle que Hindley-Milner .

Bien sûr, certains langages statiques comme Haskell font déjà quelque chose similaire à ce que vous suggérez; le compilateur Haskell est capable d'inférer des types (la plupart du temps) sans avoir à les déclarer explicitement. Mais évidemment, Java / C # n’a pas cette capacité.

Autres conseils

Une toute nouvelle réponse à cette question, Go a exactement cette fonctionnalité . Je pense que c'est vraiment cool & amp; intelligent (même si je serais intéressé de voir comment cela se passe dans la vie réelle) et bravo pour y avoir pensé.

Comme décrit dans la documentation officielle (dans le cadre du Tour of Go, avec exemple de code) :

  

Les interfaces sont implémentées implicitement

     

Un type implémente une interface en implémentant ses méthodes. Il y a   pas de déclaration d’intention explicite, pas d ’" implémentation " mot clé.

     

Les interfaces implicites découplent la définition d’une interface de sa   mise en œuvre, qui pourrait alors apparaître dans n’importe quel paquet   arrangement préalable.

Pourquoi utiliser des modèles en 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

Je n’ai pas réellement compilé ce code, mais j’estime qu’il est réalisable et qu’il constitue une assez simple C ++ - isation du code original (mais non fonctionnel) proposé.

Je ne vois pas le but. Pourquoi ne pas être explicite sur le fait que la classe implémente l'interface et en a fini avec? L'implémentation de l'interface est ce qui indique aux autres programmeurs que cette classe est supposée se comporter de la manière définie par l'interface. Le fait d'avoir le même nom et la même signature sur une méthode ne signifie en aucun cas que l'intention du concepteur était d'exécuter des actions similaires avec la méthode. C'est peut-être le cas, mais pourquoi laisser le soin de l'interprétation (et du mauvais usage)?

La raison pour laquelle vous pouvez "vous évader". avec cela, les langages dynamiques ont plus à faire avec TDD qu'avec le langage lui-même. À mon avis, si le langage offre la possibilité de donner ce type de conseils à ceux qui utilisent / visualisent le code, vous devez l’utiliser. Cela améliore réellement la clarté et vaut les quelques caractères supplémentaires. Si vous n’avez pas accès à cette option, un adaptateur a le même objectif: déclarer explicitement le lien entre l’interface et l’autre classe.

F # prend en charge le dactylographie statique, mais avec un accroc: vous devez utiliser les contraintes de membre. Les détails sont disponibles dans ce entrée de blog .

Exemple tiré du blog cité:

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 plupart des langues de la famille ML prennent en charge les types structurels avec inférence et schémas de types sous contrainte , qui est la terminologie geek du concepteur de langage qui semble le plus probable à ce que vous entendez par l'expression "canard statique". -type " dans la question initiale.

Les langues les plus populaires de cette famille qui viennent à l’esprit sont: Haskell, Objective Caml, F # et Scala. Objective Caml est celui qui correspond le mieux à votre exemple. Voici une traduction de votre exemple:

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

Remarque: certains des noms que vous avez utilisés doivent être modifiés pour être conformes à la notion de sémantique de casse d'identification utilisée par OCaml. Sinon, il s'agit d'une traduction assez simple.

A noter également que ni l'annotation de type dans la fonction callMyMethod ni la définition du type de classe iMyInterface ne sont strictement nécessaires. Objective Caml peut tout inférer dans votre exemple sans aucune déclaration de type.

TypeScript!

Eh bien, d'accord ... C'est donc un sur-ensemble javascript qui ne constitue peut-être pas un "langage", mais ce type de saisie statique est essentiel dans TypeScript.

entrer la description de l'image ici

Boo est définitivement un langage statique typé "canard": http://boo.codehaus.org/Duck + Dactylographie

Un extrait:

  

Boo est un langage statiquement typé,   comme Java ou C #. Cela signifie votre boo   les applications fonctionneront à peu près aussi vite que   ceux codés dans d'autres statiquement typés   langues pour .NET ou Mono. Mais en utilisant   un langage typé statiquement parfois   vous contraint à un inflexible et   style de codage détaillé, avec le   déclarations de type parfois nécessaires   (comme "x as int", mais ce n'est pas   souvent nécessaire en raison du type de boo   Inférence) et parfois nécessaire   type jette (voir Types de casting). Boo's   support pour Type Inference et   finalement, les génériques aident ici, mais ...

     

Parfois, il convient d'abandonner   le filet de sécurité fourni par statique   dactylographie. Peut-être que vous voulez juste explorer   une API sans trop se soucier de   signatures de méthode ou peut-être que vous êtes   créer du code qui parle à l'externe   composants tels que les objets COM. Non plus   Ainsi le choix devrait être à vous pas   le mien.

     

Avec les types normaux comme   objet, int, string ... boo a un   type spécial appelé "canard". Le terme   s'inspire de la programmation ruby   la fonction de saisie de la langue de la langue (" Si elle   marche comme un canard et charlatans comme un   canard, ce doit être un canard ").

Les nouvelles versions de C ++ vont dans le sens de la saisie statique. Vous pouvez un jour (aujourd'hui?) Écrire quelque chose comme ceci:

auto plus(auto x, auto y){
    return x+y;
}

et la compilation échouera s'il n'y a pas d'appel de fonction correspondant pour x + y .

Quant à votre critique:

  

Un nouveau " CallMyMethod " est créé pour chaque type différent que vous lui transmettez, donc ce n'est pas vraiment l'inférence de type.

Mais C’EST une inférence de type (vous pouvez dire foo (barre) foo est une fonction basée sur des modèles), et a le même effet, sauf que cela permet de gagner du temps. et prend plus de place dans le code compilé.

Sinon, vous devrez rechercher la méthode pendant l'exécution. Vous devez trouver un nom, puis vérifier que le nom a une méthode avec les bons paramètres.

Sinon, vous devriez stocker toutes ces informations sur les interfaces correspondantes et examiner chaque classe correspondant à une interface, puis ajouter automatiquement cette interface.

Dans les deux cas, cela vous permet de casser implicitement et accidentellement la hiérarchie des classes, ce qui est mauvais pour une nouvelle fonctionnalité car elle va à l’encontre des habitudes de celles auxquelles les programmeurs de C # / Java sont habitués. Avec les modèles C ++, vous savez déjà que vous êtes dans un champ de mines (ils ajoutent également des fonctionnalités ("concepts") permettant d'autoriser des restrictions sur les paramètres de modèle).

Les types de structure dans Scala font quelque chose comme ceci.

Voir Vérification statique & # 8220; Duck Typing & # 8221; en Scala

Une conception préliminaire de Visual Basic 9 prenait en charge le typage statique des canards à l'aide d'interfaces dynamiques, mais coupait la fonctionnalité * pour une livraison à temps.

D ( http://dlang.org ) est un langage compilé de manière statique et fournit une saisie de code via wrap () et unwrap () ( http://dlang.org/phobos-prerelease/std_typecons.html # .unwrap ).

Crystal est un langage statiquement typé "canard". Exemple :

def add(x, y)
  x + y
end

add(true, false)

L'appel à add provoque cette erreur de compilation:

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

add(true, false)
^~~

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

  x + y
    ^

Dans la dernière version de mon langage de programmation, Heron , il prend en charge quelque chose de similaire grâce à une contrainte de sous-typage structurel. opérateur appelé comme . Donc au lieu de:

MyClass obj = new MyClass();
CallMyMethod(obj);

Vous écririez:

MyClass obj = new MyClass();
CallMyMethod(obj as IMyInterface);

Comme dans votre exemple, dans ce cas, MyClass ne doit pas implémenter explicitement IMyInterface , mais si cela se produisait, la conversion pourrait se produire implicitement et le comme L’opérateur peut être omis.

J'ai écrit un peu plus sur la technique que j'appelle sous-typage structurel explicite dans cet article .

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top