W dyskusji na temat metod statycznych i metod instancji zawsze myślę, że Sqrt()
powinna to być metoda instancji typów liczb zamiast metody statycznej. Dlaczego? Oczywiście działa na wartość.
// looks wrong to me
var y = Math.Sqrt(x);
// looks better to me
var y = x.Sqrt();
Typy wartości oczywiście mogą mieć metody instancji, ponieważ w wielu językach istnieje metoda instancji ToString()
.
Aby odpowiedzieć na niektóre pytania z komentarzy: Dlaczego 1.Sqrt()
nie powinno być legalne? 1.ToString()
jest.
Niektóre języki nie pozwalają na stosowanie metod dla typów wartości, ale niektóre języki mogą. Mówię o nich, w tym Java, ECMAScript, C # i Python (ze __str__(self)
zdefiniowanym). To samo odnosi się do innych funkcji, takich jak ceil()
, floor()
etc.
programming-languages
language-design
history
api-design
math
Pozostałość
źródło
źródło
1.sqrt()
ważny?Sqrt(x)
wygląda o wiele bardziej naturalnie niżx.Sqrt()
jeśli oznacza to, że w niektórych językach oznacza to dodanie klasy do klasy. Gdyby była to metoda instancjix.GetSqrt()
, bardziej odpowiednie byłoby wskazanie, że zwraca wartość, a nie modyfikuje instancję.Odpowiedzi:
Jest to całkowicie wybór projektu językowego. Zależy to również od podstawowej implementacji typów pierwotnych i związanych z tym względów dotyczących wydajności.
.NET ma tylko jedną statyczną
Math.Sqrt
metodę, która działa na adouble
i zwraca adouble
. Wszystko, co mu przekażesz, musi zostać obsadzone lub awansowane nadouble
.Z drugiej strony masz Rust, który udostępnia te operacje jako funkcje dla typów :
Ale Rust ma również uniwersalną składnię wywołań funkcji (ktoś wspomniał o tym wcześniej), więc możesz wybrać, co chcesz.
źródło
x.Sqrt()
, możesz to zrobić.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 .Załóżmy, że projektujemy nowy język i chcemy
Sqrt
być metodą instancji. Patrzymy nadouble
klasę i zaczynamy projektować. Oczywiście nie ma danych wejściowych (innych niż instancja) i zwraca adouble
. Piszemy i testujemy kod. Doskonałość.Ale przyjmowanie pierwiastka kwadratowego z liczby całkowitej jest również poprawne i nie chcemy zmusić wszystkich do konwersji na podwójną wartość tylko do pierwiastka kwadratowego. Więc przechodzimy do
int
projektowania i zaczynamy projektować. Co to zwraca? My mogli zwracaćint
i sprawiają, że działa tylko na idealnych kwadratów lub okrągły wynik z dokładnością doint
(pomijając debaty na temat właściwego sposobu zaokrąglania do tej pory). Ale co, jeśli ktoś chce wyniku niecałkowitego? Powinniśmy mieć dwie metody - jedną, która zwraca an,int
i jedną, która zwraca adouble
(co nie jest możliwe w niektórych językach bez zmiany nazwy). Postanawiamy więc, że powinien zwrócićdouble
. Teraz wdrażamy. Ale implementacja jest identyczna z tą, której używaliśmydouble
. Czy kopiujemy i wklejamy? Czy przekazujemy instancję dodouble
i wywołujemy metodę tej instancji? Dlaczego nie zastosować logiki w metodzie bibliotecznej, do której można uzyskać dostęp z obu klas. Wywołamy bibliotekęMath
i funkcjęMath.Sqrt
.Nie zajęliśmy się nawet innymi argumentami:
GetSqrt
ponieważ zwraca nową wartość zamiast modyfikować instancję?Square
?Abs
?Trunc
?Log10
?Ln
?Power
?Factorial
?Sin
?Cos
?ArcTan
?źródło
1.sqrt()
vs1.1.sqrt()
(ghady, które wyglądają brzydko) czy mają wspólną klasę podstawową? Jaki jest kontrakt na jegosqrt()
metodę?1.1.Sqrt
reprezentuje, zajęło mi cztery czytania . Sprytny.Operacje matematyczne są często bardzo wrażliwe na wyniki. Dlatego będziemy chcieli używać metod statycznych, które można w pełni rozwiązać (i zoptymalizować lub wstawić) w czasie kompilacji. Niektóre języki nie oferują żadnego mechanizmu określającego metody wysyłane statycznie. Co więcej, model obiektowy wielu języków ma znaczny narzut pamięci, który jest nie do przyjęcia dla typów „prymitywnych”, takich jak
double
.Kilka języków pozwala nam definiować funkcje, które używają składni wywołania metody, ale są faktycznie wysyłane statycznie. Przykładem są metody rozszerzenia w C # 3.0 lub nowszym. Metody inne niż wirtualne (np. Domyślne dla metod w C ++) to inny przypadek, chociaż C ++ nie obsługuje metod na typach pierwotnych. Możesz oczywiście stworzyć własną klasę opakowań w C ++, która zdobi prymitywny typ różnymi metodami, bez żadnych narzutów w czasie wykonywania. Będziesz jednak musiał ręcznie przekonwertować wartości na ten typ opakowania.
Istnieje kilka języków, które definiują metody dla swoich typów numerycznych. Są to zwykle bardzo dynamiczne języki, w których wszystko jest przedmiotem. Wydajność jest tutaj drugim aspektem elegancji pojęciowej, ale języki te nie są zwykle używane do kruszenia liczb. Jednak te języki mogą mieć optymalizator, który może „rozpakować” operacje na operacjach podstawowych.
Nieuwzględniając względów technicznych, możemy zastanowić się, czy taki oparty na metodach interfejs matematyczny byłby dobrym interfejsem. Powstają dwa problemy:
42.sqrt
dla wielu użytkowników będzie znacznie bardziej obce niżsqrt(42)
. Jako użytkownik wymagający matematyki wolałbym możliwość tworzenia własnych operatorów niż składni wywołania metody kropki.mean
,median
,variance
,std
,normalize
na listach numerycznych lub funkcji gamma numerów) może być przydatna. W przypadku języka ogólnego, to tylko obciąża interfejs. Przeniesienie niepotrzebnych operacji do oddzielnej przestrzeni nazw sprawia, że ten typ jest bardziej dostępny dla większości użytkowników.źródło
transpose
imean
ma na celu zapewnienie jednolitego interfejsu z tablicami NumPy, które są prawdziwą strukturą danych konia roboczego.Byłbym motywowany faktem, że istnieje mnóstwo funkcji matematycznych specjalnego przeznaczenia, i zamiast zapełniać każdy typ matematyki wszystkimi (lub losowym podzbiorem) tych funkcji, które umieściłeś w klasie użytkowej. W przeciwnym razie zanieczyścisz etykietkę automatycznego uzupełniania lub zmusisz ludzi, by zawsze patrzyli w dwa miejsca. (Czy jest
sin
wystarczająco ważny, aby być członkiemDouble
lub jest wMath
klasie wraz z wsobnymi, takimi jakhtan
iexp1p
?)Innym praktycznym powodem jest fakt, że mogą istnieć różne sposoby wdrażania metod numerycznych, z różnymi kompromisami w zakresie wydajności i precyzji. Java ma
Math
i ma równieżStrictMath
.źródło
Math.<^space>
? Ta etykieta z autouzupełnianiem również zostanie zanieczyszczona. Odwrotnie, myślę, że twój drugi akapit jest prawdopodobnie jedną z lepszych odpowiedzi tutaj.Prawidłowo zauważyłeś, że gra tutaj ciekawa symetria.
Niezależnie od tego, czy powiem,
sqrt(n)
czyn.sqrt()
nie ma to tak naprawdę znaczenia, oba wyrażają to samo, a który preferujesz, jest bardziej kwestią osobistego gustu niż czegokolwiek innego.Dlatego też niektórzy projektanci języków mają mocny argument za zamianą tych dwóch składni. Język programowania D już na to pozwala w ramach funkcji o nazwie Jednolita składnia wywołania funkcji . Podobna funkcja została również zaproponowana do standaryzacji w C ++ . Jak zaznacza Mark Ameryk w komentarzach , Python również na to pozwala.
Nie obyło się bez problemów. Wprowadzenie fundamentalnej zmiany składni, takiej jak ta, ma daleko idące konsekwencje dla istniejącego kodu i jest oczywiście tematem kontrowersyjnych dyskusji między programistami, którzy przez dziesięciolecia byli szkoleni, aby myśleć o tych dwóch składniach jako o różnych opisach.
Wydaje mi się, że tylko czas pokaże, czy unifikacja obu jest możliwa na dłuższą metę, ale jest to z pewnością interesująca uwaga.
źródło
self
jako swój pierwszy parametr, a gdy wywołujesz metodę jako właściwość instancji, a nie jako właściwość klasy, instancja zostaje domyślnie przekazana jako pierwszy argument. Dlatego mogę pisać"foo".startswith("f")
lubstr.startswith("foo", "f")
, i mogę pisaćmy_list.append(x)
lublist.append(my_list, x)
.Oprócz odpowiedzi D. Stanleya musisz pomyśleć o polimorfizmie. Metody takie jak Math.Sqrt powinny zawsze zwracać tę samą wartość do tego samego wejścia. Uczynienie tej metody statyczną jest dobrym sposobem na wyjaśnienie tej kwestii, ponieważ metod statycznych nie można zastąpić.
Wspomniałeś o metodzie ToString (). Tutaj możesz zastąpić tę metodę, aby (pod) klasa była reprezentowana w inny sposób jako String jako jej klasa nadrzędna. Czynisz to instancją Metodą.
źródło
Cóż, w Javie istnieje opakowanie dla każdego podstawowego typu.
Podstawowe typy nie są typami klas i nie mają żadnych funkcji składowych.
Masz więc następujące możliwości:
Math
.Wykluczmy opcję 4, ponieważ ... Java jest Javą, a zwolennicy twierdzą, że tak ją lubią.
Teraz możemy także wykluczyć opcję 3 ponieważ podczas przydzielania obiektów jest dość tanie, to nie jest za darmo, i robi to w kółko robi dodać.
Dwa w dół, jeden wciąż do zabicia: Opcja 2 to także zły pomysł, ponieważ oznacza to, że każda funkcja musi być zaimplementowana dla każdego typu, nie można polegać na poszerzeniu konwersji w celu wypełnienia luk, w przeciwnym razie niekonsekwencje naprawdę będą bolały.
I patrząc na to
java.lang.Math
, istnieje wiele luk, szczególnie w przypadku typów mniejszych niżint
odpowiedniedouble
.Ostatecznie wyraźnym zwycięzcą jest opcja pierwsza, gromadząc je wszystkie w jednym miejscu w klasie funkcji-funkcji.
Wracając do opcji 4, coś w tym kierunku wydarzyło się znacznie później: możesz poprosić kompilator o rozważenie wszystkich statycznych elementów dowolnej klasy, kiedy rozwiązujesz nazwy od dłuższego czasu.
import static someclass.*;
Nawiasem mówiąc, inne języki nie mają tego problemu, ponieważ nie mają żadnego uszczerbku dla wolnych funkcji (opcjonalnie korzystających z przestrzeni nazw) lub znacznie mniej małych typów.
źródło
Math.min()
we wszystkich typach opakowań.Math.sqrt()
został utworzony w tym samym czasie co reszta Java, więc kiedy podjęto decyzję o wprowadzeniu sqrt (),Math
nie było historycznej bezwładności użytkowników Java, którzy „lubią to w ten sposób”. Chociaż nie ma z tym większego problemusqrt()
, zachowanie przeciążenioweMath.round()
jest okropne. Możliwość użycia składni składowej z wartościami typufloat
idouble
pozwoliłaby uniknąć tego problemu.Jedną kwestią, o której nie widzę wyraźnie wspomnianego (chociaż nawiązuje do niego amon), jest to, że pierwiastek kwadratowy można traktować jako operację „pochodną”: jeśli implementacja tego nie zapewnia, możemy napisać własną.
Ponieważ pytanie jest oznaczone wzorem językowym, możemy rozważyć opis zależny od języka. Chociaż wiele języków ma różne filozofie, w paradygmatach bardzo często stosuje się enkapsulację, aby zachować niezmienniki; tzn. aby uniknąć posiadania wartości, która nie zachowuje się tak, jak sugerowałby jej typ.
Na przykład, jeśli mamy jakąś implementację liczb całkowitych używających słów maszynowych, prawdopodobnie chcemy jakoś zakapsułować reprezentację (np. Aby zapobiec zmianie bitów przez zmianę znaku), ale jednocześnie nadal potrzebujemy dostępu do tych bitów, aby zaimplementować operacje takie jak dodanie.
Niektóre języki mogą to zaimplementować za pomocą klas i metod prywatnych:
Niektóre z systemami modułowymi:
Niektóre z zakresem leksykalnym:
I tak dalej. Jednak żaden z tych mechanizmów nie jest potrzebny do implementacji pierwiastka kwadratowego: można go zaimplementować za pomocą publicznego interfejsu typu liczbowego, a zatem nie potrzebuje dostępu do kapsułkowanych szczegółów implementacji.
Dlatego lokalizacja pierwiastka kwadratowego sprowadza się do filozofii / smaków języka i projektanta biblioteki. Niektórzy mogą wybrać, aby umieścić go „wewnątrz” wartości liczbowych (np sprawiają, że metody instancji), niektóre mogą wybrać, aby umieścić go na tym samym poziomie, co prymitywne operacji (może to oznaczać metodę instancji, czy może to oznaczać, żyjących poza wartości liczbowe, ale wewnątrz tego samego modułu / klasy / przestrzeni nazw, np. jako samodzielna funkcja lub metoda statyczna), niektórzy mogą zdecydować o umieszczeniu go w kolekcji funkcji „pomocniczych”, niektórzy mogą zdecydować o przekazaniu go do bibliotek stron trzecich.
źródło
W Javie i C # ToString jest metodą obiektową, rdzeniem hierarchii klas, więc każdy obiekt wdroży metodę ToString. Dla Integertype jest naturalne, że implementacja ToString będzie działać w ten sposób.
Więc twoje rozumowanie jest błędne. Powodem, dla którego typy wartości implementują ToString, nie jest to, że niektórzy ludzie byli tacy: hej, zastosujmy metodę ToString dla typów wartości. To dlatego, że ToString już tam jest i jest to „najbardziej naturalna” rzecz do wydrukowania.
źródło
object
zastosowaniuToString()
metody. To jest w twoich słowach „niektórzy ludzie byli tacy: hej, zastosujmy metodę ToString dla typów wartości”.W przeciwieństwie do String.substring, Number.sqrt nie jest tak naprawdę atrybutem liczby, ale raczej nowym wynikiem opartym na twoim numerze. Myślę, że przekazanie numeru do funkcji kwadratu jest bardziej intuicyjne.
Co więcej, obiekt Math zawiera inne elementy statyczne i bardziej sensowne jest zebranie ich razem i użycie w jednolity sposób.
źródło