Abaissement automatique en déduisant le type

11

En java, vous devez explicitement transtyper afin de downcaster une variable

public class Fruit{}  // parent class
public class Apple extends Fruit{}  // child class

public static void main(String args[]) {
    // An implicit upcast
    Fruit parent = new Apple();
    // An explicit downcast to Apple
    Apple child = (Apple)parent;
}

Y a-t-il une raison à cette exigence, à part le fait que java ne fait aucune inférence de type?

Y a-t-il des "accrochages" avec l'implémentation de la conversion automatique vers le bas dans une nouvelle langue?

Par exemple:

Apple child = parent; // no cast required 
Sam Washburn
la source
Vous dites que, si le compilateur peut déduire que l'objet en cours de descente est toujours du type correct, vous ne devriez pas pouvoir exprimer explicitement la descente? Mais en fonction de la version du compilateur et de la quantité d'inférence de type qu'il peut faire, certains programmes peuvent ne pas être valides ... des bogues dans l'inférenceur de type pourraient empêcher l'écriture de programmes valides, etc ... cela n'a pas de sens d'introduire des cas qui dépend de nombreux facteurs, il ne serait pas intuitif pour les utilisateurs s'ils devaient continuer à ajouter ou supprimer des castes sans raison à chaque version.
Bakuriu

Réponses:

24

Les retransmissions réussissent toujours.

Les downcasts peuvent entraîner une erreur d'exécution, lorsque le type d'exécution d'objet n'est pas un sous-type du type utilisé dans le transtypage.

Puisque le second est une opération dangereuse, la plupart des langages de programmation typés nécessitent que le programmeur le demande explicitement. Essentiellement, le programmeur dit au compilateur "croyez-moi, je sais mieux - ce sera OK au moment de l'exécution".

En ce qui concerne les systèmes de types, les upcasts imposent la charge de la preuve au compilateur (qui doit le vérifier statiquement), les downcasts imposent la charge de la preuve au programmeur (qui doit y réfléchir sérieusement).

On pourrait faire valoir qu'un langage de programmation correctement conçu interdirait complètement les downcasts, ou fournirait des alternatives de conversion sûres, par exemple en renvoyant un type facultatif Option<T>. Cependant, de nombreuses langues répandues ont choisi l’approche la plus simple et la plus pragmatiqueT et de soulever une erreur autrement.

Dans votre exemple spécifique, le compilateur aurait pu être conçu pour déduire qu'il parents'agit en fait d'une Appleanalyse statique simple et permettre la conversion implicite. Cependant, en général, le problème est indécidable, nous ne pouvons donc pas nous attendre à ce que le compilateur effectue trop de magie.

chi
la source
1
Juste à titre de référence, un exemple de langage qui descend vers les options est Rust, mais c'est parce qu'il n'a pas de véritable héritage, seulement un "n'importe quel type" .
Kroltan
3

Habituellement, le downcasting est ce que vous faites lorsque les connaissances statiquement connues du compilateur sur le type de quelque chose sont moins spécifiques que ce que vous savez (ou du moins espérez).

Dans des situations comme votre exemple, l'objet a été créé en tant Appleque puis cette connaissance a été supprimée en stockant la référence dans une variable de type Fruit. Ensuite, vous souhaitez utiliser à nouveau la même référence qu'un Apple.

Étant donné que les informations n'ont été supprimées que "localement", bien sûr, le compilateur peut conserver les connaissances qui parentsont vraiment un Apple, même si son type déclaré estFruit .

Mais généralement, personne ne fait ça. Si vous voulez créer un Appleet l'utiliser comme un Apple, vous le stockez dans une Applevariable, pas unFruit .

Lorsque vous en avez un Fruitet que vous souhaitez l'utiliser comme un, Applecela signifie généralement que vous avez obtenu le Fruitvia un moyen qui peut généralement renvoyer tout type de Fruit, mais dans ce cas, vous savez que c'était unApple . Presque toujours, vous ne l'avez pas simplement construit, vous l'avez passé par un autre code.

Un exemple évident est si j'ai une parseFruitfonction qui peut transformer des chaînes comme "pomme", "orange", "citron", etc., en sous-classe appropriée; généralement tout ce que nous (et le compilateur) peut savoir sur cette fonction est qu'elle retourne une sorte de Fruit, mais si je l' appelle parseFruit("apple")alors je sais que ce va appeler un Appleet peut vouloir utiliserApple des méthodes, donc je pouvais baissés.

Encore une fois, un compilateur suffisamment intelligent pourrait comprendre cela en insérant le code source de parseFruit, car je l'appelle avec une constante (à moins qu'il ne soit dans un autre module et que nous ayons une compilation séparée, comme en Java). Mais vous devriez facilement pouvoir voir comment des exemples plus complexes impliquant des informations dynamiques pourraient devenir plus difficiles (voire impossibles!) À vérifier par le compilateur.

Dans le code, les downcasts se produisent généralement lorsque le compilateur ne peut pas vérifier que le downcast est sûr à l'aide de méthodes génériques, et pas dans des cas aussi simples que de suivre immédiatement un upcast en jetant le même type d'informations que nous essayons de récupérer par downcasting.

Ben
la source
3

Il s'agit de savoir où voulez-vous tracer la ligne. Vous pouvez concevoir un langage qui détectera la validité d'un abaissement implicite:

public static void main(String args[]) { 
    // An implicit upcast 
    Fruit parent = new Apple();
    // An implicit downcast to Apple 
    Apple child = parent; 
}

Maintenant, extrayons une méthode:

public static void main(String args[]) { 
    // An implicit upcast 
    Fruit parent = new Apple();
    eat(parent);
}
public static void eat(Fruit parent) { 
    // An implicit downcast to Apple 
    Apple child = parent; 
}

Nous sommes toujours bons. L'analyse statique est beaucoup plus difficile mais toujours possible.

Mais le problème surgit dès que quelqu'un ajoute:

public static void causeTrouble() { 
    // An implicit upcast 
    Fruit trouble = new Orange();
    eat(trouble);
}

Maintenant, où voulez-vous soulever l'erreur? Cela crée un dilemme, on peut dire que le problème est Apple child = parent;présent, mais cela peut être réfuté par "Mais ça a marché avant". D'un autre côté, l'ajout a eat(trouble);causé le problème ", mais tout l'intérêt du polymorphisme est de permettre exactement cela.

Dans ce cas, vous pouvez faire une partie du travail du programmeur, mais vous ne pouvez pas tout faire. Plus vous allez loin avant d'abandonner, plus il sera difficile d'expliquer ce qui a mal tourné. Il est donc préférable de s'arrêter le plus tôt possible, selon le principe du signalement précoce des erreurs.

BTW, en Java, le downcast que vous avez décrit n'est pas réellement un downcast. Il s'agit d'une fonte générale, qui peut également couler des pommes en oranges. Donc, techniquement parlant, l'idée de @ chi est déjà là, il n'y a pas de downcasts en Java, seulement des "everycasts". Il serait judicieux de concevoir un opérateur de conversion vers le bas spécialisé qui générera une erreur de compilation lorsque son type de résultat ne peut pas être trouvé en aval de son type d'argument. Ce serait une bonne idée de rendre "Everycast" beaucoup plus difficile à utiliser afin de décourager les programmeurs de l'utiliser sans très bonne raison. La XXXXX_cast<type>(argument)syntaxe de C ++ me vient à l'esprit.

Agent_L
la source