Функція перевантаження? Так чи ні [закрито]


17

Я розробляю статично і сильно набраний, складений мову, і я переглядаю ідею, чи включати перевантаження функцій в якості мовної функції. Я зрозумів, що я трохи упереджений, іду в основному з C[++|#]фону.

Які найбільш переконливі аргументи « за» і « проти», включаючи перевантаження функцій мовою?


EDIT: Чи немає нікого, хто має протилежну думку?

Бертран Мейер (творець Ейфеля ще в 1985/1986 рр.) Називає метод, який перевантажує це: (джерело)

механізм марнославства, який нічого не приносить семантичній силі мови ОО, але перешкоджає читанню та ускладнює завдання кожного

Зараз це кілька химерних узагальнень, але він розумний хлопець, тому я думаю, що можна з упевненістю сказати, що він міг би їх створити, якщо потрібно. Насправді він майже переконав Бреда Абрамса (одного з розробників CLSv1), що .NET не повинен підтримувати перевантаження методу. (джерело) Це деякі потужні речі. Чи може хтось пролити світло на його думки, і чи виправдана його точка зору через 25 років?

Відповіді:


24

Перевантаження функцій абсолютно важливо для коду шаблону стилю C ++. Якщо мені доведеться використовувати різні назви функцій для різних типів, я не можу записати загальний код. Це усуне значну частину бібліотеки C ++ та значну частину функціоналу C ++.

Зазвичай він присутній у назвах функцій членів. A.foo()може викликати зовсім іншу функцію від B.foo(), але обидві функції названі foo. Він присутній в операторах, як +і різні речі при застосуванні до цілих чисел і чисел з плаваючою комою, і часто використовується як оператор конкатенації рядків. Мабуть, дивно не допускати цього і в регулярних функціях.

Це дає змогу використовувати загальні «мультиметоди» у стилі Lisp, у яких точна названа функція залежить від двох типів даних. Якщо ви не запрограмовані в загальній системі об'єктів Lisp, спробуйте його, перш ніж називати це марним. Це життєво важливо для потоків C ++.

Введення / виведення без перевантаження функцій (або варіативні функції, які гірші) потребують декількох різних функцій: або для друку значень різних типів, або для перетворення значень різних типів у загальний тип (наприклад, String).

Без перевантаження функцій, якщо я змінюю тип якоїсь змінної або значення, мені потрібно змінити кожну функцію, яка її використовує. Це значно ускладнює код рефактора.

Це полегшує використання API, коли користувачеві не потрібно пам’ятати, який тип іменування типів використовується, і користувач може просто запам'ятати стандартні імена функцій.

Без перевантаження оператора нам доведеться позначати кожну функцію типами, які вона використовує, якщо базова операція може бути використана для більш ніж одного типу. Це по суті угорська позначення, поганий спосіб зробити це.

Загалом це робить мову набагато більш зручною.


1
+1, всі дуже хороші бали. І повірте, я не вважаю, що багатометоди є марними ... Я проклинаю ту саму клавіатуру, яку я набираю кожен раз, коли я змушений використовувати шаблон відвідувачів.
Зауважте, що самостійно - придумайте ім’я

Чи не цей хлопець, що описує функцію, переосмислює і не перевантажує?
dragosb

8

Я рекомендую принаймні бути обізнаними про типи класів у Haskell. Класи типів були створені для дисциплінованого підходу до перевантаження оператора, але вони знайшли інші види використання, і в деякій мірі зробили Haskell таким, яким він є.

Наприклад, ось приклад тимчасової перевантаження (не зовсім дійсний Haskell):

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

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

І ось той самий приклад перевантаження класами типів:

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  = ...

Недолік цього є те, що ви повинні придумати кумедні імена для всіх класів типів (наприклад , в Haskell, у вас є досить абстрактно Monad, Functor, Applicativeа також більш простий і відомий Eq, Numі Ord).

Переваги полягають у тому, що після ознайомлення з класом типу ви знаєте, як використовувати будь-який тип у цьому класі. Крім того, легко захистити функції від типів, які не реалізують необхідні класи, як у:

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

Редагувати: В Haskell, якщо ви хотіли, щоб ==оператор приймав два різні типи, ви могли б використовувати клас багатопараметричних типів:

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  = ...

Звичайно, це, мабуть, погана ідея, оскільки явно дозволяє порівнювати яблука та апельсини. Однак, можливо, ви захочете це врахувати +, оскільки додавання Word8до Intсправді є розумною справою в деяких контекстах.


+1, я намагаюся зрозуміти це поняття ще з моменту його першого читання, і це допомагає. Чи не дозволяє ця парадигма визначити, наприклад, (==) :: Int -> Float -> Boolдесь? (незалежно від того, чи це гарна ідея, звичайно)
Зауважте, що потрібно самостійно придумати ім’я

Якщо ви дозволите багатопараметричні класи типів (які Haskell підтримує як розширення), ви можете. Я оновив відповідь прикладом.
Joey Adams

Хм, цікаво. Таким чином, class Eq a ...перекладено в сімейство псевдо-С було б interface Eq<A> {bool operator==(A x, A y);}, і замість того, щоб використовувати шаблонний код для порівняння довільних об'єктів, ви використовуєте цей 'інтерфейс'. Це так?
Зауважте, що самостійно - придумайте ім’я

Правильно. Ви також можете скоріше ознайомитися з інтерфейсами в Go. А саме, вам не потрібно декларувати тип як інтерфейс, просто потрібно реалізувати всі методи цього інтерфейсу.
Джої Адамс

1
@ Notetoself-thinkofaname: Щодо додаткових операторів - так і ні. Це дозволяє ==знаходитись в іншому просторі імен, але не дозволяє переосмислити його. Зауважте, що Preludeза замовчуванням є одне простір імен ( ), але ви можете запобігти його завантаженню, використовуючи розширення або явно імпортуючи ( import Prelude ()не імпортуйте нічого з Preludeі import qualified Prelude as Pне вставляйте символи у поточну область імен).
Maciej Piechotka

4

Дозволити перевантаження функцій, ви не можете зробити наступне з додатковими параметрами (або, якщо можете, не дуже).

тривіальний приклад не передбачає base.ToString() методу

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

Для сильно набраної мови, так. Для слабо набраної мови немає. Ви можете зробити вищезазначене лише однією функцією слабко набраною мовою.
spex

2

Я завжди віддав перевагу параметрам за замовчуванням над перевантаженням функції. Перевантажені функції зазвичай просто викликають версію "за замовчуванням" із параметрами за замовчуванням. Навіщо писати

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

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

Коли я міг зробити:

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

З цього приводу я розумію, що іноді перевантажені функції роблять різні речі, а не просто викликають інший варіант із параметрами за замовчуванням ... але в такому випадку це не погана ідея (насправді це, мабуть, гарна ідея) просто дати йому інша назва.

(Також аргументи ключових слів у стилі Python дуже добре працюють із параметрами за замовчуванням.)


Гаразд, спробуємо це ще раз, і я цього разу спробую мати сенс ... Що з Array Slice(int start, int length) {...}перевантаженим Array Slice(int start) {return this.Slice(start, this.Count - start);}? Це неможливо кодувати за допомогою параметрів за замовчуванням. Як ви думаєте, їм слід давати різні імена? Якщо так, як би ви їх назвали?
Зауважте, що самостійно - придумайте ім'я

Це не стосується всіх застосувань перевантаження.
MetalMikester

@MetalMikester: Яке використання ти вважаєш, що я пропустив?
mipadi

@mipadi Не вистачає таких речей, як indexOf(char ch)+ indexOf(Date dt)у списку. Мені також подобаються значення за замовчуванням, але вони не взаємозамінні зі статичним набором тексту.
Марк

2

Ви щойно описали Java. Або C #.

Чому ви винаходите колесо?

Переконайтесь, що тип повернення є частиною методу підпису та Перевантаження вмісту ваших сердець, він дійсно очищає код, коли вам не потрібно говорити.

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

7
Існує причина, чому тип повернення не є частиною підпису методу - або, принаймні, не частиною, яка може бути використана для вирішення перевантаження - будь-якою моєю відомою мені мовою. Коли ви викликаєте функцію як процедуру, не привласнюючи результат змінній чи властивості, як компілятор повинен з'ясувати, до якої версії слід викликати, якщо всі інші аргументи однакові?
Мейсон Уілер

@Mason: Ви можете евристично визначити очікуваний тип повернення на основі того, що очікується повернення, однак я не очікую, що це буде зроблено.
Josh K

1
Гм ... як ваша евристика знає, що очікується повернути, коли ви не очікуєте повернення значення?
Мейсон Уілер

1
Евристика очікування типу насправді є на місці ... ви можете робити такі речі EnumX.Flag1 | Flag2 | Flag3. Я цього не буду реалізовувати. Якщо я це зробив, а тип повернення не використовувався, я б шукав тип повернення void.
Зауважте, що самостійно - придумайте ім’я

1
@ Мейсон: Це гарне запитання, але в такому випадку я б шукав недійсну функцію (як згадувалося). Також теоретично ви можете вибрати будь-який з них, оскільки всі вони будуть виконувати одну і ту ж функцію, просто повертайте дані в іншому форматі.
Josh K

2

Гррр .. недостатньо привілей для коментарів ..

@ Mason Wheeler: Будьте в курсі Ада, що робить перевантаження при поверненні типу. Також моя мова Фелікс робить це теж у деяких контекстах, зокрема, коли функція повертає іншу функцію і відбувається виклик типу:

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

тип b може використовуватися для вирішення перевантаження. Також C ++ перевантажує тип повернення в деяких випадках:

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

Насправді існують алгоритми для перевантаження за типом повернення, використовуючи умовиводи типу. Насправді це не так важко зробити з машиною, проблема полягає в тому, що людям важко. (Я думаю, що контури наведені в Книзі Драконів, алгоритм називається алгоритмом проглядання, якщо я правильно пам’ятаю)


2

Використання випадку проти перевантаження функції: 25 подібних методів, які роблять те саме, але з абсолютно різними наборами аргументів у найрізноманітніших моделях.

Use-case проти нереалізованої функції перевантаження: 5 аналогічно названих методів з дуже схожими наборами типів у точно такій самій схемі.

Зрештою, я не з радістю читаю документи для API, створеного в будь-якому випадку.

Але в одному випадку мова йде про те, що можуть робити користувачі. В іншому випадку користувачі повинні робити це через мовне обмеження. ІМО, краще хоча б дозволити можливість авторів програми бути досить розумними, щоб перевантажувати розумно, не створюючи двозначності. Коли ви плескаєте їх руками і відбираєте варіант, ви в основному гарантуєте двозначність. Я більше довіряю користувачеві зробити правильну справу, ніж припускаю, що вони завжди зроблять неправильну справу. На мій досвід, протекціонізм, як правило, призводить до ще гіршої поведінки з боку мовної спільноти.


1

Я вирішив надати мою мову Felix звичайними класами перевантаження та різнотипними типами.

Я вважаю (відкритим) перевантаження важливим, особливо мовою, яка є численним числовим типом (Фелікс має всі числові типи С). Однак на відміну від C ++, який зловживає перевантаженням, створюючи шаблони залежать від цього, поліморфізм Фелікса є параметричним: вам потрібна перевантаження шаблонів на C ++, оскільки шаблони в C ++ погано розроблені.

Класові типи також передбачені у Felix. Для тих, хто знає C ++, але не захоплюється Haskell, ігноруйте тих, хто описує це як перевантаження. Це не віддалено як перевантаження, скоріше, це як спеціалізація шаблонів: ви оголошуєте шаблон, який ви не реалізуєте, а потім надаєте реалізації для певних випадків, коли вони вам потрібні. Типізація є параметрично поліморфною, реалізація здійснюється за допомогою спеціальної інстанції, але вона не має бути обмеженою: вона повинна реалізувати передбачувану семантику.

У Haskell (і C ++) ви не можете вказати семантику. У C ++ ідея "Концепції" - це орієнтовно спроба апроксимації семантики. У Фелікс ви можете наблизити наміри до аксіом, скорочень, лем і теорем.

Основна, і єдина перевага (відкритої) перевантаження в такій принциповій мові, як Фелікс, полягає в тому, що це полегшує запам’ятовування імен функцій бібліотеки, як для програміста, так і для рецензента коду.

Основним недоліком перевантаження є складний алгоритм, необхідний для його здійснення. Він також не дуже добре відповідає висновкам типу: хоча ці два не зовсім ексклюзивні, алгоритм робити обидва досить складний, програміст, ймовірно, не зможе передбачити результати.

У C ++ це також проблема, оскільки він має неохайний алгоритм узгодження, а також підтримує автоматичні перетворення типів: у Felix I "виправлено" цю проблему, вимагаючи точної відповідності та автоматичних перетворень типу.

Тож у вас є вибір, на який я думаю: перевантаження або введення висновку. Висновок милий, але його також дуже важко реалізувати таким чином, щоб правильно діагностувати конфлікти. Наприклад, Ocaml повідомляє вам, де він виявляє конфлікт, але не звідки він вивів очікуваний тип.

Перевантаження не набагато краще, навіть якщо у вас є якісний компілятор, який намагається повідомити вам усіх кандидатів, важко буде прочитати, чи кандидати поліморфні, а ще гірше, якщо це хакерство шаблонів C ++.


Звучить цікаво. Я хотів би прочитати більше, але посилання на документи на веб-сторінці Felix порушено.
Зауважте, що самостійно - придумайте ім’я

Так, весь сайт наразі будується (знову), вибачте.
Іттріл

0

Зводиться до контексту, але я думаю, що перевантаження робить клас набагато кориснішим, коли я використовую один, написаний кимось іншим. Ви часто стикаєтесь із меншими надмірностями.


0

Якщо ви хочете, щоб користувачі ознайомилися з мовами сімейства C, то так, так як вам слід, тому що ваші користувачі очікують цього.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.