Chức năng quá tải? Có hoặc không [đóng]


16

Tôi đang phát triển một ngôn ngữ được biên dịch, đánh máy tĩnh và mạnh mẽ và tôi đang xem xét lại ý tưởng về việc có nên đưa quá tải chức năng làm tính năng ngôn ngữ hay không. Tôi nhận ra rằng tôi hơi thiên vị một chút, chủ yếu đến từ một C[++|#]nền tảng.

Các đối số thuyết phục nhất chochống lại bao gồm quá tải chức năng trong một ngôn ngữ là gì?


EDIT: Có ai có ý kiến ​​trái ngược không?

Bertrand Meyer (người tạo ra Eiffel trở lại vào năm 1985/1986) gọi phương thức này làm quá tải điều này: (nguồn)

một cơ chế phù phiếm không mang lại gì cho sức mạnh ngữ nghĩa của ngôn ngữ OO, nhưng cản trở khả năng đọc và làm phức tạp nhiệm vụ của mọi người

Bây giờ đó là một số khái quát sâu rộng, nhưng anh ấy là một người thông minh, vì vậy tôi nghĩ rằng anh ấy có thể sao lưu chúng nếu cần. Trên thực tế, anh ta gần như đã có Brad Abrams (một trong những nhà phát triển CLSv1) thuyết phục rằng .NET không nên hỗ trợ quá tải phương thức. (nguồn) Đó là một số thứ mạnh mẽ. Bất cứ ai cũng có thể làm sáng tỏ suy nghĩ của mình, và liệu quan điểm của anh ấy có còn hợp lý 25 năm sau không?

Câu trả lời:


24

Quá tải chức năng là cực kỳ quan trọng đối với mã mẫu kiểu C ++. Nếu tôi phải sử dụng các tên hàm khác nhau cho các loại khác nhau, tôi không thể viết mã chung. Điều đó sẽ loại bỏ một phần lớn và được sử dụng nhiều trong thư viện C ++ và phần lớn chức năng của C ++.

Nó thường xuất hiện trong tên hàm thành viên. A.foo()có thể gọi một hàm hoàn toàn khác với B.foo(), nhưng cả hai hàm đều được đặt tên foo. Nó hiện diện trong các toán tử, cũng như +những thứ khác nhau khi được áp dụng cho các số nguyên và số dấu phẩy động, và nó thường được sử dụng như một toán tử nối chuỗi. Có vẻ kỳ lạ khi không cho phép nó trong các chức năng thông thường là tốt.

Nó cho phép sử dụng "multimethods" kiểu Lisp thông thường, trong đó hàm chính xác được gọi phụ thuộc vào hai loại dữ liệu. Nếu bạn chưa lập trình trong Hệ thống đối tượng Lisp chung, hãy thử nó trước khi bạn gọi nó là vô dụng. Nó rất quan trọng đối với các luồng C ++.

I / O mà không bị quá tải hàm (hoặc các hàm matrixdic, tệ hơn) sẽ yêu cầu một số hàm khác nhau, để in các giá trị của các loại khác nhau hoặc để chuyển đổi các giá trị của các loại khác nhau thành một loại chung (như Chuỗi).

Không có quá tải hàm, nếu tôi thay đổi loại của một số biến hoặc giá trị, tôi cần thay đổi mọi hàm sử dụng nó. Nó làm cho mã tái cấu trúc khó hơn nhiều.

Nó giúp sử dụng API dễ dàng hơn khi người dùng không phải nhớ quy ước đặt tên loại nào đang được sử dụng và người dùng chỉ có thể nhớ tên hàm tiêu chuẩn.

Nếu không quá tải toán tử, chúng ta sẽ phải gắn nhãn cho từng chức năng với các loại nó sử dụng, nếu hoạt động cơ sở đó có thể được sử dụng trên nhiều loại. Đây thực chất là ký hiệu Hungary, cách làm xấu.

Nhìn chung, nó làm cho một ngôn ngữ có thể sử dụng nhiều hơn.


1
+1, tất cả các điểm rất tốt. Và hãy tin tôi, tôi không nghĩ rằng nhiều hình ảnh là vô dụng ... Tôi nguyền rủa chính bàn phím tôi gõ mỗi khi tôi buộc phải sử dụng mẫu khách truy cập.
Lưu ý đến bản thân - nghĩ về một cái tên

Có phải anh chàng này mô tả chức năng ghi đè và không quá tải?
dragosb

8

Tôi khuyên bạn ít nhất nên biết về các lớp loại trong Haskell. Các lớp loại được tạo ra là một cách tiếp cận có kỷ luật đối với quá tải toán tử, nhưng đã tìm thấy các cách sử dụng khác, và ở một mức độ nào đó, đã tạo ra Haskell nó là gì.

Ví dụ, đây là một ví dụ về quá tải ad-hoc (Haskell không hoàn toàn hợp lệ):

(==) :: Int -> Int -> Bool
x == y = ...
x /= y = not (x == y)

(==) :: Char -> Char -> Bool
x == y = ...
x /= y = not (x == y)

Và đây là ví dụ tương tự về quá tải với các lớp loại:

class Eq a where
    (==) :: a -> a -> Bool
    (/=) :: a -> a -> Bool

    x /= y  =  not (x == y)

instance Eq Int where
    x == y  = ...

instance Eq Char where
    x == y  = ...

Một nhược điểm của việc này là bạn phải đưa ra tên sôi nổi cho tất cả typeclasses của bạn (như trong Haskell, bạn có khá trừu tượng Monad, Functor, Applicativecũng như đơn giản và dễ nhận biết hơn Eq, NumOrd).

Một nhược điểm là, một khi bạn đã quen thuộc với một kiểu chữ, bạn sẽ biết cách sử dụng bất kỳ loại nào trong lớp đó. Ngoài ra, thật dễ dàng để bảo vệ các chức năng khỏi các loại không triển khai các lớp cần thiết, như trong:

group :: (Eq a) => [a] -> [[a]]
group = groupBy (==)

Chỉnh sửa: Trong Haskell, nếu bạn muốn một ==toán tử chấp nhận hai loại khác nhau, bạn có thể sử dụng lớp loại đa tham số:

class Eq a b where
    (==) :: a -> b -> Bool
    (/=) :: a -> b -> Bool

    x /= y  =  not (x == y)

instance Eq Int Int where
    x == y  = ...

instance Eq Char Char where
    x == y  = ...

instance Eq Int Float where
    x == y  = ...

Tất nhiên, đây có lẽ là một ý tưởng tồi, vì nó rõ ràng cho phép bạn so sánh táo và cam. Tuy nhiên, bạn có thể muốn xem xét điều này +vì việc thêm một Word8vào Intthực sự là một điều hợp lý để làm trong một số bối cảnh.


+1, tôi đã cố gắng nắm bắt khái niệm này kể từ lần đầu tiên đọc nó và điều này giúp ích. Liệu mô hình này làm cho nó không thể xác định, ví dụ, (==) :: Int -> Float -> Boolbất cứ nơi nào? (tất nhiên cho dù đó là một ý tưởng tốt, tất nhiên)
Lưu ý đến bản thân - nghĩ về một cái tên

Nếu bạn cho phép các lớp loại đa tham số (mà Haskell hỗ trợ như một phần mở rộng), bạn có thể. Tôi đã cập nhật câu trả lời với một ví dụ.
Joey Adams

Hừm, thú vị. Vì vậy, về cơ bản, được class Eq a ...dịch sang giả C-họ sẽ là interface Eq<A> {bool operator==(A x, A y);}, và thay vì sử dụng mã templated để so sánh các đối tượng tùy ý, bạn sử dụng 'giao diện' này. Có đúng không?
Lưu ý đến bản thân - nghĩ về một cái tên

Đúng. Bạn cũng có thể muốn xem nhanh các giao diện trong Go. Cụ thể, bạn không phải khai báo một loại là thực hiện giao diện, bạn chỉ cần thực hiện tất cả các phương thức cho giao diện đó.
Joey Adams

1
@ Notetoself-thinkofaname: Về các nhà khai thác bổ sung - có và không. Nó cho phép ==ở trong một không gian tên khác nhưng không cho phép ghi đè lên nó. Xin lưu ý rằng có một không gian tên duy nhất ( Prelude) được bao gồm theo mặc định nhưng bạn có thể ngăn tải nó bằng cách sử dụng các phần mở rộng hoặc bằng cách nhập nó một cách rõ ràng ( import Prelude ()sẽ không nhập gì từ Preludeimport qualified Prelude as Psẽ không chèn các ký hiệu vào không gian tên hiện tại).
Maciej Piechotka

4

Cho phép quá tải chức năng, bạn không thể thực hiện các thao tác sau với các tham số tùy chọn (hoặc nếu bạn có thể, không độc đáo).

ví dụ tầm thường giả định không có base.ToString() phương pháp

string ToString(int i) {}
string ToString(double d) {}
string ToString(DateTime d) {}
...

Đối với một ngôn ngữ đánh máy mạnh mẽ, có. Đối với một ngôn ngữ gõ yếu, không. Bạn có thể làm như trên chỉ với một chức năng trong một ngôn ngữ được gõ yếu.
spex

2

Tôi luôn thích các tham số mặc định hơn là quá tải chức năng. Các hàm quá tải thường chỉ gọi một phiên bản "mặc định" với các tham số mặc định. Tại sao lại viết

int indexOf(char ch)
{
  return self.indexOf(ch, 0);
}

int indexOf(char ch, int fromIndex)
{
  // Do whatever
}

Khi tôi có thể làm:

int indexOf(char ch, int fromIndex=0)
{
  // Do whatever
}

Điều đó nói rằng, tôi nhận ra đôi khi các hàm quá tải làm những việc khác nhau, thay vì chỉ gọi một biến thể khác với các tham số mặc định ... nhưng trong trường hợp như vậy, đó không phải là một ý tưởng tồi (thực tế, có lẽ đó là một ý tưởng tốt ) chỉ cung cấp cho nó một tên khác.

(Ngoài ra, các đối số từ khóa kiểu Python hoạt động thực sự độc đáo với các tham số mặc định.)


Được rồi, chúng ta hãy thử lại lần nữa và lần này tôi sẽ cố gắng hiểu ... Thế còn Array Slice(int start, int length) {...}quá tải Array Slice(int start) {return this.Slice(start, this.Count - start);}thì sao? Điều đó không thể được mã hóa bằng các tham số mặc định. Bạn có nghĩ rằng họ nên được đặt tên khác nhau? Nếu vậy, bạn sẽ đặt tên cho chúng như thế nào?
Lưu ý đến bản thân - nghĩ về một cái tên

Điều đó không giải quyết tất cả việc sử dụng quá tải.
MetalMikester

@MetalMikester: Bạn cảm thấy mình đã bỏ lỡ những gì?
mipadi

@mipadi Nó bỏ lỡ những thứ như indexOf(char ch)+ indexOf(Date dt)trong danh sách. Tôi cũng thích các giá trị mặc định nhưng chúng không thể thay thế cho nhau bằng cách gõ tĩnh.
Đánh dấu

2

Bạn vừa mô tả Java. Hoặc C #.

Tại sao bạn lại phát minh lại bánh xe?

Hãy chắc chắn rằng kiểu trả về là một phần của chữ ký phương thức và Quá tải nội dung trái tim của bạn, nó thực sự làm sạch mã khi bạn không cần phải nói.

function getThisFirstWay(int type)
{ ... }
function getThisSecondWay(int type, double limit)
{ ... }
function getThisThirdWay(int type, String match)
{ ... }

7
Có một lý do tại sao loại trả về không phải là một phần của chữ ký phương thức - hoặc ít nhất không phải là một phần có thể được sử dụng để giải quyết quá tải - trong bất kỳ ngôn ngữ nào tôi biết. Khi bạn gọi một hàm là một thủ tục, mà không gán kết quả cho một biến hoặc thuộc tính, trình biên dịch sẽ tìm ra phiên bản nào để gọi nếu tất cả các đối số khác giống hệt nhau?
Mason Wheeler

@Mason: Bạn có thể phát hiện một cách tự nhiên loại lợi nhuận dự kiến ​​dựa trên những gì được mong đợi sẽ được trả lại, tuy nhiên tôi không hy vọng điều đó sẽ được thực hiện.
Josh K

1
Umm ... làm thế nào heuristic của bạn biết những gì được mong đợi sẽ được trả lại khi bạn không mong đợi bất kỳ giá trị trả lại nào?
Mason Wheeler

1
Các heuristic loại kỳ vọng thực sự được đặt ra ... bạn có thể làm những việc như thế EnumX.Flag1 | Flag2 | Flag3. Tôi sẽ không thực hiện điều này, mặc dù. Nếu tôi đã làm và loại trả lại không được sử dụng, tôi sẽ tìm loại trả về void.
Lưu ý đến bản thân - nghĩ về một cái tên

1
@Mason: Đó là một câu hỏi hay, nhưng trong trường hợp đó tôi sẽ tìm một hàm void (như đã đề cập). Ngoài ra về lý thuyết, bạn có thể chọn bất kỳ trong số chúng, bởi vì tất cả chúng sẽ thực hiện cùng một chức năng, chỉ cần trả về dữ liệu ở định dạng khác.
Josh K

2

Grrr .. chưa đủ đặc quyền để bình luận ..

@Mason Wheeler: Hãy biết về Ada, hiện không quá tải đối với loại trả về. Ngoài ra, ngôn ngữ của tôi, Felix cũng thực hiện nó trong một số ngữ cảnh, cụ thể, khi một hàm trả về một hàm khác và có một cuộc gọi như:

f a b  // application is left assoc: (f a) b

loại b có thể được sử dụng để giải quyết quá tải. Ngoài ra C ++ quá tải về kiểu trả về trong một số trường hợp:

int (*f)(int) = g; // choses g based on type, not just signature

Trong thực tế, có các thuật toán cho quá tải về kiểu trả về, sử dụng suy luận kiểu. Nó thực sự không khó để làm với một cái máy, vấn đề là con người cảm thấy khó khăn. (Tôi nghĩ rằng phác thảo được đưa ra trong Sách Rồng, thuật toán được gọi là thuật toán cưa nếu tôi nhớ chính xác)


2

Ca sử dụng chống lại việc thực thi nạp chồng hàm: 25 phương thức giống như loại thực hiện cùng một việc nhưng với các nhóm đối số hoàn toàn khác nhau trong một loạt các mẫu.

Ca sử dụng chống lại việc không thực hiện nạp chồng hàm: 5 phương thức được đặt tên tương tự với các bộ kiểu rất giống nhau trong cùng một mẫu chính xác.

Vào cuối ngày, tôi không mong được đọc tài liệu cho một API được sản xuất trong cả hai trường hợp.

Nhưng trong một trường hợp, đó là về những gì người dùng có thể làm. Trong trường hợp khác, đó là những gì người dùng phải làm vì hạn chế ngôn ngữ. IMO, tốt nhất là ít nhất cho phép khả năng các tác giả chương trình đủ thông minh để quá tải một cách hợp lý mà không tạo ra sự mơ hồ. Khi bạn vỗ tay họ và đưa tùy chọn đi, về cơ bản bạn sẽ đảm bảo sự mơ hồ sẽ xảy ra. Tôi quan tâm đến việc tin tưởng người dùng làm điều đúng hơn là cho rằng họ sẽ luôn làm sai. Theo kinh nghiệm của tôi, chủ nghĩa bảo hộ có xu hướng dẫn đến hành vi thậm chí còn tồi tệ hơn đối với cộng đồng ngôn ngữ.


1

Tôi đã chọn cung cấp các loại lớp quá tải thông thường và nhiều loại trong ngôn ngữ của mình.

Tôi coi (mở) quá tải là điều cần thiết, đặc biệt là trong một ngôn ngữ có rất nhiều kiểu số (Felix có tất cả các kiểu số C). Tuy nhiên, không giống như C ++ lạm dụng quá tải bằng cách tạo các mẫu phụ thuộc vào nó, tính đa hình của Felix là tham số: bạn cần nạp chồng cho các mẫu trong C ++ vì các mẫu trong C ++ được thiết kế xấu.

Các loại lớp cũng được cung cấp trong Felix. Đối với những người biết C ++ nhưng không tìm hiểu Haskell, hãy bỏ qua những người mô tả nó là quá tải. Nó không giống như quá tải, thay vào đó, nó giống như chuyên môn hóa mẫu: bạn khai báo một mẫu mà bạn không triển khai, sau đó cung cấp các triển khai cho các trường hợp cụ thể khi bạn cần chúng. Việc gõ là đa hình tham số, việc thực hiện là bằng cách khởi tạo ad hoc nhưng nó không có ý định không bị ràng buộc: nó phải thực hiện các ngữ nghĩa dự định.

Trong Haskell (và C ++), bạn không thể nêu ra ngữ nghĩa. Trong C ++, ý tưởng "Khái niệm" đại khái là một nỗ lực để gần đúng ngữ nghĩa. Trong Felix, bạn có thể tính gần đúng ý định với các tiên đề, rút ​​gọn, bổ đề và định lý.

Ưu điểm chính và duy nhất của quá tải (mở) trong một ngôn ngữ có nguyên tắc tốt như Felix là nó giúp dễ nhớ các tên hàm thư viện hơn, cho cả người viết chương trình và cho người xem mã.

Nhược điểm chính của quá tải là thuật toán phức tạp cần thiết để thực hiện nó. Nó cũng không phù hợp lắm với suy luận kiểu: mặc dù cả hai không hoàn toàn độc quyền, thuật toán để thực hiện cả hai đều đủ phức tạp để lập trình viên có thể không dự đoán được kết quả.

Trong C ++, đây cũng là một vấn đề vì nó có thuật toán so khớp cẩu thả và cũng hỗ trợ chuyển đổi loại tự động: trong Felix tôi đã "khắc phục" vấn đề này bằng cách yêu cầu khớp chính xác và không có chuyển đổi loại tự động.

Vì vậy, bạn có một sự lựa chọn tôi nghĩ: quá tải hoặc loại suy luận. Suy luận là dễ thương, nhưng cũng rất khó để thực hiện theo cách chẩn đoán chính xác các xung đột. Ocaml, ví dụ, cho bạn biết nơi nó phát hiện xung đột, nhưng không phải là nơi nó suy ra loại dự kiến.

Quá tải không tốt hơn nhiều, ngay cả khi bạn có một trình biên dịch chất lượng cố gắng cho bạn biết tất cả các ứng cử viên, thật khó để đọc nếu các ứng cử viên là đa hình, và thậm chí tệ hơn nếu đó là tin tặc mẫu C ++.


Nghe có vẻ thú vị. Tôi muốn đọc thêm, nhưng liên kết đến các tài liệu trên trang web của Felix đã bị hỏng.
Lưu ý đến bản thân - nghĩ về một cái tên

Có, toàn bộ trang web hiện đang được xây dựng (một lần nữa), xin lỗi.
Yttrill

0

Đi vào bối cảnh, nhưng tôi nghĩ rằng quá tải làm cho một lớp hữu ích hơn nhiều khi tôi đang sử dụng một lớp được viết bởi người khác. Bạn thường kết thúc với ít dư thừa.


0

Nếu bạn đang muốn có những người dùng quen thuộc với các ngôn ngữ trong gia đình C thì có, vì bạn nên mong đợi vì người dùng của bạn sẽ mong đợi nó.

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.