Tại sao Math.Sqrt () là hàm tĩnh?


31

Trong một cuộc thảo luận về các phương thức tĩnh và cá thể, tôi luôn nghĩ rằng, đó Sqrt()phải là một phương thức thể hiện của các kiểu số thay vì một phương thức tĩnh. Tại sao vậy? Nó rõ ràng hoạt động trên một giá trị.

 // looks wrong to me
 var y = Math.Sqrt(x);
 // looks better to me
 var y = x.Sqrt();

Các loại giá trị rõ ràng có thể có các phương thức cá thể, như trong nhiều ngôn ngữ, có một phương thức cá thể ToString().

Để trả lời một số câu hỏi từ các ý kiến: Tại sao 1.Sqrt()không nên hợp pháp? 1.ToString()Là.

Một số ngôn ngữ không cho phép có các phương thức trên các loại giá trị, nhưng một số ngôn ngữ có thể. Tôi đang nói về những điều này, bao gồm Java, ECMAScript, C # và Python (với __str__(self)định nghĩa). Điều tương tự áp dụng cho các chức năng khác như ceil(), floor()vv


18
Ngôn ngữ nào bạn đề xuất này? Sẽ 1.sqrt()hợp lệ?

20
Trong nhiều ngôn ngữ (ví dụ java), nhân đôi là nguyên thủy (vì lý do hiệu suất) vì vậy họ không có phương pháp
Richard Tingle

45
Vì vậy, các loại số nên được làm đầy với mọi hàm toán học có thể được áp dụng cho chúng?
D Stanley

19
FWIW Tôi nghĩ rằng Sqrt(x)vẻ bề ngoài nhiều hơn tự nhiên hơn x.Sqrt() Nếu điều đó có nghĩa prepending chức năng với các lớp trong một số ngôn ngữ tôi vẫn ổn với điều đó. Nếu đó một phương thức cá thể thì x.GetSqrt()sẽ phù hợp hơn để chỉ ra rằng nó trả về một giá trị thay vì sửa đổi thể hiện.
D Stanley

23
Câu hỏi này không thể là ngôn ngữ bất khả tri ở dạng hiện tại. Đó là gốc rễ của vấn đề.
tăngDarkness

Câu trả lời:


20

Nó hoàn toàn là một sự lựa chọn của thiết kế ngôn ngữ. Nó cũng phụ thuộc vào việc thực hiện cơ bản của các loại nguyên thủy và xem xét hiệu suất do đó.

.NET chỉ có một Math.Sqrtphương thức tĩnh hoạt động trên a doublevà trả về adouble . Bất cứ điều gì khác mà bạn vượt qua nó phải được chọn hoặc thăng cấp lên a double.

double sqrt2 = Math.Sqrt(2d);

Mặt khác, bạn có Rust hiển thị các hoạt động này dưới dạng các hàm trên các loại :

let sqrt2 = 2.0f32.sqrt();
let higher = 2.0f32.max(3.0f32);

Nhưng Rust cũng có cú pháp gọi hàm phổ quát (ai đó đã đề cập trước đó), vì vậy bạn có thể chọn bất cứ thứ gì bạn thích.

let sqrt2 = f32::sqrt(2.0f32);
let higher = f32::max(2.0f32, 3.0f32);

1
Điều đáng chú ý là trong .NET bạn có thể viết các phương thức mở rộng, vì vậy nếu bạn thực sự muốn thực hiện việc này giống như vậy x.Sqrt(), thì có thể thực hiện được. public static class DoubleExtensions { public static double Sqrt( this double self) { return Math.Sqrt(self); } }
Zachary Dow

1
Ngoài ra, trong C # 6, nó có thể chỉ là Sqrt(x) msdn.microsoft.com/en-us/l Library / sf0df423.aspx .
Den

65

Giả sử chúng ta đang thiết kế một ngôn ngữ mới và chúng ta muốn Sqrttrở thành một phương thức ví dụ. Vì vậy, chúng tôi nhìn vào doublelớp học và bắt đầu thiết kế. Nó rõ ràng không có đầu vào (trừ trường hợp) và trả về a double. Chúng tôi viết và kiểm tra mã. Sự hoàn hảo.

Nhưng lấy căn bậc hai của một số nguyên cũng hợp lệ và chúng tôi không muốn buộc mọi người chuyển đổi thành gấp đôi chỉ để lấy căn bậc hai. Vì vậy, chúng tôi di chuyển đến intvà bắt đầu thiết kế. Nó trở về cái gì? Chúng ta có thể trả về một intvà làm cho nó chỉ hoạt động cho các ô vuông hoàn hảo, hoặc làm tròn kết quả đến gần nhất int(bỏ qua các cuộc tranh luận về phương pháp làm tròn thích hợp cho đến bây giờ). Nhưng nếu ai đó muốn một kết quả không nguyên? Chúng ta nên có hai phương thức - một phương thức trả về một intvà một phương thức trả về một double(không thể thực hiện được trong một số ngôn ngữ mà không thay đổi tên). Vì vậy, chúng tôi quyết định rằng nó sẽ trở lại a double. Bây giờ chúng tôi thực hiện. Nhưng việc thực hiện giống hệt như chúng ta đã sử dụng chodouble. Chúng ta có sao chép và dán không? Chúng ta có đưa ra thể hiện cho a doublevà gọi phương thức cá thể đó không? Tại sao không đặt logic trong một phương thức thư viện có thể được truy cập từ cả hai lớp. Chúng tôi sẽ gọi thư viện Mathvà chức năng Math.Sqrt.

Tại sao là Math.Sqrthàm tĩnh?:

  • Bởi vì việc thực hiện là như nhau bất kể kiểu số cơ bản
  • Bởi vì nó không ảnh hưởng đến một trường hợp cụ thể (nó nhận một giá trị và trả về kết quả)
  • Bởi vì các kiểu số không phụ thuộc vào chức năng đó, do đó, có ý nghĩa khi có nó trong một lớp riêng biệt

Chúng tôi thậm chí không giải quyết các đối số khác:

  • Nó có nên được đặt tên GetSqrtvì nó trả về một giá trị mới thay vì sửa đổi thể hiện?
  • Thế còn Square? Abs? Trunc? Log10? Ln? Power? Factorial? Sin? Cos? ArcTan?

6
Không đề cập đến niềm vui của 1.sqrt()vs 1.1.sqrt()(ghads, trông xấu xí) họ có một lớp cơ sở chung không? Hợp đồng cho sqrt()phương pháp của nó là gì?

5
@MichaelT Ví dụ hay. Tôi phải mất bốn lần đọc để hiểu những gì 1.1.Sqrtđại diện. Tài giỏi.
D Stanley

17
Tôi không thực sự rõ ràng từ câu trả lời này làm thế nào lớp tĩnh giúp với lý do chính của bạn. Nếu bạn có gấp đôi Sqrt (int) và gấp đôi Sqrt (gấp đôi) trên lớp Toán của mình, bạn có hai tùy chọn: chuyển đổi int thành gấp đôi, sau đó gọi vào phiên bản kép hoặc sao chép và dán phương thức với các thay đổi phù hợp (nếu có ). Nhưng đó là những tùy chọn chính xác giống như bạn mô tả cho phiên bản cá thể. Lý do khác của bạn (đặc biệt là điểm đạn thứ ba của bạn) Tôi đồng ý với nhiều hơn.
Ben Aaronson

14
-1 câu trả lời này là vô lý, bất kỳ điều này có liên quan gì đến tĩnh? Bạn sẽ quyết định câu trả lời cho những câu hỏi tương tự theo cách đó (và "[với hàm tĩnh] việc thực hiện là như nhau" là sai hoặc ít nhất là không đúng hơn so với các phương thức ví dụ ..)
BlueRaja - Daniel Pflughoeft

20
"Việc thực hiện là như nhau bất kể kiểu số cơ bản" là hoàn toàn vô nghĩa. Việc triển khai hàm căn bậc hai cần khác biệt đáng kể dựa trên loại chúng đang làm việc để không bị kém hiệu quả.
R ..

25

Các phép toán thường rất nhạy cảm với hiệu năng. Do đó, chúng tôi sẽ muốn sử dụng các phương thức tĩnh có thể được giải quyết hoàn toàn (và tối ưu hóa, hoặc nội tuyến) tại thời điểm biên dịch. Một số ngôn ngữ không cung cấp bất kỳ cơ chế nào để chỉ định các phương thức được gửi tĩnh. Hơn nữa, mô hình đối tượng của nhiều ngôn ngữ có chi phí sử dụng bộ nhớ đáng kể không thể chấp nhận được đối với các loại hình nguyên thủy của chế độ cơ bản như double.

Một vài ngôn ngữ cho phép chúng ta xác định các hàm sử dụng cú pháp gọi phương thức, nhưng thực sự được gửi tĩnh. Các phương thức mở rộng trong C # 3.0 trở lên là một ví dụ. Các phương thức không ảo (ví dụ: mặc định cho các phương thức trong C ++) là một trường hợp khác, mặc dù C ++ không hỗ trợ các phương thức trên các kiểu nguyên thủy. Tất nhiên, bạn có thể tạo lớp trình bao bọc của riêng mình trong C ++, trang trí một kiểu nguyên thủy bằng nhiều phương thức khác nhau, không có bất kỳ chi phí thời gian chạy nào. Tuy nhiên, bạn sẽ phải chuyển đổi thủ công các giá trị sang loại trình bao bọc đó.

Có một vài ngôn ngữ xác định các phương thức trên các kiểu số của chúng. Đây thường là những ngôn ngữ rất năng động trong đó mọi thứ là một đối tượng. Ở đây, hiệu suất là một sự xem xét thứ yếu đối với sự thanh lịch về mặt khái niệm, nhưng những ngôn ngữ đó thường không được sử dụng để xử lý số. Tuy nhiên, các ngôn ngữ này có thể có một trình tối ưu hóa có thể giúp unbox unbox hoạt động trên các nguyên thủy.


Với những cân nhắc kỹ thuật ngoài lề, chúng ta có thể xem xét liệu một giao diện toán học dựa trên phương pháp như vậy có phải là một giao diện tốt hay không. Hai vấn đề phát sinh:

  • ký hiệu toán học dựa trên các toán tử và hàm, không dựa trên các phương thức. Một biểu thức như 42.sqrtsẽ xuất hiện xa lạ hơn đối với nhiều người dùng hơn sqrt(42). Là một người dùng nặng về toán học, tôi thích khả năng tạo các toán tử của riêng mình hơn cú pháp gọi phương thức dot.
  • Nguyên tắc Trách nhiệm duy nhất khuyến khích chúng ta giới hạn số lượng hoạt động là một phần của loại đối với các hoạt động thiết yếu. So với phép nhân, bạn cần căn bậc hai hiếm khi. Nếu ngôn ngữ của bạn được thiết kế đặc biệt cho anlysis thống kê, sau đó cung cấp thêm nguyên thủy (như hoạt động mean, median, variance, std, normalizetrên danh sách số, hoặc hàm Gamma cho số) có thể hữu ích. Đối với một ngôn ngữ có mục đích chung, điều này chỉ làm nặng giao diện. Việc đưa các hoạt động không thiết yếu vào một không gian tên riêng biệt làm cho kiểu này dễ truy cập hơn đối với phần lớn người dùng.

Python là một ví dụ điển hình của ngôn ngữ mọi thứ là một đối tượng được sử dụng để xử lý số lượng lớn. Vô hướng NumPy thực sự có hàng tá và hàng tá phương thức, nhưng sqrt vẫn không phải là một trong số chúng. Hầu hết trong số chúng là những thứ giống như transposemeanchỉ có ở đó để cung cấp một giao diện thống nhất với các mảng NumPy, đó là cấu trúc dữ liệu phù hợp thực sự.
user2357112 hỗ trợ Monica

7
@ user2357112: Vấn đề là, chính NumPy được viết bằng hỗn hợp C và Cython, với một số keo Python. Nếu không thì nó không bao giờ có thể nhanh như vậy được.
Kevin

1
Tôi nghĩ rằng câu trả lời này đánh khá sát vào một số loại thỏa hiệp trong thế giới thực đã đạt được qua nhiều năm thiết kế. Trong các tin tức khác, nó thực sự có ý nghĩa trong .Net để có thể thực hiện "Hello World" .Max () vì các tiện ích mở rộng LINQ cho phép chúng tôi rất dễ nhìn thấy trong Intellisense. Điểm thưởng: Kết quả là gì? Tiền thưởng, kết quả trong Unicode là gì ...?
Andyz Smith

15

Tôi sẽ bị thúc đẩy bởi thực tế là có rất nhiều hàm toán học có mục đích đặc biệt, thay vì tập hợp mọi loại toán với tất cả (hoặc một tập hợp con ngẫu nhiên) của các hàm mà bạn đặt chúng vào một lớp tiện ích. Mặt khác, bạn sẽ làm ô nhiễm công cụ hoàn thành tự động của mình hoặc bạn buộc mọi người phải luôn nhìn ở hai nơi. (Là sinđủ quan trọng để trở thành thành viên của Double, hoặc là trong Mathlớp cùng với các con lai như htanexp1p?)

Một lý do thực tế khác là hóa ra có thể có nhiều cách khác nhau để thực hiện các phương pháp số, với hiệu suất và sự đánh đổi chính xác khác nhau. Java có Math, và nó cũng có StrictMath.


Tôi hy vọng các nhà thiết kế ngôn ngữ không quan tâm đến các công cụ tự động hoàn thành. Cũng như, những gì xảy ra trên Math.<^space>? Tooltip tự động hoàn thành đó cũng sẽ bị ô nhiễm. Ngược lại, tôi nghĩ đoạn thứ hai của bạn có lẽ là một trong những câu trả lời tốt hơn ở đây.
Qix

@Qix Họ làm. Mặc dù, những người khác ở đây có thể gọi nó là "làm cho một giao diện cồng kềnh."
Alexanderr Dubinsky

6

Bạn đã quan sát chính xác rằng có một đối xứng tò mò đang chơi ở đây.

Cho dù tôi nói sqrt(n)hay n.sqrt()không thực sự quan trọng, cả hai đều thể hiện cùng một điều và điều bạn thích là vấn đề sở thích cá nhân hơn bất cứ điều gì khác.

Đó cũng là lý do tại sao có một lập luận mạnh mẽ từ các nhà thiết kế ngôn ngữ nhất định để làm cho hai cú pháp có thể hoán đổi cho nhau. Ngôn ngữ lập trình D đã cho phép điều này trong một tính năng gọi là Cú pháp gọi chức năng thống nhất . Một tính năng tương tự cũng đã được đề xuất để chuẩn hóa trong C ++ . Như Mark Amery chỉ ra trong các bình luận , Python cũng cho phép điều này.

Điều này không phải là không có vấn đề. Giới thiệu một thay đổi cú pháp cơ bản như thế này có hậu quả trên phạm vi rộng đối với mã hiện có và tất nhiên cũng là một chủ đề thảo luận gây tranh cãi giữa các nhà phát triển đã được đào tạo trong nhiều thập kỷ để nghĩ về hai cú pháp như mô tả những điều khác nhau.

Tôi đoán chỉ có thời gian sẽ cho biết liệu sự hợp nhất của hai người có khả thi trong thời gian dài hay không, nhưng đó chắc chắn là một sự cân nhắc thú vị.


Python đã hỗ trợ cả hai cú pháp này. Mọi phương thức không tĩnh lấy selftham số đầu tiên của nó và khi bạn gọi phương thức đó là một thuộc tính của một thể hiện, thay vì là một thuộc tính của lớp, thì thể hiện đó được truyền ngầm làm đối số đầu tiên. Do đó tôi có thể viết "foo".startswith("f")hoặc str.startswith("foo", "f"), và tôi có thể viết my_list.append(x)hoặc list.append(my_list, x).
Đánh dấu Amery

@MarkAmery Điểm tốt. Điều này không hoàn toàn quyết liệt như những gì đề xuất của D hoặc C ++, nhưng nó phù hợp với ý tưởng chung. Cảm ơn đã chỉ ra!
ComicSansMS

3

Ngoài câu trả lời của D Stanley bạn phải nghĩ về đa hình. Các phương thức như Math.Sqrt phải luôn trả về cùng một giá trị cho cùng một đầu vào. Làm cho phương thức tĩnh là một cách tốt để làm rõ điểm này, vì các phương thức tĩnh không bị quá tải.

Bạn đã đề cập đến phương thức ToString () -. Ở đây bạn có thể muốn ghi đè phương thức này, vì vậy lớp (phụ) được biểu diễn theo một cách khác là Chuỗi làm lớp cha của nó. Vì vậy, bạn làm cho nó một phương pháp ví dụ.


2

Vâng, trong Java có một trình bao bọc cho mọi loại cơ bản.
Và các kiểu cơ bản không phải là kiểu lớp và không có hàm thành viên.

Vì vậy, bạn có các lựa chọn sau:

  1. Thu thập tất cả các hàm trợ giúp vào một lớp pro-forma như thế nào Math.
  2. Làm cho nó một hàm tĩnh trên trình bao bọc tương ứng.
  3. Làm cho nó thành một hàm thành viên trên trình bao bọc tương ứng.
  4. Thay đổi các quy tắc của Java.

Hãy loại bỏ tùy chọn 4 ra, bởi vì ... Java là Java và các tín đồ tuyên bố thích nó theo cách đó.

Bây giờ, chúng ta cũng có thể loại trừ lựa chọn 3 vì trong khi phân bổ các đối tượng là khá rẻ, nó không phải là miễn phí, và làm điều đó hơn và hơn nữa không thêm lên.

Hai xuống, một vẫn để giết: Tùy chọn 2 cũng là một ý tưởng tồi, bởi vì điều đó có nghĩa là mọi chức năng phải được thực hiện cho mọi loại, người ta không thể dựa vào việc mở rộng chuyển đổi để lấp đầy các khoảng trống, hoặc sự không nhất quán sẽ thực sự bị tổn thương.
Và nhìn vào java.lang.Math, có rất nhiều khoảng trống, đặc biệt là đối với các loại nhỏ hơn inttương ứng double.

Vì vậy, cuối cùng, kẻ chiến thắng rõ ràng là lựa chọn thứ nhất, thu thập tất cả chúng ở một nơi trong lớp chức năng tiện ích.

Quay trở lại tùy chọn 4, một cái gì đó theo hướng đó thực sự đã xảy ra sau đó: Bạn có thể yêu cầu trình biên dịch xem xét tất cả các thành viên tĩnh của bất kỳ lớp nào bạn muốn khi giải quyết tên trong một thời gian khá dài. import static someclass.*;

Bên cạnh đó, các ngôn ngữ khác không gặp phải vấn đề đó, vì chúng không có thành kiến ​​đối với các chức năng miễn phí (tùy chọn sử dụng không gian tên) hoặc ít hơn các loại nhỏ.


1
Xem xét niềm vui của việc thực hiện các biến thể trên Math.min()trong tất cả các loại trình bao bọc.

Tôi thấy # 4 không thuyết phục. Math.sqrt()được tạo ra cùng lúc với phần còn lại của Java, vì vậy khi quyết định đưa sqrt () vào Mathđó không có quán tính lịch sử của những người dùng Java "thích nó theo cách đó". Mặc dù không có nhiều vấn đề với sqrt(), hành vi quá tải của Math.round()nó là tàn bạo. Có thể sử dụng cú pháp thành viên với các giá trị loại floatdoublesẽ tránh được vấn đề đó.
supercat

2

Một điểm mà tôi không thấy được đề cập rõ ràng (mặc dù amon ám chỉ nó) là căn bậc hai có thể được coi là một hoạt động "có nguồn gốc": nếu việc triển khai không cung cấp cho chúng tôi, chúng tôi có thể tự viết.

Vì câu hỏi được gắn thẻ với thiết kế ngôn ngữ, chúng tôi có thể xem xét một số mô tả không biết ngôn ngữ. Mặc dù nhiều ngôn ngữ có những triết lý khác nhau, nhưng việc sử dụng đóng gói để bảo tồn bất biến là điều rất phổ biến; tức là để tránh có một giá trị không hoạt động như kiểu của nó sẽ gợi ý.

Ví dụ: nếu chúng tôi thực hiện một số số nguyên bằng cách sử dụng các từ máy, có lẽ chúng tôi muốn đóng gói biểu diễn bằng cách nào đó (ví dụ: để ngăn dịch chuyển bit thay đổi dấu hiệu), nhưng đồng thời chúng tôi vẫn cần truy cập vào các bit đó để thực hiện các hoạt động như thêm vào.

Một số ngôn ngữ có thể thực hiện điều này với các lớp và phương thức riêng tư:

class Int {
    public Int add(Int x) {
      // Do something with the bits
    }
    private List<Boolean> getBits() {
      // ...
    }
}

Một số hệ thống mô-đun:

signature INT = sig
  type int
  val add : int -> int -> int
end

structure Word : INT = struct
  datatype int  = (* ... *)
  fun add x y   = (* Do something with the bits *)
  fun getBits x = (* ... *)
end

Một số có phạm vi từ vựng:

(defun getAdder ()
   (let ((getBits (lambda (x) ; ...
         (add     (lambda (x y) ; Do something with the bits
     'add))

Và như vậy. Tuy nhiên, không ai trong số các cơ chế này là cần thiết cho việc thực hiện căn bậc hai: nó có thể được thực hiện bằng cách sử dụng công cộng giao diện của một số loại, và do đó nó không cần phải truy cập vào các chi tiết thực hiện đóng gói.

Do đó, vị trí của căn bậc hai đi xuống triết lý / thị hiếu của ngôn ngữ và của người thiết kế thư viện. Một số có thể chọn để đặt nó "bên trong" các giá trị số (ví dụ như làm cho nó một phương pháp chẳng hạn), một số có thể chọn để đặt nó ở mức độ tương tự như các hoạt động nguyên thủy (điều này có thể có nghĩa là một phương pháp dụ, hoặc nó có thể có nghĩa là sống bên ngoài các các giá trị số, nhưng bên trong cùng một mô-đun / lớp / không gian tên, ví dụ như một hàm độc lập hoặc phương thức tĩnh), một số có thể chọn đưa nó vào một tập hợp các hàm "trợ giúp", một số có thể chọn ủy thác nó cho các thư viện của bên thứ ba.


Không có gì có thể ngăn ngôn ngữ cho phép gọi một phương thức tĩnh bằng cách sử dụng cú pháp thành viên (như với các phương thức mở rộng C # hoặc vb.net) hoặc với một biến thể của chuỗi thành viên (vì vậy tôi muốn thấy một cú pháp hai chấm, vì vậy như để cho phép các lợi thế của Intellisense khi chỉ có thể liệt kê các hàm phù hợp với đối số chính, nhưng tránh sự mơ hồ với các toán tử thành viên thực sự).
supercat

-2

Trong Java và C # ToString là một phương thức của đối tượng, gốc của hệ thống phân cấp lớp, vì vậy mọi đối tượng sẽ thực hiện phương thức ToString. Đối với một Integertype, việc triển khai ToString sẽ hoạt động theo cách này là điều tự nhiên.

Vì vậy, bạn suy luận là sai. Các loại giá trị lý do triển khai ToString không phải là một số người giống như: hey hãy có phương thức ToString cho các loại Giá trị. Đó là bởi vì ToString đã có sẵn và đó là điều "tự nhiên nhất" để xuất ra.


1
Tất nhiên đó là một quyết định, tức là quyết định objectcó một ToString()phương pháp. Đó là trong lời nói của bạn "một số người giống như: hey hãy có phương pháp ToString cho các loại Giá trị".
Residuum

-3

Không giống như String.sub chuỗi, Number.sqrt không thực sự là một thuộc tính của số mà là kết quả mới dựa trên số của bạn. Tôi nghĩ rằng việc chuyển số của bạn cho một hàm bình phương là trực quan hơn.

Hơn nữa, đối tượng Math chứa các thành viên tĩnh khác và sẽ hợp lý hơn khi kết hợp chúng lại với nhau và sử dụng chúng một cách thống nhất.


3
Ví dụ về bộ đếm: BigInteger.pow () .
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.