Погано повертати різні типи даних з однієї функції динамічно набраною мовою?


65

Моя основна мова введена статично (Java). У Java вам потрібно повернути один тип із кожного методу. Наприклад, у вас не може бути методу, який умовно повертає a Stringабо умовно повертає Integer. Але в JavaScript, наприклад, це дуже можливо.

Статично набраною мовою я розумію, чому це погана ідея. Якщо кожен метод повертається Object(загальний батьківський від успадкованих всіх класів), ви та компілятор не маєте поняття, з чим маєте справу. Вам доведеться виявити всі свої помилки під час виконання.

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


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


Чому б не кинути виняток у випадку помилки?
TrueWill

1
@TrueWill я вирішу це в наступному реченні.
Даніель Каплан

Ви помітили, що це звичайна практика в більш динамічних мовах? Це звичайно у функціональних мовах, але там правила робляться дуже явними. І я знаю, що зазвичай, особливо в JavaScript, допускається, щоб аргументи були різними типами (адже це, по суті, єдиний спосіб зробити перевантаження функцій), але я рідко бачив, як це застосовується для повернення значень. Єдиний приклад, про який я можу придумати, це PowerShell з його в основному автоматичним загортанням / розгортанням масиву скрізь, а мови сценаріїв - винятковий випадок.
Aaronaught

3
Не згадується, але є багато прикладів (навіть у Java Generics) функцій, які приймають тип повернення як параметр; наприклад, у Common Lisp ми (coerce var 'string)отримуємо а stringабо (concatenate 'string this that the-other-thing)аналогічно. Я також писав такі речі ThingLoader.getThingById (Class<extends FindableThing> klass, long id). І там я можу повернути лише те, що підкласи того, про що ви просили: loader.getThingById (SubclassA.class, 14)може повернути розширення, SubclassBяке розширюється SubclassA...
BRPocock

1
Динамічно набрані мови схожі на Ложку у фільмі Матриця . Не намагайтеся визначати Ложку як рядок або число . Це було б неможливо. Натомість ... намагайтеся зрозуміти правду. Що немає Ложки .
Реакційний

Відповіді:


42

На відміну від інших відповідей, бувають випадки, коли повернення різних типів є прийнятним.

Приклад 1

sum(2, 3)  int
sum(2.1, 3.7)  float

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

var sum = function (a, b) {
    return a + b;
};

Одна і та ж функція, різні типи повернутого значення.

Приклад 2

Уявіть, що ви отримаєте відповідь від компонента OpenID / OAuth. Деякі постачальники OpenID / OAuth можуть містити більше інформації, наприклад, вік людини.

var user = authProvider.findCurrent();
// user is now:
// {
//     provider: 'Facebook',
//     name: {
//         firstName: 'Hello',
//         secondName: 'World',
//     },
//     email: 'hello.world@example.com',
//     age: 27
// }

Інші матимуть мінімум, буде це адреса електронної пошти чи псевдонім.

var user = authProvider.findCurrent();
// user is now:
// {
//     provider: 'Google',
//     email: 'hello.world@example.com'
// }

Знову ж функція, різні результати.

Тут вигода від повернення різних типів особливо важлива в контексті, коли вам не байдуже типи та інтерфейси, а які об’єкти насправді містять. Наприклад, уявимо, що веб-сайт містить зрілу мову. Тоді findCurrent()може використовуватися так:

var user = authProvider.findCurrent();
if (user.age || 0 >= 16) {
    // The person can stand mature language.
    allowShowingContent();
} else if (user.age) {
    // OpenID/OAuth gave the age, but the person appears too young to see the content.
    showParentalAdvisoryRequestedMessage();
} else {
    // OpenID/OAuth won't tell the age of the person. Ask the user himself.
    askForAge();
}

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

var age;
if (['Facebook', 'Yahoo', 'Blogger', 'LiveJournal'].contains(user.provider)) {
    age = user.age;
}

5
"У статизованих мовах це стосується перевантажень" Я думаю, ви мали на увазі "в деяких статично набраних мовах" :) Хороші статично набрані мови не потребують перевантаження для чогось типу вашого sumприкладу.
Андрес Ф.

17
Для вашого конкретного прикладу розглянемо Haskell: sum :: Num a => [a] -> a. Ви можете підсумувати список всього, що є числом. На відміну від JavaScript, якщо ви намагаєтеся підсумувати щось, що не є числом, помилка буде виявлена ​​під час компіляції.
Андрес Ф.

3
@MainMa У Scala Iterator[A]є метод def sum[B >: A](implicit num: Numeric[B]): B, який знову дозволяє підсумовувати будь-який вид чисел і перевіряється під час компіляції.
Петро Пудлак

3
@Bakuriu Звичайно, ви можете написати таку функцію, хоча спочатку вам доведеться або перевантажити +рядки (реалізуючи Num, що є жахливим дизайном ідеї, але законним), або винайти іншого оператора / функції, перевантаженого цілими числами та рядками відповідно. Хаскелл має спеціальний поліморфізм за допомогою типів класів. Список, що містить цілі числа та рядки, набагато складніший (можливо, неможливий без розширень мови), але досить інший питання.

2
Я не особливо вражений вашим другим прикладом. Ці два об'єкти є одним і тим же концептуальним "типом", це просто те, що в деяких випадках певні поля не визначені або заповнені. Ви могли б представити це просто добре навіть у мові статичного типу зі nullзначеннями.
Aaronaught

31

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

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

Але зауважте, що можуть бути й інші причини, що конкретна функція погано розроблена: Наприклад, sumфункція, що повертає помилку на недійсних входах, є поганою ідеєю насамперед через те, що це повернення є марним та схильним до помилок (0 <-> помилкове плутанина).


40
Мої два центи: Я ненавиджу це, коли це станеться. Я бачив бібліотеки JS, які повертають нуль, коли немає результатів, об’єкт на одному результаті та масив на двох чи більше результатах. Отже, замість того, щоб просто перебирати масив, ви змушені визначати, чи він є нульовим, а якщо ні, чи це масив, і якщо він є, зробіть одне, інакше зробіть щось інше. Тим більше, що в нормальному розумінні будь-який розумний розробник збирається просто додати Object до масиву і переробити логіку програми для обробки масиву.
фірфокс

10
@Izkata: Я не думаю, що NaNце взагалі "контрапункт". NaN - фактично допустимий вхід для будь-якого обчислення з плаваючою комою. Ви можете зробити все, NaNщо б ви могли зробити з фактичною кількістю. Правда, кінцевий результат зазначеного розрахунку не може бути дуже корисним для вас, але це не призведе до дивних помилок у час виконання , і вам не потрібно перевіряти його весь час - ви можете просто перевірити один раз в кінці ряд розрахунків. NaN - це не інший тип , це лише особливе значення , подібне до Null Object .
Aaronaught

5
@Aaronaught З іншого боку, NaNяк і нульовий об'єкт, як правило, приховує, коли виникають помилки, лише щоб вони з’являлися пізніше. Наприклад, ваша програма, ймовірно, вибухне, якщо NaNпрослизає в цикл умовно.
Ізката

2
@Izkata: NaN та null мають абсолютно різну поведінку. Нульова посилання вибухне одразу після доступу, NaN поширюється. Чому остання трапляється, очевидно, вона дозволяє писати математичні вирази, не перевіряючи результат кожного підвиразу. Особисто я вважаю нульовим набагато більш згубним, тому що NaN являє собою належну чисельну концепцію, без якої неможливо піти, і, математично, поширення - це правильна поведінка.
Phoshi

4
Я не знаю, чому це перетворилося на аргумент "нуль проти всього іншого". Я просто вказував, що NaNзначення (а) насправді не є іншим типом повернення і (б) має чітко визначену семантику з плаваючою комою, і тому насправді не кваліфікується як аналог змінному типу повернення. Насправді це не все так, що відрізняється від нуля в цілій арифметиці; якщо нуль "випадково" прослизає у ваші обчислення, то ви, найімовірніше, в кінцевому підсумку станете або нулем, або помилкою поділу на нуль. Чи є також злі значення "нескінченності", визначені плаваючою точкою IEEE?
Aaronaught

26

У динамічних мовах не слід запитувати, чи повертати різні типи, але об’єкти з різними API . Оскільки більшість динамічних мов насправді не цікавлять типи, але використовують різні версії набору тексту качок .

При поверненні різних типів має сенс

Наприклад, цей спосіб має сенс:

def load_file(file): 
    if something: 
       return ['a ', 'list', 'of', 'strings'] 
    return open(file, 'r')

Тому що і файл, і список рядків є (в Python) ітерабелями, що повертають рядок. Дуже різні типи, той самий API (якщо хтось не намагається викликати методи файлів у списку, але це вже інша історія).

Ви можете умовно повернути listабо tuple( tupleє незмінним списком у python).

Формально навіть роблять:

def do_something():
    if ...: 
        return None
    return something_else

або:

function do_something(){
   if (...) return null; 
   return sth;
}

повертає різні типи, оскільки і python, Noneі Javascript - nullце типи самостійно.

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

При умовному поверненні об'єктів з різним API - це гарна ідея

Що стосується того, чи повернення різних API є хорошою ідеєю, IMO у більшості випадків не має сенсу. Єдиний розумний приклад, який спадає на думку, є чимось близьким до того, що сказав @MainMa : коли ваш API може надати різну кількість деталей, можливо, буде сенс повернути більше деталей, коли вони доступні.


4
Хороша відповідь. Варто зазначити, що еквівалент Java / статично типізований - це функція повертає інтерфейс, а не конкретний конкретний тип, інтерфейс представляє API / абстракцію.
mikera

1
Думаю, ви ниппекіруете, в Python список і кортеж можуть бути різних "типів", але вони одного і того ж "типу качки", тобто вони підтримують той самий набір операцій. І такі випадки саме тому були введені введення качок. Ви також можете робити довгий x = getInt () і в Java.
Каербер

"Повернення різних типів" означає "повернення об'єктів з різними API". (Деякі мови роблять те, як об’єкт побудований фундаментальною частиною API, деякі - ні. Це питання про те, наскільки виразною є система типу.) У прикладі списку / файлу do_fileбудь-який спосіб повертає ітерабельні рядки.
Жиль

1
У цьому прикладі Python ви також можете зателефонувати iter()як у списку, так і у файл, щоб переконатися, що результат може використовуватися лише як ітератор в обох випадках.
RemcoGerlich

1
повернення None or somethingє вбивцею для оптимізації продуктивності , виконану інструменти , такі як PyPy, Numba, Pyston і інші. Це один із Python-ізмів, які роблять python не надто швидким.
Метт

9

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

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

Але це короткий крок до того, щоб сказати "Я буду використовувати це для створення заводського шаблону " і повертаюсь fooабо, barабо bazзалежно від настрою функції. Налагодження цього стане кошмаром, коли абонент очікує, fooале йому дали bar.

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

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


6

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

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

Якщо ви хочете знати, де може працювати динамічний сценарій повернення, коли ви звикли працювати на мові статичного типу, розгляньте dynamicключове слово на C #. Роб Конери зміг успішно записати об'єктно-реляційний Mapper у 400 рядках коду за допомогою dynamicключового слова.

Звичайно, все dynamicдійсно полягає в обгортанні objectзмінної з певною безпекою типу виконання.


4

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


4

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

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

total = sum_of_array([20, 30, 'q', 50])
if (type_of(total) == Boolean) {
  display_error(...)
} else {
  record_number(total)
}

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

total = sum_of_array([20, 30, 'q', 50])
display_number(total)

Оскільки перший рядок повертається успішно, я припускаю, що він totalфактично містить суму масиву. Якщо вона не повертається успішно, ми переходимо на якусь іншу сторінку моєї програми.

Давайте скористаємось іншим прикладом, який стосується не лише поширення помилок. Можливо sum_of_array намагається бути розумним і повертати читабельну людину рядок у деяких випадках, наприклад "Це моя комбінація шаф!" якщо і тільки якщо масив дорівнює [11,7,19]. У мене виникають труднощі придумати хороший приклад. У будь-якому випадку стосується тієї ж проблеми. Ви повинні перевірити значення, що повертається, перш ніж ви зможете зробити щось з ним:

total = sum_of_array([20, 30, 40, 50])
if (type_of(total) == String) {
  write_message(total)
} else {
  record_number(total)
}

Ви можете стверджувати, що функція буде корисною для повернення цілого чи флоату, наприклад:

sum_of_array(20, 30, 40) -> int
sum_of_array(23.45, 45.67, 67.789044) -> float

Але наскільки ви не хочете, ці результати не є різними. Ви будете ставитися до них як до числа, і це все, що вам важливо. Отже sum_of_array повертає тип числа. Це те, про що йдеться у поліморфізмі.

Отже, є деякі практики, які ви можете порушити, якщо ваша функція може повертати кілька типів. Знаючи їх, ви допоможете визначити, чи повинна ваша функція повертати кілька типів.


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

4

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

Насправді методи на Java майже завжди повертають один із чотирьох типів: якийсь об’єкт чи nullвиняток, або вони взагалі ніколи не повертаються.

У багатьох мовах умови помилок моделюються як підпрограми, що повертають або тип результату, або тип помилки. Наприклад, у Scala:

def transferMoney(amount: Decimal): Either[String, Decimal]

Це, безумовно, дурний приклад. Тип повернення означає "або повернути рядок, або десятковий". За умовою, лівий тип - це тип помилки (у цьому випадку рядок із повідомленням про помилку), а правий - тип результату.

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


метод повернення виключення на Java видається дивним. « Повернення в виняток » ... хмм
комара

3
Виняток - один можливий результат методу на Java. Насправді я забув четвертий тип: Unit(метод взагалі не потрібно повертати). Правда, ви фактично не використовуєте returnключове слово, але це результат, який все-таки повертається з методу. З перевіреними винятками, він навіть прямо вказується в підписі типу.
Йорг W Міттаг

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

4
У багатьох мовах умови помилок моделюються як підпрограма, що повертає або тип результату, або тип помилки. Винятки є подібною ідеєю, за винятком того, що вони також є контрольними конструкціями потоку ( GOTOфактично за рівнем виражальної сили ).
Йорг W Міттаг

4

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

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

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

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


3

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

function ensure_fields($consideration)
{
        $args = func_get_args();
        foreach ( $args as $a ) {
                if ( !is_string($a) ) {
                        return NULL;
                }
                if ( !isset($consideration[$a]) || $consideration[$a]=='' ) {
                        return FALSE;
                }
        }

        return TRUE;
}

Функція номінально повертає BOOLEAN, однак повертає NULL на неправильному введенні. Зауважте, що починаючи з PHP 5.3, усі внутрішні функції PHP також ведуть себе таким чином . Крім того, деякі внутрішні функції PHP повертають FALSE або INT на номінальний вхід, див.

strpos('Hello', 'e');  // Returns INT(1)
strpos('Hello', 'q');  // Returns BOOL(FALSE)

4
І саме тому я ненавиджу PHP. Що ж, одна з причин.
Aaronaught

1
Зниження, тому що комусь не подобається мова, яку я професійно розвиваю?!? Почекайте, поки вони дізнаються, що я єврей! :)
dotancohen

3
Не завжди вважайте, що люди, які коментують, і люди, які звертаються з головою, - це ті самі люди.
Aaronaught

1
Я вважаю за краще мати виняток замість null, тому що (а) він виходить з ладу , тому швидше за все це буде фіксовано на фазі розробки / тестування; (б) з цим легко впоратися, тому що я можу зловити всі рідкісні / несподівані винятки один раз для вся моя програма (у моєму передньому контролері) та просто ввійдіть у систему (я зазвичай надсилаю електронні листи команді розробників), щоб потім це було виправлено. І я насправді ненавиджу, що стандартна бібліотека PHP в основному використовує підхід "return null" - це дійсно просто робить PHP-код більш схильним до помилок, якщо ви не перевіряєте все isset(), що є занадто великим тягарем.
сценарій

1
Крім того, якщо ви повернетесь nullдо помилки, будь ласка, будь ласка, уточніть це в docblock (наприклад @return boolean|null), тому, якщо я знайду ваш код якийсь день, мені не доведеться перевіряти функцію / метод методу.
сценарій

3

Я не думаю, що це погана ідея! На відміну від цієї найпоширенішої думки, і, як уже зазначав Роберт Харві, мови, які статистично набирали тип, як Java, ввели Generics саме для таких ситуацій, як та, про яку ви запитуєте. Насправді Java намагаються зберігати (де це можливо) безпеку типу під час компіляції, а іноді Generics уникає дублювання коду, чому? Тому що ви можете написати один і той же метод або той самий клас, який обробляє / повертає різні типи . Я зроблю лише дуже короткий приклад, щоб показати цю ідею:

Java 1.4

public static Boolean getBoolean(String property){
    return (Boolean) properties.getProperty(property);
}
public static Integer getInt(String property){
    return (Integer) properties.getProperty(property);
}

Java 1.5+

public static <T> getValue(String property, Class<T> clazz) throws WhateverCheckedException{
    return clazz.getConstructor(String.class).newInstance(properties.getProperty(property));
}
//the call will be
Boolean b = getValue("useProxy",Boolean.class);
Integer b = getValue("proxyPort",Integer.class);

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


Ще один голос без пояснень! Дякую спільноті SE!
thermz

2
Майте симпатію +1. Спробуйте відкрити свою відповідь більш чіткою та прямою відповіддю та утримуйтесь від коментування різних відповідей. (Я дав би вам ще +1, оскільки я з вами згоден, але мені є лише один.)
DougM

3
Дженріки не існує в динамічно набраних мовах (які я знаю). Причин для них не було б. Більшість дженериків - це окремий випадок, коли "контейнер" цікавіший, ніж те, що в ньому (саме тому деякі мови, як-от Java, насправді використовують стирання типу). Родовий тип насправді є власним типом. Загальні методи , без фактичного загального типу, як у вашому прикладі тут, майже завжди тільки синтаксис для правонаступника-і-відкидати семантичними. Не особливо переконливий приклад того, про що насправді задається питання.
Aaronaught

1
Я намагаюся бути трохи синтетичнішим: "Оскільки навіть на мові, що має статичний тип, для вирішення цієї проблеми були представлені дженерики, явно натяк, що написання функції, яка повертає різні типи динамічною мовою, не є поганою ідеєю". Краще зараз? Перевірте відповідь Роберта Харві або коментар BRPocock, і ви зрозумієте, що приклад
Generics

1
Не відчувай себе погано, @thermz. Часописи часто говорять більше про читача, ніж про письменника.
Карл Білефельдт

2

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


1

Perl дуже використовує це, оскільки те, що функція робить, залежить від її контексту . Наприклад, функція може повернути масив, якщо він використовується в контексті списку, або довжину масиву, якщо він використовується в місці, де очікується скалярне значення. З підручника, який є першим зверненням до "контексту perl" , якщо ви зробите це:

my @now = localtime();

Тоді @now є змінною масиву (саме це означає @) і міститиме масив типу (40, 51, 20, 9, 0, 109, 5, 8, 0).

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

my $now = localtime();

то це робить щось зовсім інше: $ now стане чимось на кшталт "Пт 9 січня 20:51:40 2009".

Ще один приклад, який я можу придумати, - це реалізація API REST, де формат повернутого файлу залежить від того, що хоче клієнт. Наприклад, HTML або JSON або XML. Хоча технічно це всі потоки байтів, ідея схожа.


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

За збігом обставин я маю справу з цим API Java Rest, який я будую. Я дав можливість одному ресурсу повернути або XML, або JSON. Найнижчий загальний тип знаменника у цьому випадку - a String. Зараз люди просять документацію про тип повернення. Інструменти java можуть генерувати його автоматично, якщо я повертаю клас, але, оскільки я вибрав, Stringне можу генерувати документацію автоматично. Задумливо / морально розповідаючи, я хотів би, щоб я повертав один тип за методом.
Даніель Каплан

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

1

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

function ThingyWrapper(thingy){ //a function constructor (class-like thingy)

    //thingy is effectively private and persistent for ThingyWrapper instances

    if(typeof thingy === 'array'){
        this.alertItems = function(){
            thingy.forEach(function(el){ alert(el); });
        }
    }
    else {
        this.alertItems = function(){
            for(var x in thingy){ alert(thingy[x]); }
        }
    }
}

function gimmeThingy(){
    var
        coinToss = Math.round( Math.random() ),//gives me 0 or 1
        arrayThingy = [1,2,3],
        objectThingy = { item1:1, item2:2, item3:3 }
    ;

    //0 dynamically evaluates to false in JS
    return new ThingyWrapper( coinToss ? arrayThingy : objectThingy );
}

gimmeThingy().alertItems(); //should be same every time except order of numbers - maybe

Іноді може мати сенс взагалі виводити різні типи без загальної обгортки, але, чесно кажучи, за 7 років написання JS це не те, що я вважав, що це було розумно чи зручно робити дуже часто. Здебільшого це я б робив в умовах закритих середовищ, як інтер'єр об'єкта, де речі збираються. Але це не те, що я робив досить часто, щоб будь-які приклади стрибали на думку.

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

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