Em uma discussão sobre métodos estáticos e de instância, eu sempre penso que Sqrt()
deveria ser um método de instância de tipos numéricos em vez de um método estático. Por que é que? Obviamente, funciona com um valor.
// looks wrong to me
var y = Math.Sqrt(x);
// looks better to me
var y = x.Sqrt();
Os tipos de valor obviamente podem ter métodos de instância, pois em muitos idiomas, existe um método de instância ToString()
.
Para responder a algumas perguntas dos comentários: Por que 1.Sqrt()
não deve ser legal? 1.ToString()
é.
Alguns idiomas não permitem ter métodos em tipos de valor, mas outros podem. Eu estou falando sobre isso, incluindo Java, ECMAScript, C # e Python (com __str__(self)
definido). O mesmo se aplica a outras funções ceil()
, como floor()
etc.
1.sqrt()
válido?Sqrt(x)
parece muito mais natural do quex.Sqrt()
Se isso significa anexar a função à classe em alguns idiomas, estou bem com isso. Se fosse um método de instância,x.GetSqrt()
seria mais apropriado indicar que ele está retornando um valor em vez de modificar a instância.Respostas:
É inteiramente uma escolha de design de linguagem. Também depende da implementação subjacente de tipos primitivos e de considerações de desempenho devido a isso.
O .NET possui apenas um
Math.Sqrt
método estático que atua sobredouble
e retorna adouble
. Qualquer outra coisa que você passar para ele deve ser convertida ou promovida para adouble
.Por outro lado, você possui o Rust, que expõe essas operações como funções nos tipos :
Mas o Rust também possui sintaxe de chamada de função universal (alguém mencionou isso anteriormente), para que você possa escolher o que quiser.
fonte
x.Sqrt()
, isso pode ser feito.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 .Suponha que estamos projetando uma nova linguagem e queremos
Sqrt
ser um método de instância. Então, olhamos para adouble
classe e começamos a projetar. Obviamente, não possui entradas (além da instância) e retorna adouble
. Nós escrevemos e testamos o código. Perfeição.Mas pegar a raiz quadrada de um número inteiro também é válido, e não queremos forçar todos a converterem em um duplo apenas para obter uma raiz quadrada. Então, passamos
int
e começamos a projetar. O que isso retorna? Nós poderia retornar umint
e fazê-lo funcionar apenas para quadrados perfeitos, ou arredondar o resultado para o mais próximoint
(ignorando o debate sobre o método de arredondamento adequado para agora). Mas e se alguém quiser um resultado não inteiro? Devemos ter dois métodos - um que retorna umint
e outro que retorna adouble
(o que não é possível em alguns idiomas sem alterar o nome). Então, decidimos que ele deve retornar adouble
. Agora implementamos. Mas a implementação é idêntica à que usamos paradouble
. Copiamos e colamos? Nós convertemos a instância para adouble
e chamamos esse método de instância? Por que não colocar a lógica em um método de biblioteca que pode ser acessado de ambas as classes. Vamos chamar a bibliotecaMath
e a funçãoMath.Sqrt
.Ainda não abordamos outros argumentos:
GetSqrt
pois retorna um novo valor em vez de modificar a instância?Square
?Abs
?Trunc
?Log10
?Ln
?Power
?Factorial
?Sin
?Cos
?ArcTan
?fonte
1.sqrt()
vs1.1.sqrt()
(ghads, que parece feio) eles têm uma classe base comum? Qual é o contrato para o seusqrt()
método?1.1.Sqrt
representava. Inteligente.As operações matemáticas geralmente são muito sensíveis ao desempenho. Portanto, queremos usar métodos estáticos que possam ser totalmente resolvidos (e otimizados ou incorporados) em tempo de compilação. Alguns idiomas não oferecem nenhum mecanismo para especificar métodos enviados estaticamente. Além disso, o modelo de objeto de muitas linguagens possui uma sobrecarga considerável de memória que é inaceitável para tipos "primitivos" como
double
.Algumas linguagens nos permitem definir funções que usam sintaxe de chamada de método, mas são realmente despachadas estaticamente. Métodos de extensão no C # 3.0 ou posterior são um exemplo. Métodos não virtuais (por exemplo, o padrão para métodos em C ++) são outro caso, embora o C ++ não suporte métodos em tipos primitivos. Obviamente, você poderia criar sua própria classe de wrapper em C ++ que decora um tipo primitivo com vários métodos, sem qualquer sobrecarga de tempo de execução. No entanto, você precisará converter valores manualmente para esse tipo de invólucro.
Existem alguns idiomas que definem métodos em seus tipos numéricos. Geralmente, são linguagens altamente dinâmicas em que tudo é um objeto. Aqui, o desempenho é uma consideração secundária à elegância conceitual, mas essas linguagens geralmente não são usadas para processamento de números. No entanto, esses idiomas podem ter um otimizador que pode "unbox" operações em primitivas.
Com as considerações técnicas fora do caminho, podemos considerar se essa interface matemática baseada em método seria uma boa interface. Duas questões surgem:
42.sqrt
parecerá muito mais estranha para muitos usuários do quesqrt(42)
. Como um usuário pesado em matemática, prefiro a capacidade de criar meus próprios operadores em vez da sintaxe de chamada de método de ponto.mean
,median
,variance
,std
,normalize
em listas numérico ou a função Gamma para números) pode ser útil. Para uma linguagem de uso geral, isso apenas sobrecarrega a interface. Relegar operações não essenciais a um espaço para nome separado torna o tipo mais acessível para a maioria dos usuários.fonte
transpose
emean
existe apenas para fornecer uma interface uniforme com matrizes NumPy, que são a estrutura de dados real do corpo de trabalho.Eu ficaria motivado pelo fato de haver uma tonelada de funções matemáticas para fins especiais e, em vez de preencher todos os tipos de matemática com todas (ou um subconjunto aleatório) daquelas funções que você as coloca em uma classe de utilidade. Caso contrário, você poluirá sua dica de ferramenta de preenchimento automático ou forçará as pessoas a procurar sempre em dois lugares. (É
sin
importante o suficiente para ser um membro deDouble
, ou está naMath
classe junto com os parentes comohtan
eexp1p
?)Outra razão prática é que pode haver diferentes maneiras de implementar métodos numéricos, com diferentes trocas de desempenho e precisão. Java tem
Math
, e também temStrictMath
.fonte
Math.<^space>
? Essa dica de ferramenta de preenchimento automático também será poluída. Inversamente, acho que seu segundo parágrafo é provavelmente uma das melhores respostas aqui.Você observou corretamente que existe uma curiosa simetria em jogo aqui.
Se digo
sqrt(n)
oun.sqrt()
não realmente importa, ambos expressam a mesma coisa e qual você prefere é mais uma questão de gosto pessoal do que qualquer outra coisa.É também por isso que há um forte argumento de certos designers de linguagem para tornar as duas sintaxes intercambiáveis. A linguagem de programação D já permite isso em um recurso chamado Sintaxe de chamada de função uniforme . Um recurso semelhante também foi proposto para padronização em C ++ . Como Mark Amery aponta nos comentários , o Python também permite isso.
Isso não é sem problemas. A introdução de uma mudança fundamental de sintaxe como essa tem conseqüências abrangentes para o código existente e, é claro, também é um tópico de discussões controversas entre desenvolvedores treinados há décadas para pensar nas duas sintaxes como descrevendo coisas diferentes.
Acho que apenas o tempo dirá se a unificação dos dois é viável a longo prazo, mas é definitivamente uma consideração interessante.
fonte
self
como primeiro parâmetro e, quando você chama o método como propriedade de uma instância, em vez de propriedade da classe, a instância é passada implicitamente como o primeiro argumento. Portanto, eu posso escrever"foo".startswith("f")
oustr.startswith("foo", "f")
, e eu posso escrevermy_list.append(x)
oulist.append(my_list, x)
.Além da resposta de D Stanley, você tem que pensar em polimorfismo. Métodos como Math.Sqrt sempre devem retornar o mesmo valor para a mesma entrada. Tornar o método estático é uma boa maneira de esclarecer esse ponto, pois os métodos estáticos não são substituíveis.
Você mencionou o método ToString (). Aqui você pode substituir esse método, para que a (sub) classe seja representada de outra maneira como String como sua classe pai. Então você o torna um método de instância.
fonte
Bem, em Java, existe um invólucro para cada tipo básico.
E tipos básicos não são tipos de classe e não têm funções-membro.
Então, você tem as seguintes opções:
Math
.Vamos descartar a opção 4, porque ... Java é Java, e os aderentes professam gostar dessa maneira.
Agora, nós também podemos descartar a opção 3, porque ao alocar objetos é bastante barato, não é livre, e fazer isso uma e outra vez não se somam.
Dois a menos, um ainda a ser eliminado: a opção 2 também é uma péssima idéia, porque significa que todas as funções devem ser implementadas para todos os tipos, não se pode contar com a ampliação da conversão para preencher as lacunas, ou as inconsistências realmente prejudicarão.
E, observando
java.lang.Math
, existem muitas lacunas, especialmente para os tipos menores que osint
respectivosdouble
.Portanto, no final, o vencedor claro é a opção um, coletando todos eles em um só lugar em uma classe de função de utilidade.
Voltando à opção 4, algo nessa direção realmente aconteceu muito mais tarde: você pode solicitar ao compilador que considere todos os membros estáticos de qualquer classe que desejar ao resolver nomes por um longo tempo.
import static someclass.*;
Além disso, outros idiomas não têm esse problema, porque não têm preconceito contra funções livres (opcionalmente usando espaços para nome) ou muito menos tipos pequenos.
fonte
Math.min()
em todos os tipos de wrapper.Math.sqrt()
foi criado ao mesmo tempo que o restante do Java, portanto, quando foi tomada a decisão de colocar o sqrt (),Math
não havia inércia histórica dos usuários do Java que "gostam dessa maneira". Embora não exista muito problemasqrt()
, o comportamento de sobrecarga deMath.round()
é atroz. Ser capaz de usar a sintaxe do membro com valores do tipofloat
edouble
teria evitado esse problema.Um ponto que não vejo mencionado explicitamente (embora amon faça alusão a ele) é que a raiz quadrada pode ser pensada como uma operação "derivada": se a implementação não fornecer isso para nós, podemos escrever nossos próprios.
Como a pergunta está marcada com design de linguagem, podemos considerar uma descrição independente de idioma. Embora muitas línguas tenham filosofias diferentes, é muito comum entre paradigmas usar encapsulamento para preservar invariantes; ou seja, para evitar ter um valor que não se comporte conforme o tipo sugerido.
Por exemplo, se tivermos alguma implementação de números inteiros usando palavras de máquina, provavelmente queremos encapsular a representação de alguma forma (por exemplo, para evitar que mudanças de bits alterem o sinal), mas ao mesmo tempo ainda precisamos acessar esses bits para implementar operações como Adição.
Alguns idiomas podem implementar isso com classes e métodos privados:
Alguns com sistemas de módulos:
Alguns com escopo lexical:
E assim por diante. No entanto, nenhum desses mecanismos é necessário para implementar a raiz quadrada: ele pode ser implementado usando a interface pública de um tipo numérico e, portanto, não precisa acessar os detalhes da implementação encapsulada.
Portanto, a localização da raiz quadrada se resume à filosofia / gostos da linguagem e do designer da biblioteca. Alguns podem optar por colocá-lo "dentro" dos valores numéricos (por exemplo, torná-lo um método de instância), outros podem optar por colocá-lo no mesmo nível das operações primitivas (isso pode significar um método de instância ou pode viver fora do valores numéricos, mas dentro do mesmo módulo / classe / espaço para nome, por exemplo, como uma função autônoma ou método estático), alguns podem optar por colocá-lo em uma coleção de funções "auxiliares", outros podem optar por delegá-lo a bibliotecas de terceiros.
fonte
Em Java e C #, o ToString é um método de objeto, a raiz da hierarquia de classes; portanto, todo objeto implementará o método ToString. Para um tipo inteiro, é natural que a implementação do ToString funcione dessa maneira.
Então, seu raciocínio está errado. A razão pela qual os tipos de valor implementam o ToString não é o que algumas pessoas pensam: ei, vamos usar o método ToString para os tipos Value. É porque o ToString já está lá e é "a coisa mais natural" a ser produzida.
fonte
object
ter umToString()
método. Ou seja, em suas palavras "algumas pessoas eram como: ei, vamos ter um método ToString para tipos de valor".Ao contrário de String.substring, Number.sqrt não é realmente um atributo do número, mas um novo resultado com base no seu número. Eu acho que passar seu número para uma função quadrática é mais intuitivo.
Além disso, o objeto Math contém outros membros estáticos e faz mais sentido agrupá-los e usá-los de maneira uniforme.
fonte