Почему многие языки не поддерживают именованные параметры? [закрыто]

14

Я просто подумал, насколько проще было бы прочитать код, если бы при вызове функции вы могли написать:

doFunction(param1=something, param2=somethingElse);

Я не могу думать о каких-либо недостатках, и это сделало бы код более читабельным. Я знаю, что вы могли бы передать массив в качестве единственного аргумента и иметь ключи массива в качестве имен параметров, однако это все равно было бы не столь читабельным.

Есть ли недостаток в этом, что я скучаю? Если нет, то почему многие языки не допускают этого?


источник
1
Можете ли вы дать язык, который должен (и может) иметь именованные параметры, но не иметь его?
Брайан Чен
1
Я думаю, что это слишком многословно во многих случаях. Насколько я видел, то, использует ли язык это или нет, зависит от того, повлияет ли это на язык положительно или отрицательно. Большинство языков C-стиля хорошо обходятся без него. В их случае часто совершенно очевидно, что происходит в любом случае, и его отсутствие действительно помогает просто уменьшить беспорядок в целом.
Panzercrisis
2
@SteveFallows: «Имена именованных параметров» выглядит как странное имя для определения плавного API (как его назвал Мартин Фаулер) в классе строителя. Вы можете найти примеры того же шаблона в одном из первых пунктов « Эффективной Java» Джошуа Блоха .
23
3
Это не обязательно вопрос, основанный на мнении
Catskul
2
Согласовано. "Почему многие языки не поддерживают именованные параметры?" это больше вопрос истории языков, чем мнения. Было бы мнение, если бы кто-то спросил его как «Не названы ли параметры лучше?».
Энди Флеминг

Ответы:

14

Указание имен параметров не всегда делает код более читабельным: например, вы бы предпочли читать min(first=0, second=1)или min(0, 1)?

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

Я могу придумать как минимум две причины:

  • В общем, введение второго синтаксиса для уже охваченной потребности (здесь передача параметров) - это то, чьи преимущества должны быть сбалансированы с внутренней стоимостью введенной языковой сложности (как в реализации языка, так и в модели языка программиста) ;
  • Это делает изменение имен параметров изменяющим API-изменением, которое может не соответствовать ожиданиям разработчика, что изменение имени параметра является тривиальным, неразрывным изменением;

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

jhominal
источник
6
Я регулярно пишу код на нескольких разных языках, мне почти всегда приходится искать параметры для простой подстроки на любом языке. Подстрока (начало, длина) или подстрока (начало, конец) и конец включен в строку?
Джеймс Андерсон
1
@JamesAnderson - Как подстрока именованных параметров решит вашу проблему? Вам все равно придется искать параметры, чтобы узнать, как называется второй параметр (если только не существуют обе функции и функция языка полиморфизма допускает одинаковые параметры типа с разными именами).
Mouviciel
6
@mouviciel: Именованные параметры не очень полезны для писателя , но фантастичны для читателя . Мы все знаем, насколько важны имена переменных для создания читабельного кода, а именованные параметры позволяют создателю интерфейса давать хорошие имена параметров, а также хорошие имена функций, упрощая тем, кто использует этот интерфейс для написания читаемого кода.
Фоши
@Phoshi - Вы правы. Я просто забыл о важности чтения кода, когда писал свой предыдущий комментарий.
Mouviciel
14

Именованные параметры делают код легче для чтения, труднее писать

Когда я читаю фрагмент кода, именованные параметры могут вводить контекст, облегчающий понимание кода. Рассмотрим, например , этот конструктор: Color(1, 102, 205, 170). Что на земле это значит? Действительно, Color(alpha: 1, red: 102, green: 205, blue: 170)было бы намного легче читать. Но, увы, компилятор говорит «нет» - он хочет Color(a: 1, r: 102, g: 205, b: 170). При написании кода с использованием именованных параметров вы тратите ненужное количество времени на поиск точных имен - проще забыть точные имена некоторых параметров, чем забыть их порядок.

Это однажды меня поразило, когда я использовал DateTimeAPI, у которого было два родственных класса для точек и длительностей с почти идентичными интерфейсами. Пока DateTime->new(...)принимаются second => 30аргументы, DateTime::Duration->new(...)разыскиваются seconds => 30и аналогичные для других подразделений. Да, это имеет смысл, но это показало мне, что именованные параметры ≠ простота использования.

Плохие имена даже не облегчают чтение

Другим примером того, как именованные параметры могут быть плохими, является, вероятно, язык R. Этот фрагмент кода создает график данных:

plot(plotdata$n, plotdata$mu, type="p", pch=17,  lty=1, bty="n", ann=FALSE, axes=FALSE)

Вы видите два позиционных аргумента для строк данных x и y , а затем список именованных параметров. Есть еще много опций с настройками по умолчанию, и в списке указаны только те, настройки по умолчанию которых я хотел изменить или явно указать. Как только мы проигнорируем, что этот код использует магические числа, и может извлечь выгоду из использования перечислений (если R имел их!), Проблема в том, что многие из этих имен параметров довольно неразборчивы.

  • pchна самом деле символ графика, символ, который будет отображаться для каждой точки данных. 17это пустой круг или что-то в этом роде.
  • ltyтип линии Здесь 1сплошная линия.
  • btyэто тип коробки. Установка этого "n"позволяет избежать рисования рамки вокруг графика.
  • ann контролирует появление аннотаций оси.

Для тех, кто не знает, что означает каждая аббревиатура, эти варианты довольно запутанные. Это также показывает, почему R использует эти метки: не как самодокументируемый код, а (будучи языком с динамической типизацией) в качестве ключей для сопоставления значений с их правильными переменными.

Свойства параметров и подписей

Сигнатуры функций могут иметь следующие свойства:

  • Аргументы могут быть упорядочены или неупорядочены,
  • названный или неназванный,
  • требуется или необязательно.
  • Подписи также могут быть перегружены по размеру или типу,
  • и может иметь неопределенный размер с varargs.

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

Динамически типизированные языки с varargs (интерфейсы командной строки, Perl, ...) могут эмулировать необязательные именованные параметры. Языки с перегрузкой размера сигнатуры имеют нечто вроде позиционных необязательных параметров.

Как не реализовать именованные параметры

Когда мы думаем об именованных параметрах, мы обычно предполагаем именованные, необязательные, неупорядоченные параметры. Реализовать это сложно.

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

Теперь важный вопрос - как аргументы фактически передаются в функцию. При упорядоченных параметрах аргументы могут передаваться в регистре или в их внутреннем порядке в стеке. Когда мы на мгновение исключаем регистры, проблема заключается в том, как поместить неупорядоченные необязательные аргументы в стек.

Для этого нам нужен некоторый порядок над необязательными аргументами. Что делать, если код декларации изменен? Поскольку порядок не имеет значения, переупорядочение в объявлении функции не должно изменять положение значений в стеке. Мы также должны рассмотреть возможность добавления нового необязательного параметра. С точки зрения пользователей это выглядит так, потому что код, который ранее не использовал этот параметр, должен работать с новым параметром. Таким образом, это исключает порядок, такой как использование порядка в объявлении или алфавитный порядок.

Рассмотрим это также в свете подтипирования и принципа подстановки Лискова - в скомпилированном выводе те же инструкции должны иметь возможность вызывать метод для подтипа с возможно новыми именованными параметрами и для супертипа.

Возможные реализации

Если у нас не может быть определенного порядка, нам нужна неупорядоченная структура данных.

  • Самая простая реализация - просто передать имя параметров вместе со значениями. Вот как именованные параметры эмулируются в Perl или с помощью инструментов командной строки. Это решает все проблемы расширения, упомянутые выше, но может быть огромной тратой пространства - не вариант для кода, критичного к производительности. Кроме того, обработка этих именованных параметров теперь намного сложнее, чем просто извлечение значений из стека.

    На самом деле, требования к пространству могут быть уменьшены с помощью объединения строк, что может уменьшить последующее сравнение строк до сравнения указателей (за исключением случаев, когда невозможно гарантировать, что статические строки фактически объединены, и в этом случае две строки должны будут сравниваться в подробно).

  • Вместо этого мы могли бы также передать умную структуру данных, которая работает как словарь именованных аргументов. Это дешево для вызывающей стороны, потому что набор ключей статически известен в месте вызова. Это позволило бы создать идеальную хеш-функцию или рассчитать три. Вызываемый все равно должен будет проверить наличие всех возможных имен параметров, что несколько дорого. Нечто подобное используется в Python.

Так что это просто слишком дорого в большинстве случаев

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

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

Другие подводные камни

Имена переменных в объявлении функции обычно имеют некоторое внутреннее значение и не являются частью интерфейса - даже если многие инструменты документации по-прежнему будут их отображать. Во многих случаях вам понадобятся разные имена для внутренней переменной и соответствующего именованного аргумента. Языки, которые не позволяют выбирать внешне видимые имена именованного параметра, не получают много из них, если имя переменной не используется с учетом вызывающего контекста.

Проблема с эмуляцией именованных аргументов заключается в отсутствии статической проверки на стороне вызывающей стороны. Это особенно легко забыть при передаче словаря аргументов (глядя на вас, Python). Это важно , потому что прохождение словаря является общим обходным путем, например , в JavaScript: foo({bar: "baz", qux: 42}). Здесь ни типы значений, ни наличие или отсутствие определенных имен не могут быть проверены статически.

Эмуляция именованных параметров (в статически типизированных языках)

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

// Java

static abstract class Arguments {
  public String bar = "default";
  public int    qux = 0;
}

void foo(Arguments args) {
  ...
}

/* using an initializer block */
foo(new Arguments(){{ bar = "baz"; qux = 42; }});
Амон
источник
3
« it is easier to forget the exact names of some parameters than it is to forget their order... это интересное утверждение.
Catskul
1
Первый контрпример не является проблемой с именованными параметрами, а скорее недостатком в том, как английские множественные числа отображаются на имена полей в интерфейсах. Второй контрпример также является проблемой с разработчиками, делающими неправильный выбор имен. (Никакой выбор интерфейса передачи параметров сам по себе не будет достаточным условием для удобочитаемости - вопрос в том, является ли это необходимым условием или помощью в некоторых случаях).
mtraceur
1
+1 за общие баллы о затратах на реализацию и компромиссах с выполнением именованных параметров. Я просто думаю, что начало потеряет людей.
mtraceur
@mtraceur У тебя есть точка зрения. Теперь, 5 лет спустя, я бы, наверное, написал ответ совсем по-другому. Если вы хотите, вы можете отредактировать мой ответ, чтобы удалить менее полезные вещи. (Обычно такие правки отклоняются, поскольку они противоречат намерениям автора, но здесь я в порядке). Например, теперь я считаю, что многие проблемы с именованными параметрами являются просто ограничениями динамических языков, и они должны просто получить систему типов. Например, в JavaScript есть Flow и TypeScript, в Python - MyPy. С правильной интеграцией IDE, которая устраняет большинство проблем с юзабилити. Я все еще чего-то жду в Perl.
Amon
7

По той же причине, что венгерская нотация больше не используется широко; Intellisense (или его моральный эквивалент в IDE не Microsoft). Большинство современных IDE расскажут вам все, что вам нужно знать о параметре, просто наведя указатель на параметр.

Тем не менее, многие языки поддерживают именованные параметры, включая C # и Delphi. В C # это необязательно, поэтому вам не нужно его использовать, если вы этого не хотите, и есть другие способы специально объявить члены, такие как инициализация объекта.

Именованные параметры в основном полезны, когда вы хотите указать только подмножество необязательных параметров, а не все из них. В C # это очень полезно, потому что вам больше не нужна куча перегруженных методов, чтобы предоставить программисту такую ​​гибкость.

Роберт Харви
источник
4
Python использует именованные параметры, и это замечательная особенность языка. Я хотел бы, чтобы больше языков использовали это. Это упрощает аргументы по умолчанию, поскольку вам больше не нужно, как в C ++, явно указывать любые аргументы «до» значений по умолчанию. (Как и в предыдущем абзаце, именно так Python обходится без перегрузки ... аргументы по умолчанию и аргументы имени позволяют вам делать все то же самое.) Именованные аргументы также способствуют созданию более читабельного кода. Когда у вас есть что-то вроде sin(radians=2), вам не нужно "Intellisense".
Gort the Robot
2

Bash, безусловно, поддерживает этот стиль программирования, и Perl и Ruby поддерживают передачу списков имен / значений параметров (как и любой язык с поддержкой хэшей / карт). Ничто не мешает вам выбрать определение структуры / записи или хэша / карты, которая присоединяет имена к параметрам.

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

Обратите внимание, что опытные программисты привыкли передавать параметры по порядку / слоту. Вы также можете обнаружить, что эта идиома полезна только тогда, когда у вас есть несколько аргументов (скажем,> 5/6). А поскольку большое количество аргументов часто указывают на (чрезмерно) сложные методы, вы можете обнаружить, что эта идиома полезна только для самых сложных методов.

ChuckCottrill
источник
2

Я думаю, что это та же самая причина, почему C # более популярен, чем VB.net. Хотя VB.NET более «читабелен», например, вместо закрывающей скобки вы набираете «end if», но в итоге получается больше материала, который дополняет код и в конечном итоге усложняет его понимание.

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

Rocklan
источник
1
Я категорически не согласен с тем, что «чем меньше, тем лучше». Если принять его логическую крайность, победителями Code Golf станут «лучшие» программы.
Райли Майор
2

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

Языки, которые трудно реорганизовать, часто поддерживают именованные параметры, потому foo(1)что они ломаются при изменении сигнатуры foo(), но с foo(name:1)меньшей вероятностью ломаются, требуя меньше усилий от программиста для внесения изменений foo.

Что вы делаете, когда вам нужно ввести новый параметр в следующую функцию, и есть сотни или тысячи строк кода, вызывающих эту функцию?

foo(int weight, int height, int age = 0);

Большинство программистов будут делать следующее.

foo(int weight, int height, int age = 0, string gender = null);

Теперь рефакторинг не требуется, и функция может выполняться в устаревшем режиме, когда gender равно нулю.

Для конкретного genderзначения теперь HARDCODED в вызове age. Как этот пример:

 foo(10,10,0,"female");

Программист посмотрел определение функции, увидел, что она ageимеет значение по умолчанию 0и использовал это значение.

Теперь значение по умолчанию для age в определении функции совершенно бесполезно.

Именованные параметры позволяют избежать этой проблемы.

foo(weight: 10, height: 10, gender: "female");

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

Reactgular
источник