Перегрузка функции? Да или нет [закрыто]


16

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

Каковы наиболее убедительные аргументы за и против включения перегрузки функций в языке?


РЕДАКТИРОВАТЬ: нет никого, кто имеет противоположное мнение?

Бертран Мейер (создатель Eiffel в 1985/1986) вызывает метод, перегружающий это: (источник)

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

Теперь это некоторые обширные обобщения, но он умный парень, поэтому я могу с уверенностью сказать, что он может их поддержать, если понадобится. Фактически, он почти убедил Брэда Абрамса (одного из разработчиков CLSv1), что .NET не должен поддерживать перегрузку методов. (источник) Это очень мощный материал. Может ли кто-нибудь пролить свет на его мысли, и оправдана ли его точка зрения еще 25 лет спустя?

Ответы:


24

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

Обычно присутствует в именах функций-членов. A.foo()Можно вызвать совершенно другую функцию из B.foo(), но обе функции названы foo. Он присутствует в операторах, как +и разные вещи, когда применяется к целым числам и числам с плавающей запятой, и часто используется как оператор конкатенации строк. Кажется странным не допустить этого и в обычных функциях.

Это позволяет использовать «мультиметоды» в стиле Common Lisp, в которых точная вызываемая функция зависит от двух типов данных. Если вы еще не запрограммировали в Common Lisp Object System, попробуйте, прежде чем вызывать это как бесполезное. Это жизненно важно для потоков 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 поддерживает как расширение), вы можете это сделать. Я обновил ответ с примером.
Джои Адамс

Хм, интересно. Таким образом, в основном, будет class Eq a ...преобразовано в псевдо-C-семейство 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) {}
...

Для строго типизированного языка, да. Для слабо типизированного языка нет. Вы можете сделать вышеупомянутое только с одной функцией на слабо типизированном языке.
18:30

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 Это пропускает такие вещи, как indexOf(char ch)+ indexOf(Date dt)в списке. Мне тоже нравятся значения по умолчанию, но они не взаимозаменяемы со статической типизацией.
Mark

2

Вы только что описали Java. Или C #.

Почему ты изобретаешь колесо?

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

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

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

@ Мейсон: Вы можете эвристически определить ожидаемый тип возврата на основе ожидаемого возврата, однако я не ожидаю, что это будет сделано.
Джош К

1
Хм ... как ваша эвристика узнает, что ожидается вернуть, если вы не ожидаете никакого возвращаемого значения?
Мейсон Уилер

1
Эвристика ожидания типа на самом деле на месте ... вы можете делать такие вещи, как EnumX.Flag1 | Flag2 | Flag3. Я не буду реализовывать это, хотя. Если бы я это сделал и тип возвращаемого значения не использовался, я бы искал тип возвращаемого значения void.
Обратите внимание на себя - придумайте имя

1
@ Мейсон: Это хороший вопрос, но в этом случае я бы искал функцию void (как уже упоминалось). Также теоретически вы можете выбрать любой из них, поскольку все они будут выполнять одну и ту же функцию, просто возвращая данные в другом формате.
Джош К

2

Grrr .. недостаточно прав, чтобы комментировать еще ..

@ Мейсон Уилер: Знайте тогда об Аде, которая перегружает тип возвращаемого значения. Кроме того, мой язык Феликс делает это тоже в некоторых контекстах, в частности, когда функция возвращает другую функцию, и есть такой вызов:

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 методов с одинаковыми именами, которые делают одно и то же, но с совершенно разными наборами аргументов в самых разных шаблонах.

Вариант использования против невыполнения перегрузки функций: 5 методов с одинаковыми именами с очень похожими наборами типов в одном шаблоне.

В конце концов, я не собираюсь читать документы для API, созданного в обоих случаях.

Но в одном случае речь идет о том, что могут делать пользователи. В другом случае это то, что пользователи должны делать из-за языкового ограничения. ИМО, лучше хотя бы учесть, что авторы программ достаточно умны, чтобы разумно перегружать их, не создавая двусмысленности. Когда вы хлопаете в ладоши и забираете опцию, вы в основном гарантируете двусмысленность. Я больше доверяю пользователям делать правильные вещи, чем предполагать, что они всегда будут делать неправильные вещи. По моему опыту, протекционизм ведет к еще худшему поведению со стороны языкового сообщества.


1

Я решил предоставить обычные классы перегрузки и многотипных типов на своем языке Felix.

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

Классы типов также представлены в Феликсе. Для тех, кто знает C ++, но не использует Haskell, игнорируйте тех, кто описывает его как перегрузку. Это не удаленно, как перегрузка, скорее, это как специализация шаблона: вы объявляете шаблон, который не реализуете, а затем предоставляете реализации для конкретных случаев по мере необходимости. Типизация является параметрически полиморфной, реализация осуществляется посредством специальной реализации, но она не предназначена для ограничения: она должна реализовывать предполагаемую семантику.

В Haskell (и C ++) вы не можете указать семантику. В C ++ идея «Concepts» - это попытка приблизить семантику. В Феликсе вы можете аппроксимировать намерение с помощью аксиом, сокращений, лемм и теорем.

Основное и единственное преимущество (открытой) перегрузки в таком принципиальном языке, как Felix, заключается в том, что он облегчает запоминание имен библиотечных функций как для автора программы, так и для рецензента кода.

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

В C ++ это также проблема, поскольку он имеет неаккуратный алгоритм сопоставления и также поддерживает автоматическое преобразование типов: в Феликсе I «исправил» эту проблему, требуя точного сопоставления и не используя автоматических преобразований типов.

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

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


Звучит интересно. Я хотел бы прочитать больше, но ссылка на документы на веб-странице Феликса не работает.
Примечание для себя - придумайте имя

Да, весь сайт находится в стадии разработки (опять же), извините.
Иттрилл

0

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


0

Если вы ищете пользователей, знакомых с языками семейства C, то да, вам следует, потому что ваши пользователи будут ожидать этого.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.