Dans une discussion sur les méthodes statiques et d'instance, je pense toujours que cela Sqrt()
devrait être une méthode d'instance de types numériques au lieu d'une méthode statique. Pourquoi donc? Cela fonctionne évidemment sur une valeur.
// looks wrong to me
var y = Math.Sqrt(x);
// looks better to me
var y = x.Sqrt();
Les types de valeur peuvent évidemment avoir des méthodes d'instance, car dans de nombreux langages, il existe une méthode d'instance ToString()
.
Pour répondre à quelques questions des commentaires: Pourquoi 1.Sqrt()
ne devrait pas être légal? 1.ToString()
est.
Certaines langues ne permettent pas d'avoir des méthodes sur les types de valeurs, mais certaines langues le peuvent. Je parle de ceux-ci, y compris Java, ECMAScript, C # et Python (avec __str__(self)
défini). La même chose s'applique à d'autres fonctions comme ceil()
, floor()
etc.
1.sqrt()
valide?Sqrt(x)
semble beaucoup plus naturel quex.Sqrt()
si cela signifie ajouter la fonction à la classe dans certaines langues, je suis d'accord avec ça. S'il s'agissait d' une méthode d'instance, ilx.GetSqrt()
serait plus approprié d'indiquer qu'elle renvoie une valeur plutôt que de modifier l'instance.Réponses:
C'est entièrement un choix de conception de langage. Cela dépend également de l'implémentation sous-jacente des types primitifs et des considérations de performances qui en découlent.
.NET n'a qu'une seule
Math.Sqrt
méthode statique qui agit sur adouble
et renvoie adouble
. Tout ce que vous lui passez doit être lancé ou promu adouble
.D'un autre côté, vous avez Rust qui expose ces opérations comme des fonctions sur les types :
Mais Rust a également une syntaxe d'appel de fonction universelle (quelqu'un l'a mentionné plus tôt), vous pouvez donc choisir ce que vous voulez.
la source
x.Sqrt()
, cela peut être fait.public static class DoubleExtensions { public static double Sqrt( this double self) { return Math.Sqrt(self); } }
Sqrt(x)
msdn.microsoft.com/en-us/library/sf0df423.aspx .Supposons que nous concevons un nouveau langage et que nous voulons
Sqrt
être une méthode d'instance. Nous examinons donc ladouble
classe et commençons à concevoir. Il n'a évidemment aucune entrée (autre que l'instance) et renvoie adouble
. Nous écrivons et testons le code. La perfection.Mais prendre la racine carrée d'un entier est également valable, et nous ne voulons pas forcer tout le monde à se convertir en double juste pour prendre une racine carrée. Nous passons donc à
int
et commençons à concevoir. Que revient-il? Nous pourrions retourner unint
et le faire fonctionner uniquement pour des carrés parfaits, ou arrondir le résultat au plus procheint
(en ignorant le débat sur la méthode d'arrondi appropriée pour l'instant). Mais que faire si quelqu'un veut un résultat non entier? Devrions-nous avoir deux méthodes - une qui retourne unint
et une qui retourne undouble
(ce qui n'est pas possible dans certaines langues sans changer le nom). Nous décidons donc qu'il devrait retourner adouble
. Maintenant, nous mettons en œuvre. Mais la mise en œuvre est identique à celle que nous avons utilisée pourdouble
. Copions-collons-nous? Allons-nous convertir l'instance en adouble
et appeler cette méthode d'instance? Pourquoi ne pas mettre la logique dans une méthode de bibliothèque accessible à partir des deux classes. Nous appellerons la bibliothèqueMath
et la fonctionMath.Sqrt
.Nous n'avons même pas abordé d'autres arguments:
GetSqrt
car il renvoie une nouvelle valeur plutôt que de modifier l'instance?Square
?Abs
?Trunc
?Log10
?Ln
?Power
?Factorial
?Sin
?Cos
?ArcTan
?la source
1.sqrt()
vs1.1.sqrt()
(ghads, ça a l'air moche) ont-ils une classe de base commune? Quel est le contrat pour sasqrt()
méthode?1.1.Sqrt
représentait. Intelligent.Les opérations mathématiques sont souvent très sensibles aux performances. Par conséquent, nous voudrons utiliser des méthodes statiques qui peuvent être entièrement résolues (et optimisées ou intégrées) au moment de la compilation. Certains langages n'offrent aucun mécanisme pour spécifier des méthodes distribuées statiquement. En outre, le modèle objet de nombreux langages a une surcharge de mémoire considérable qui est inacceptable pour les types «primitifs» tels que
double
.Quelques langages nous permettent de définir des fonctions qui utilisent la syntaxe d'invocation de méthode, mais sont en fait distribuées statiquement. Les méthodes d'extension en C # 3.0 ou version ultérieure en sont un exemple. Les méthodes non virtuelles (par exemple la méthode par défaut pour les méthodes en C ++) sont un autre cas, bien que C ++ ne prenne pas en charge les méthodes sur les types primitifs. Vous pouvez bien sûr créer votre propre classe wrapper en C ++ qui décore un type primitif avec diverses méthodes, sans surcharge d'exécution. Cependant, vous devrez convertir manuellement les valeurs dans ce type d'encapsuleur.
Il existe quelques langages qui définissent des méthodes sur leurs types numériques. Ce sont généralement des langages très dynamiques où tout est un objet. Ici, la performance est une considération secondaire à l'élégance conceptuelle, mais ces langages ne sont généralement pas utilisés pour le calcul des nombres. Cependant, ces langages peuvent avoir un optimiseur qui peut «décompresser» les opérations sur les primitives.
Avec les considérations techniques à l'écart, nous pouvons examiner si une telle interface mathématique basée sur une méthode serait une bonne interface. Deux problèmes se posent:
42.sqrt
ci apparaîtra beaucoup plus étrangère à de nombreux utilisateurs quesqrt(42)
. En tant qu'utilisateur lourd en mathématiques, je préférerais plutôt la possibilité de créer mes propres opérateurs plutôt que la syntaxe d'appel par méthode point.mean
,median
,variance
,std
,normalize
sur les listes numériques, ou la fonction Gamma pour les nombres) peut être utile. Pour un langage à usage général, cela alourdit simplement l'interface. Reléguer les opérations non essentielles dans un espace de noms séparé rend le type plus accessible à la majorité des utilisateurs.la source
transpose
etmean
qui ne sont là que pour fournir une interface uniforme avec les tableaux NumPy, qui sont la véritable structure de données de travail.Je serais motivé par le fait qu'il y a une tonne de fonctions mathématiques spéciales, et plutôt que de remplir chaque type mathématique avec toutes (ou un sous-ensemble aléatoire) de ces fonctions, vous les mettez dans une classe utilitaire. Sinon, vous pollueriez votre info-bulle de saisie semi-automatique ou vous forceriez les gens à toujours regarder à deux endroits. (Est-ce
sin
assez important pour être membre deDouble
, ou est-ce dans laMath
classe avec des consanguins commehtan
etexp1p
?)Une autre raison pratique est qu'il s'avère qu'il peut y avoir différentes manières de mettre en œuvre des méthodes numériques, avec des compromis de performances et de précision différents. Java a
Math
, et il a aussiStrictMath
.la source
Math.<^space>
? Cette info-bulle de saisie semi-automatique sera également polluée. Inversement, je pense que votre deuxième paragraphe est probablement l'une des meilleures réponses ici.Vous avez correctement observé qu'il y a ici une curieuse symétrie.
Que je dise
sqrt(n)
oun.sqrt()
pas vraiment d'importance, ils expriment tous deux la même chose et celle que vous préférez est plus une question de goût personnel qu'autre chose.C'est aussi pourquoi certains concepteurs de langage plaident fortement pour rendre les deux syntaxes interchangeables. Le langage de programmation D le permet déjà sous une fonctionnalité appelée Syntaxe d'appel de fonction uniforme . Une fonctionnalité similaire a également été proposée pour la normalisation en C ++ . Comme le souligne Mark Amery dans les commentaires , Python le permet également.
Ce n'est pas sans problèmes. L'introduction d'un changement de syntaxe fondamental comme celui-ci a de vastes conséquences sur le code existant et est bien sûr également un sujet de discussions controversées parmi les développeurs qui ont été formés pendant des décennies à penser que les deux syntaxes décrivent des choses différentes.
Je suppose que seul le temps nous dira si l'unification des deux est réalisable à long terme, mais c'est certainement une considération intéressante.
la source
self
comme premier paramètre, et lorsque vous appelez la méthode en tant que propriété d'une instance, plutôt qu'en tant que propriété de la classe, l'instance est implicitement transmise comme premier argument. Je peux donc écrire"foo".startswith("f")
oustr.startswith("foo", "f")
, et je peux écriremy_list.append(x)
oulist.append(my_list, x)
.En plus de la réponse de D Stanley, il faut penser au polymorphisme. Des méthodes comme Math.Sqrt doivent toujours renvoyer la même valeur à la même entrée. Rendre la méthode statique est un bon moyen de clarifier ce point, car les méthodes statiques ne sont pas remplaçables.
Vous avez mentionné la méthode ToString (). Ici, vous voudrez peut-être remplacer cette méthode, de sorte que la (sous) classe est représentée d'une autre manière que String comme sa classe parent. Vous en faites donc une méthode d'instance.
la source
Eh bien, en Java, il existe un wrapper pour chaque type de base.
Et les types de base ne sont pas des types de classe et n'ont pas de fonctions membres.
Vous avez donc les choix suivants:
Math
.Excluons l'option 4, car ... Java est Java, et les adhérents professent l'aimer de cette façon.
Maintenant, nous pouvons également exclure l'option 3, car si l'allocation d'objets est assez bon marché, elle n'est pas gratuite, et cela revient encore et encore .
Deux vers le bas, un à tuer: l'option 2 est également une mauvaise idée, car cela signifie que chaque fonction doit être implémentée pour chaque type, on ne peut pas compter sur l'élargissement de la conversion pour combler les lacunes, ou les incohérences seront vraiment néfastes.
Et en y jetant un œil
java.lang.Math
, il y a beaucoup de lacunes, en particulier pour les types plus petits que leursint
respectifsdouble
.Donc, à la fin, le vainqueur clair est l'option un, les rassemblant tous au même endroit dans une classe de fonctions utilitaires.
En revenant à l'option 4, quelque chose dans ce sens s'est produit beaucoup plus tard: vous pouvez demander au compilateur de considérer tous les membres statiques de n'importe quelle classe lorsque vous résolvez des noms depuis assez longtemps maintenant.
import static someclass.*;
Soit dit en passant, d'autres langages n'ont pas ce problème, soit parce qu'ils n'ont aucun préjugé contre les fonctions libres (utilisant éventuellement des espaces de noms) ou bien moins de petits types.
la source
Math.min()
dans tous les types d'encapsuleurs.Math.sqrt()
a été créé en même temps que le reste de Java, donc quand la décision a été prise de mettre sqrt (),Math
il n'y avait aucune inertie historique des utilisateurs de Java qui "l'aiment comme ça". Bien qu'il n'y ait pas beaucoup de problème avecsqrt()
, le comportement de surcharge deMath.round()
est atroce. Être en mesure d'utiliser la syntaxe des membres avec des valeurs de typefloat
etdouble
aurait évité ce problème.Un point que je ne vois pas mentionné explicitement (bien que amon y fasse allusion) est que la racine carrée peut être considérée comme une opération "dérivée": si l'implémentation ne nous la fournit pas, nous pouvons écrire la nôtre.
Étant donné que la question est étiquetée avec la conception de la langue, nous pourrions envisager une description indépendante de la langue. Bien que de nombreuses langues aient des philosophies différentes, il est très courant à travers les paradigmes d'utiliser l'encapsulation pour préserver les invariants; c'est-à-dire pour éviter d'avoir une valeur qui ne se comporte pas comme le suggère son type.
Par exemple, si nous avons une implémentation d'entiers utilisant des mots machine, nous voulons probablement encapsuler la représentation d'une manière ou d'une autre (par exemple pour empêcher les décalages de bits de changer le signe), mais en même temps nous avons toujours besoin d'accéder à ces bits pour implémenter des opérations comme une addition.
Certains langages peuvent implémenter cela avec des classes et des méthodes privées:
Certains avec des systèmes de modules:
Certains à portée lexicale:
Etc. Cependant, aucun de ces mécanismes n'est nécessaire pour implémenter la racine carrée: il peut être implémenté en utilisant l' interface publique de type numérique, et donc il n'a pas besoin d'accéder aux détails de l'implémentation encapsulée.
Par conséquent, l'emplacement de la racine carrée se résume à la philosophie / aux goûts de la langue et du concepteur de la bibliothèque. Certains peuvent choisir de le mettre "à l'intérieur" des valeurs numériques (par exemple, en faire une méthode d'instance), certains peuvent choisir de le mettre au même niveau que les opérations primitives (cela peut signifier une méthode d'instance, ou cela peut signifier vivre en dehors de la valeurs numériques, mais à l' intérieur du même module / classe / espace de noms, par exemple en tant que fonction autonome ou méthode statique), certains pourraient choisir de le placer dans une collection de fonctions "d'assistance", certains pourraient choisir de le déléguer à des bibliothèques tierces.
la source
En Java et C # ToString est une méthode d'objet, la racine de la hiérarchie des classes, donc chaque objet implémentera la méthode ToString. Pour un Integertype, il est naturel que l'implémentation de ToString fonctionne de cette façon.
Donc, votre raisonnement est faux. La raison pour laquelle les types de valeur implémentent ToString n'est pas que certaines personnes étaient comme: hé, ayons une méthode ToString pour les types de valeur. C'est parce que ToString est déjà là et que c'est "la chose la plus naturelle" à produire.
la source
object
avoir uneToString()
méthode. C'est dans vos mots "certaines personnes étaient comme: hé, ayons une méthode ToString pour les types de valeur".Contrairement à String.substring, Number.sqrt n'est pas vraiment un attribut du nombre mais plutôt un nouveau résultat basé sur votre numéro. Je pense que passer votre numéro à une fonction d'équerrage est plus intuitif.
De plus, l'objet Math contient d'autres membres statiques et il est plus logique de les regrouper et de les utiliser de manière uniforme.
la source