En una discusión sobre métodos estáticos y de instancia, siempre pienso que ese Sqrt()
debería ser un método de instancia de tipos de números en lugar de un método estático. ¿Porqué es eso? Obviamente funciona en un valor.
// looks wrong to me
var y = Math.Sqrt(x);
// looks better to me
var y = x.Sqrt();
Los tipos de valor obviamente pueden tener métodos de instancia, ya que en muchos idiomas hay un método de instancia ToString()
.
Para responder algunas preguntas de los comentarios: ¿Por qué 1.Sqrt()
no debería ser legal? 1.ToString()
es.
Algunos idiomas no permiten tener métodos sobre tipos de valores, pero algunos idiomas sí. Estoy hablando de estos, incluidos Java, ECMAScript, C # y Python (con __str__(self)
definido). Lo mismo se aplica a otras funciones como ceil()
, floor()
etc.
1.sqrt()
valido?Sqrt(x)
ve mucho más natural quex.Sqrt()
si eso significa anteponer la función con la clase en algunos idiomas, estoy de acuerdo con eso. Si se tratara de un método de instancia,x.GetSqrt()
sería más apropiado indicar que está devolviendo un valor en lugar de modificar la instancia.Respuestas:
Es completamente una elección del diseño del lenguaje. También depende de la implementación subyacente de los tipos primitivos y las consideraciones de rendimiento debido a eso.
.NET tiene solo un
Math.Sqrt
método estático que actúa sobre aydouble
devuelve adouble
. Cualquier otra cosa que le pases debe ser lanzada o promovida a adouble
.Por otro lado, tiene Rust que expone estas operaciones como funciones en los tipos :
Pero Rust también tiene una sintaxis de llamada de función universal (alguien lo mencionó anteriormente), por lo que puede elegir lo que quiera.
fuente
x.Sqrt()
, puede hacerlo.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 .Supongamos que estamos diseñando un nuevo lenguaje y queremos
Sqrt
ser un método de instancia. Entonces miramos ladouble
clase y comenzamos a diseñar. Obviamente no tiene entradas (aparte de la instancia) y devuelve adouble
. Escribimos y probamos el código. Perfección.Pero tomar la raíz cuadrada de un número entero también es válido, y no queremos obligar a todos a convertir a un doble solo para sacar una raíz cuadrada. Entonces nos movemos
int
y comenzamos a diseñar. ¿Qué devuelve? Nos podríamos devolver unaint
y hacer que funcione sólo para los cuadrados perfectos, o redondear el resultado al más cercanoint
(ignorando el debate sobre el método de redondeo adecuado por ahora). Pero, ¿qué pasa si alguien quiere un resultado no entero? Deberíamos tener dos métodos: uno que devuelve unint
y otro que devuelve undouble
(que no es posible en algunos idiomas sin cambiar el nombre). Entonces decidimos que debería devolver adouble
. Ahora lo implementamos. Pero la implementación es idéntica a la que usamos paradouble
. ¿Copiamos y pegamos? ¿Lanzamos la instancia a aydouble
llamamos a ese método de instancia? ¿Por qué no poner la lógica en un método de biblioteca al que se pueda acceder desde ambas clases? Llamaremos a la bibliotecaMath
y a la funciónMath.Sqrt
.Ni siquiera hemos abordado otros argumentos:
GetSqrt
ya que devuelve un nuevo valor en lugar de modificar la instancia?Square
?Abs
?Trunc
?Log10
?Ln
?Power
?Factorial
?Sin
?Cos
?ArcTan
?fuente
1.sqrt()
vs1.1.sqrt()
(ghads, que se ve feo) ¿tienen una clase base común? ¿Cuál es el contrato para susqrt()
método?1.1.Sqrt
representaba. Inteligente.Las operaciones matemáticas a menudo son muy sensibles al rendimiento. Por lo tanto, querremos utilizar métodos estáticos que puedan resolverse por completo (y optimizarse o incorporarse) en tiempo de compilación. Algunos idiomas no ofrecen ningún mecanismo para especificar métodos despachados estáticamente. Además, el modelo de objetos de muchos idiomas tiene una sobrecarga de memoria considerable que es inaceptable para tipos "primitivos" como
double
.Algunos idiomas nos permiten definir funciones que utilizan la sintaxis de invocación de métodos, pero en realidad se envían estáticamente. Los métodos de extensión en C # 3.0 o posterior son un ejemplo. Los métodos no virtuales (por ejemplo, el predeterminado para los métodos en C ++) son otro caso, aunque C ++ no admite métodos en tipos primitivos. Por supuesto, podría crear su propia clase de contenedor en C ++ que decora un tipo primitivo con varios métodos, sin ningún gasto de tiempo de ejecución. Sin embargo, tendrá que convertir manualmente los valores a ese tipo de contenedor.
Hay un par de idiomas que definen métodos en sus tipos numéricos. Estos suelen ser lenguajes muy dinámicos donde todo es un objeto. Aquí, el rendimiento es una consideración secundaria a la elegancia conceptual, pero esos lenguajes generalmente no se usan para la combinación de números. Sin embargo, estos lenguajes pueden tener un optimizador que puede "desempaquetar" operaciones en primitivas.
Sin tener en cuenta las consideraciones técnicas, podemos considerar si una interfaz matemática basada en métodos sería una buena interfaz. Surgen dos problemas:
42.sqrt
parecerá mucho más ajena a muchos usuarios quesqrt(42)
. Como usuario con muchas matemáticas, prefiero la posibilidad de crear mis propios operadores sobre la sintaxis de llamada al método de puntos.mean
,median
,variance
,std
,normalize
en las listas numéricas, o la función gamma para los números) puede ser útil. Para un lenguaje de propósito general, esto solo pesa la interfaz. Relegar operaciones no esenciales a un espacio de nombres separado hace que el tipo sea más accesible para la mayoría de los usuarios.fuente
transpose
ymean
solo están ahí para proporcionar una interfaz uniforme con las matrices NumPy, que son la estructura de datos real del caballo de batalla.Me motivaría el hecho de que hay un montón de funciones matemáticas de propósito especial, y en lugar de llenar cada tipo de matemáticas con todas (o un subconjunto aleatorio) de esas funciones, las coloca en una clase de utilidad. De lo contrario, contaminaría la información sobre herramientas de autocompletar o forzaría a las personas a mirar siempre en dos lugares. (¿Es lo
sin
suficientemente importante como para ser miembroDouble
, o está en laMath
clase junto con endogámicos comohtan
yexp1p
?)Otra razón práctica es que resulta que puede haber diferentes formas de implementar métodos numéricos, con diferentes compensaciones de rendimiento y precisión. Java tiene
Math
, y también tieneStrictMath
.fuente
Math.<^space>
? Esa información sobre herramientas de autocompletar también se contaminará. Inversamente, creo que su segundo párrafo es probablemente una de las mejores respuestas aquí.Has observado correctamente que hay una curiosa simetría en juego aquí.
Si digo
sqrt(n)
on.sqrt()
no realmente importa, ambos expresan lo mismo y cuál prefieres es más una cuestión de gusto personal que otra cosa.Esa es también la razón por la cual existe un fuerte argumento de ciertos diseñadores de lenguaje para hacer que las dos sintaxis sean intercambiables. El lenguaje de programación D ya lo permite bajo una característica llamada Sintaxis uniforme de llamada de función . También se ha propuesto una característica similar para la estandarización en C ++ . Como Mark Amery señala en los comentarios , Python también lo permite.
Esto no está exento de problemas. La introducción de un cambio de sintaxis fundamental como este tiene consecuencias de gran alcance para el código existente y, por supuesto, también es un tema de debates controvertidos entre desarrolladores que han sido entrenados durante décadas para pensar que las dos sintaxis describen cosas diferentes.
Supongo que solo el tiempo dirá si la unificación de los dos es factible a largo plazo, pero definitivamente es una consideración interesante.
fuente
self
como primer parámetro, y cuando llama al método como una propiedad de una instancia, en lugar de como una propiedad de la clase, la instancia se pasa implícitamente como el primer argumento. Por lo tanto, puedo escribir"foo".startswith("f")
ostr.startswith("foo", "f")
, y puedo escribirmy_list.append(x)
olist.append(my_list, x)
.Además de la respuesta de D Stanley, debes pensar en el polimorfismo. Métodos como Math.Sqrt siempre deben devolver el mismo valor a la misma entrada. Hacer que el método sea estático es una buena manera de aclarar este punto, ya que los métodos estáticos no se pueden anular.
Usted mencionó el método ToString (). Aquí es posible que desee anular este método, por lo que la (sub) clase se representa de otra manera como String como su clase principal. Entonces lo convierte en un método de instancia.
fuente
Bueno, en Java hay un contenedor para cada tipo básico.
Y los tipos básicos no son tipos de clase y no tienen funciones miembro.
Entonces, tiene las siguientes opciones:
Math
.Descartemos la opción 4, porque ... Java es Java, y los adherentes dicen que les gusta de esa manera.
Ahora, también podemos descartar la opción 3 porque si bien la asignación de objetos es bastante barata, no es gratis, y hacerlo una y otra vez sí se suma.
Dos abajo, uno aún por matar: la opción 2 también es una mala idea, porque significa que cada función debe implementarse para cada tipo, no se puede confiar en ampliar la conversión para llenar los vacíos, o las inconsistencias realmente dolerán.
Y echando un vistazo
java.lang.Math
, hay muchas lagunas, especialmente para los tipos más pequeños que losint
respectivosdouble
.Entonces, al final, el claro vencedor es la opción uno, reuniéndolos a todos en un solo lugar en una clase de función de utilidad.
Volviendo a la opción 4, algo en esa dirección sucedió mucho más tarde: puede pedirle al compilador que considere todos los miembros estáticos de cualquier clase que desee al resolver nombres durante bastante tiempo.
import static someclass.*;
Por otro lado, otros lenguajes no tienen ese problema, ya sea porque no tienen prejuicios contra las funciones libres (opcionalmente usando espacios de nombres) o muchos menos tipos pequeños.
fuente
Math.min()
en todos los tipos de envoltorios.Math.sqrt()
fue creado al mismo tiempo que el resto de Java, por lo que cuando se tomó la decisión de poner sqrt ()Math
no hubo inercia histórica de los usuarios de Java que "les gusta de esa manera". Si bien no hay muchos problemassqrt()
, el comportamiento de sobrecarga deMath.round()
es atroz. Ser capaz de usar la sintaxis de miembro con valores de tipofloat
ydouble
habría evitado ese problema.Un punto que no veo mencionado explícitamente (aunque amon lo alude) es que la raíz cuadrada se puede considerar como una operación "derivada": si la implementación no nos la proporciona, podemos escribir la nuestra.
Dado que la pregunta está etiquetada con diseño de lenguaje, podríamos considerar alguna descripción independiente del lenguaje. Aunque muchos idiomas tienen filosofías diferentes, es muy común en todos los paradigmas utilizar la encapsulación para preservar invariantes; es decir, para evitar tener un valor que no se comporte como su tipo sugeriría.
Por ejemplo, si tenemos alguna implementación de enteros usando palabras de máquina, probablemente queremos encapsular la representación de alguna manera (por ejemplo, para evitar que los cambios de bit cambien el signo), pero al mismo tiempo todavía necesitamos acceso a esos bits para implementar operaciones como adición.
Algunos idiomas pueden implementar esto con clases y métodos privados:
Algunos con sistemas de módulos:
Algunos con alcance léxico:
Y así. Sin embargo, ninguno de estos mecanismos es necesario para implementar la raíz cuadrada: puede implementarse utilizando la interfaz pública de tipo numérico y, por lo tanto, no necesita acceso a los detalles de implementación encapsulados.
Por lo tanto, la ubicación de la raíz cuadrada se reduce a la filosofía / gustos del lenguaje y del diseñador de la biblioteca. Algunos pueden optar por ponerlo "dentro" de los valores numéricos (por ejemplo, convertirlo en un método de instancia), algunos pueden optar por ponerlo al mismo nivel que las operaciones primitivas (esto puede significar un método de instancia, o puede significar vivir fuera del valores numéricos, pero dentro del mismo módulo / clase / espacio de nombres, por ejemplo, como una función independiente o método estático), algunos podrían optar por ponerlo en una colección de funciones "auxiliares", algunos podrían optar por delegarlo en bibliotecas de terceros.
fuente
En Java y C # ToString es un método de objeto, la raíz de la jerarquía de clases, por lo que cada objeto implementará el método ToString. Para un Integertype es natural que la implementación de ToString funcione de esta manera.
Entonces tu razonamiento está mal. La razón por la que los tipos de valor implementan ToString no es que algunas personas fueran así: oye, tengamos un método ToString para los tipos de Valor. Es porque ToString ya está allí y es "lo más natural" de salida.
fuente
object
tener unToString()
método. Eso está en sus palabras "algunas personas eran como: oye, tengamos un método ToString para los tipos de valor".A diferencia de String.substring, Number.sqrt no es realmente un atributo del número, sino un nuevo resultado basado en su número. Creo que pasar su número a una función de cuadratura es más intuitivo.
Además, el objeto Math contiene otros miembros estáticos y tiene más sentido agruparlos y usarlos de manera uniforme.
fuente