Дизайн синтаксису - навіщо використовувати круглі дужки, коли не передаються аргументи?


66

У багатьох мовах синтаксис function_name(arg1, arg2, ...)використовується для виклику функції. Коли ми хочемо викликати функцію без будь-яких аргументів, ми повинні це зробити function_name().

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

З іншого боку, в деяких мовах ми можемо зробити: function_name 'test';або навіть function_name 'first' 'second';викликати функцію чи команду.

Я думаю, що круглі дужки були б кращими, якби вони були потрібні лише для оголошення порядку пріоритетності, а в інших місцях були необов’язковими. Наприклад, дія if expression == true function_name;повинна бути такою ж справедливою, як і if (expression == true) function_name();.

На мою думку, найприємніше робити це, 'SOME_STRING'.toLowerCase()коли явно не потрібні аргументи функції прототипу. Чому дизайнери вирішили проти простішого 'SOME_STRING'.lower?

Відмова: Не зрозумійте мене неправильно, я люблю C-подібний синтаксис! ;) Я просто запитую, чи може бути краще. Чи вимагає ()якісь переваги у виконанні, чи це полегшує розуміння коду? Мені дуже цікаво, в чому саме причина.


103
Що б ви зробили, якби хотіли передати функцію як аргумент іншій функції?
Вінсент Савард

30
Поки код потрібен людям для читання, читабельність є важливою.
Тулен Кордова

75
Це питання з читабельністю, про яке ви питаєте (), але те, що виділяється у вашій посаді, - це if (expression == true)твердження. Ви турбуєтесь про зайве (), але тоді використовуйте зайве == true:)
Девід Арно

15
Visual Basic це дозволив
edc65

14
У Паскалі це було обов’язково, ви використовували лише паролі для функцій та процедур, які брали аргументи.
RemcoGerlich

Відповіді:


251

Для мов, які використовують функції першого класу , цілком звичайним є синтаксис посилання на функцію:

a = object.functionName

в той час як акт виклику цієї функції такий:

b = object.functionName()

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

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


9
Ви можете згадати, що ця різниця цікава лише за наявності побічних ефектів - для чистої функції це не має значення
Бергі

73
@Bergi: Для чистих функцій дуже важливо! Якщо у мене є функція, make_listяка запаковує свої аргументи у список, велика різниця між f(make_list)передачею функції fабо передачею порожнього списку.
user2357112

12
@Bergi: Навіть тоді я все одно хотів би передати SATInstance.solveчи іншу неймовірно дорогу чисту функцію в іншу функцію, не намагаючись її відразу запустити. Це також робить речі справді незручними, якщо someFunctionробить щось інше в залежності від того, чи someFunctionоголошено це чистим. Наприклад, якщо у мене є (псевдокод) func f() {return g}, func g() {doSomethingImpure}і func apply(x) {x()}, потім apply(f)дзвонить f. Якщо я потім оголошу, що fце чисте, то раптом apply(f)переходить gдо apply, applyдзвонить gі робить щось нечисте.
user2357112

6
@ user2357112 Стратегія оцінювання не залежить від чистоти. Використання синтаксису викликів як примітки, коли можна оцінити, що можливо (але, ймовірно, незручно), однак це не змінює результат або його правильність. Що стосується вашого прикладу apply(f), якщо gце нечисто (і applyяк добре?), То вся справа нечиста і, звичайно, руйнується.
Бергі

14
Прикладами цього є Python та ECMAScript, обидва з яких насправді не мають основної концепції "методів", натомість у вас є іменоване властивість, яке повертає анонімний об'єкт функції першого класу, а потім ви називаєте цю анонімну функцію першого класу. Зокрема, і в Python, і в ECMAScript, кажучи, foo.bar()це не одна операція "метод виклику barоб'єкта foo", а дві операції, "поле відміни barоб'єкта, foo а потім виклик об'єкта, повернутого з цього поля". Таким чином, .це оператор поля селектора і ()є оператором виклику , і вони незалежні.
Йорг W Міттаг

63

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

Як автор-упорядник, я вважаю б гарантовану наявність дужок досить зручною; Я завжди знав би, що це метод виклику, і мені не доведеться будувати біфуркацію для випадкового випадку.

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

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


Дякуємо, і ви дали вагомі причини, чому це важливо. Чи можете ви також навести кілька прикладів того, чому мовні дизайнери повинні використовувати функції в прототипах?
Девід Рефуа

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

20
Насправді Scala є дещо тонкішим, ніж це: в той час як інші мови дозволяють лише один список параметрів з нульовим або більше параметрами, Scala дозволяє нульовий або більше списків параметрів з нульовим або більше параметрами. Отже, def foo()це метод з одним порожнім списком параметрів, і def fooце метод без списку параметрів, і два різні речі ! Загалом, метод без списку параметрів потрібно викликати зі списком аргументів, а метод із порожнім списком параметрів потрібно викликати з порожнім списком аргументів. Виклик методу без списку параметрів із порожнім аргументом насправді…
Jörg W Mittag

19
... інтерпретується як виклик applyметоду об'єкта, повернутого викликом методу, з порожнім списком аргументів, тоді як виклик методу з порожнім списком параметрів без списку аргументів може залежно від контексту по-різному трактуватися як виклик методу з порожнім списком аргументів, η-розширення на частково застосований метод (тобто "посилання на метод"), або він може взагалі не компілюватися. Також Scala дотримується принципу єдиного доступу, не розрізняючи (синтаксично) виклик методу без списку аргументів та посилання на поле.
Йорг W Міттаг

3
Навіть коли я оцінюю Рубі за відсутність необхідної "церемонії" - паронів, дужок, явного типу - я можу сказати, що метод-дужка читається швидше ; але колись знайомий ідіоматичний Рубі досить читабельний і, безумовно, швидше писати.
radarbob

19

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

У зазначених мовах кожна функція має рівно один аргумент . Те, що ми часто вважаємо "численними аргументами", насправді є єдиним параметром типу продукту. Так, наприклад, функція, яка порівнює два цілих числа:

leq : int * int -> bool
leq (a, b) = a <= b

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

leq some_pair = some_pair.1 <= some_pair.2

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

А як щодо функції, яка нібито не має аргументів? Така функція фактично має домен Unit. Одиночний член елемента, Unitяк правило, записується як " ()," тому в них виникають дужки.

say_hi : Unit -> string
say_hi a = "Hi buddy!"

Щоб викликати цю функцію, нам доведеться застосувати її до значення типу Unit, яке має бути (), тому ми закінчимо запис say_hi ().

Отже, насправді не існує такого поняття, як список аргументів!


3
І тому порожні дужки ()нагадують ложки мінус ручки.
Міндвін

3
Визначення leqфункції, яка використовує, <а не <=досить заплутано!
KRyan

5
Цю відповідь може бути простішим для розуміння, якби вона використовувала слово "кортеж" на додаток до фраз на зразок "тип продукту".
Кевін

Більшість формалізмів трактуватиме int f (int x, int y) як функцію від I ^ 2 до I. Обчислення лямбди відрізняється; він не використовує цей метод, щоб відповідати тому, що в інших формалізмах є високою артією. Замість обчислення лямбда використовується каррі. Порівняння було б x -> (y -> (x <= y)). (x -> (y -> (x <= y)) 2 буде оцінено до (y-> 2 <= y), і (x -> (y -> (x <= y)) 2 3 оцінюється до ( y-> 2 <= y) 3, що в свою чергу оцінює до 2 <= 3.
Taemyr

1
@PeriataBreatta Я не знайомий з історією використання ()для представлення одиниці. Я не був би здивований, якби хоча б частина причин була схожа на "функцію без параметрів". Однак є інше можливе пояснення синтаксису. В алгебрі типів Unitнасправді є тотожність для типів продуктів. Бінарний продукт записується так (a,b), як ми могли б записати одинарний продукт як (a), тому логічним розширенням було б написати нульовий продукт просто ().
садок

14

Наприклад, у Javascript, використовуючи ім'я методу без (), повертає саму функцію, не виконуючи її. Таким чином, ви можете, наприклад, передати функцію як аргумент іншому методу.

У Java було зроблено вибір, що ідентифікатор, за яким слідує () або (...), означає виклик методу, тоді як ідентифікатор без () посилається на змінну члена. Це може покращити читабельність, оскільки ви не сумніваєтесь, чи маєте ви справу зі способом чи змінною члена. Насправді, те саме ім'я можна використовувати як для методу, так і для змінної члена, обидва будуть доступні у відповідному синтаксисі.


4
+1 Доки людині потрібно читати код, читабельність - це суттєво.
Тулен Кордова

1
@ TulainsCórdova Я не впевнений, що Perl з вами згоден.
Рахіт

@Racheet: Perl не може, але Perl Best Practicesробить
slebetman

1
@Racheet Perl дуже читабельний, якщо написано для читання. Це те саме для майже неезотеричної мови. Короткий, лаконічний Perl дуже читається для програмістів Perl, подібно до того, як код Scala чи Haskell читається людям, які мають досвід цих мов. Я також можу розмовляти з вами німецькою мовою, дуже грамотно цілком коректно, але все-таки ви не зрозумієте жодного слова, навіть якщо ви добре володієте німецькою мовою. І в цьому не винна німецька. ;)
simbabque

у Java це не "ідентифікувати" метод. Це називає. Object::toStringідентифікує метод.
njzk2

10

Синтаксис слідує за семантикою, тому почнемо з семантики:

Які способи використання функції чи методу?

Насправді існує кілька способів:

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

Наявність подібного синтаксису для різних цілей є найкращим способом створити або неоднозначну мову, або, принаймні, заплутану мову (а таких у нас достатньо).

На мовах, подібних до С та С:

  • виклик функції здійснюється за допомогою дужок, щоб укласти аргументи (може бути жодного), як у func()
  • трактування функції як значення може бути виконано просто використанням її імені як у &func(C, створення покажчика функції)
  • у деяких мовах у вас є синтаксиси, які частково застосовуються; Java дозволяє, someVariable::someMethodнаприклад, (обмежується приймачем методу, але все ще корисно)

Зауважте, як кожне використання має різний синтаксис, що дозволяє легко їх розрізняти.


2
Ну, "легко" - це одна з тих, "це зрозуміло лише в тому випадку, якщо це вже зрозуміло". Початківцю було б цілком виправдано зауважити, що без жодних пояснень зрозуміло, чому деяка пунктуація має значення, яке вона має.
Ерік Ліпперт

2
@EricLippert: Дійсно, легко не обов'язково означає інтуїтивне. Хоча в цьому випадку я зазначу, що дужки також використовуються для функції "виклику" в математиці. Попри це, я знайшов початківців багатьох мов, які більше борються з поняттями / семантикою, ніж синтаксисом загалом.
Матьє М.

Ця відповідь плутає відмінність між "просіванням" і "частковим застосуванням". ::Оператор в Java є частковим оператором додатки, а НЕ оператор вичинки. Часткове застосування - це операція, яка приймає функцію та одне або більше значень і повертає функцію, приймаючи менші значення. Каррінг працює лише на функції, а не на значення.
Періата Бреата

@PeriataBreatta: Добре, фіксовано.
Матьє М.

8

Мовою з побічними ефектами IMO дуже корисно розмежовувати (теоретично без побічних ефектів) читання змінної

variable

та виклик функції, яка може спричинити побічні ефекти

launch_nukes()

OTOH, якщо немає ніяких побічних ефектів (крім тих, що закодовані в системі типів), то немає сенсу навіть розрізняти читання змінної та виклик функції без аргументів.


1
Зауважте, що ОП представив випадок, коли об'єкт є єдиним аргументом і подається перед назвою функції (що також забезпечує область для імені функції). noun.verbсинтаксис міг бути таким же чітким за значенням noun.verb()і трохи менш захаращеним. Аналогічно, функція, що member_повертає ліву посилання, може запропонувати синтаксис дружнього характеру, ніж типова функція сеттера: obj.member_ = new_valuevs. obj.set_member(new_value)( _постфікс - це натяк, що говорить "відкритий", що нагадує _ префікси для "прихованих" функцій).
Пол А. Клейтон

1
@ PaulA.Clayton Я не бачу, як це не охоплюється моєю відповіддю: Якщо noun.verbозначає виклик функції, як ви відмежуєте її від простого доступу до учасника?
Даніель Жур

1
Що стосується того, що читання змінних теоретично не має побічних ефектів, я просто хочу сказати таке: Завжди остерігайтеся фальшивих друзів .
Час Джастіна

8

Жодна з інших відповідей не намагалася вирішити питання: скільки зайвих у дизайні мови має бути? Тому що навіть якщо ви можете спроектувати мову так, щоб x = sqrt yвстановити х до квадратного кореня у, це не означає, що ви обов'язково повинні.

Мовою без надмірності кожна послідовність символів щось означає, а це означає, що якщо ви зробите одну помилку, ви не отримаєте повідомлення про помилку, ваша програма зробить неправильну річ, яка може бути щось зовсім інше, ніж ви призначений і дуже важкий для налагодження. (Як буде знати кожен, хто працював із регулярними виразами.) Невелика надмірність - це хороша річ, оскільки вона дозволяє виявити багато ваших помилок, і чим більше надмірності є, тим більше шансів на те, що діагностика буде точною. Тепер, звичайно, ви можете зайняти це занадто далеко (ніхто з нас не хоче писати в COBOL в ці дні), але є баланс, який є правильним.

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


У певному сенсі я згоден: мови програмування повинні мати механізм "мережі безпеки". Але я не погоджуюся з тим, що «невелика надмірність» у синтаксисі - це хороший спосіб зробити це - це котлован , і те, що він здебільшого робить, це вводити шум, який ускладнює виявлення помилок, і відкриває можливості для синтаксичних помилок, які відволікають від справжні помилки. Вид багатослівності, що сприяє читанню, має мало спільного з синтаксисом, більше з умовами іменування . А мережа безпеки на сьогоднішній день найбільш ефективно реалізовується як система сильного типу , в якій більшість випадкових змін призведуть до неправильного набору програми.
Ліворуч близько

@leftaroundabout: Мені подобається думати про рішення дизайну мови з точки зору відстані Хеммінга. Якщо дві конструкції мають різні значення, часто корисно, щоб вони відрізнялися принаймні двома способами і мали форму, яка відрізняється лише одним способом, щоб генерувати компілятор. Дизайнер мови, як правило, не несе відповідальності за те, щоб ідентифікатори легко розрізнилися, але якщо, наприклад, призначення вимагає :=і порівняння ==, використовуючи =лише для ініціалізації часу компіляції, то ймовірність помилок друку перетворити на призначення буде значно зменшена.
supercat

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

@supercat: ну, знову ж таки, я думаю, що це вирішує проблему на неправильному рівні. Присвоєння та порівняння - це концептуально різні операції, відповідно, оцінка функцій та прийняття адреси відповідно. Отже, вони повинні мати чітко розрізнені типи, тоді це вже не має значення, якщо оператори мають малу відстань Хеммінга, оскільки будь-яка помилка буде помилкою типу компіляції.
листопада

@leftaroundabout: Якщо присвоюється зміна булевого типу або якщо тип повернення функції є самим вказівником на функцію (або - у деяких мовах - фактичною функцією), замінюючи порівняння із призначенням, або виклик функції з посиланням на функцію, може давати програму, яка є синтаксично дійсною, але має зовсім інше значення від початкової версії. Перевірка типу в цілому знайде деякі такі помилки, але кращим підходом здається синтаксис.
supercat

5

У більшості випадків це синтаксичний вибір граматики мови. Граматика корисна для різних індивідуальних конструкцій (відносно) однозначно, якщо їх узяти разом. (Якщо є неоднозначності, як, наприклад, у деяких деклараціях C ++, повинні бути конкретні правила для вирішення.) У компілятора немає широти для здогадок; потрібно дотримуватися мовної специфікації.


Visual Basic в різних формах розрізняє процедури, які не повертають значення, та функції.

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

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

(З іншого боку , Visual Basic використовує ті ж дужки ()для посилань на масиви як для викликів функцій, тому посилання на масив виглядають виклики функцій. І це полегшує ручну рефакторинг масиву в виклик функції! Таким чином, ми могли б подумати інші мови використовують []«пд.ш. для посилань на масив, але я відхиляюсь ...)


На мовах C і C ++ до вмісту змінних автоматично звертається за допомогою їх імені, і якщо ви хочете посилатися на саму змінну замість її вмісту, ви застосовуєте одинарний &оператор.

Цей тип механізму також може бути застосований до імен функцій. Неочищена назва функції може означати виклик функції, тоді як унарний &оператор буде використовуватися для позначення функції (самої) як даних. Особисто мені подобається ідея доступу до функцій без аргументів без побічних ефектів із тим же синтаксисом, що і змінні.

Це цілком правдоподібно (як і інші синтаксичні варіанти для цього).


2
Звичайно, хоча відсутність дужок у візуальних базових дужках для процедурних викликів є непотрібним відмінністю порівняно з його викликами функцій, додавання необхідних дужок до них було б зайвим розмежуванням між операторами , багато з яких на основі мови BASIC мають дуже сильні ефекти нагадує процедури. Минуло також час, коли я робив будь-яку роботу у версії MS BASIC, але чи все ще не дозволяє синтаксис call <procedure name> (<arguments>)? Досить впевнений, що це був правильний спосіб використання процедур, коли я програвав QuickBASIC програмування протягом іншого життя ...
Periata Breatta

1
@PeriataBreatta: VB як і раніше дозволяє синтаксис "виклику", а іноді вимагає цього у випадках, коли річ, яку потрібно викликати, є виразом [наприклад, можна сказати "Call If (someCondition, Delegate1, Delegate2) (Аргументи)", але прямий виклик результату оператора "If" не буде дійсним як окремий вислів].
supercat

@PeriataBreatta Мені сподобалось відсутність брекет-процедур VB6, тому що це виглядало як DSL-код.
Марк Херд

@supercat У LinqPad дивно, як часто мені потрібно використовувати Callметод, де мені майже ніколи не потрібно в "звичайному" виробничому коді.
Марк Херд

5

Послідовність і читабельність.

Якщо я дізнаюся , що ви викликаєте функцію Xна зразок цього: X(arg1, arg2, ...), то я очікую , що це працює так само , як для будь - яких аргументів: X().

Тепер в той же час я дізнаюся, що ви можете визначити змінну як деякий символ і використовувати її так:

a = 5
b = a
...

А що я міг би подумати, коли знайду це?

c = X

Ваша здогадка така ж, як і моя. У звичайних обставинах значення Xє змінною. Але якби ми пішли на ваш шлях, це також може бути функцією! Чи слід пам’ятати, який символ символізує групу (змінні / функції)?

Ми можемо накласти штучні обмеження, наприклад, "Функції починаються з великої літери. Змінні починаються з малої літери", але це не потрібно і ускладнює справи, хоча іноді може допомогти деяким із цілей дизайну.

Побічна примітка 1: Інші відповіді повністю ігнорують ще одне: мова може дозволяти вам використовувати те саме ім’я як для функції, так і для змінної та відрізняти за контекстом. Дивіться Common Lispяк приклад. Функція xта змінна xспівіснують ідеально.

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

І в будь-якому випадку ви втрачаєте можливість мати окремі простори імен для функцій та змінних.


Так. Послідовність - це достатньо вагома причина, повна зупинка (не те, що інші пункти не вірні - вони є).
Матвій

4

Щоб додати до інших відповідей, візьміть цей приклад C:

void *func_factory(void)
{
        return 0;
}

void *(*ff)(void);

void example()
{
        ff = func_factory;
        ff = func_factory();
}

Якщо оператор виклику був необов’язковим, не було б можливості розрізнити призначення функції та виклик функції.

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


3
У JavaScript не бракує системи типів. У ньому відсутні декларації типу . Але він цілком усвідомлює різницю між різними типами вартості.
Periata Breatta

1
Periata Breatta: справа в тому, що змінні та властивості Java можуть містити типи будь-якого значення, тому ви не завжди можете знати, чи є це функцією під час читання коду чи ні.
reinierpost

Наприклад, що робить ця функція? function cross(a, b) { return a + b; }
Марк К Коуан

@MarkKCowan З цього випливає: ecma-international.org/ecma-262/6.0/…
curiousdannii
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.