Является ли плохой идеей возвращать разные типы данных из одной функции в динамически типизированном языке?


65

Мой основной язык статически типизирован (Java). В Java вы должны возвращать один тип из каждого метода. Например, у вас не может быть метода, который условно возвращает Stringили условно возвращает Integer. Но в JavaScript, например, это очень возможно.

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

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


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


Почему бы не выбросить исключение в случае ошибки?
TrueWill

1
@ Правда, я рассмотрю это в следующем предложении.
Даниэль Каплан

Вы заметили, что это обычная практика в более динамичных языках? Это распространено в функциональных языках, но там правила сделаны очень явными. И я знаю, что обычно, особенно в JavaScript, разрешается использовать аргументы разных типов (поскольку это, по сути, единственный способ перегрузки функций), но я редко видел, чтобы это применялось к возвращаемым значениям. Единственный пример, который я могу вспомнить, - это PowerShell с его в основном автоматическим переносом / развёртыванием массивов, а языки сценариев - исключительный случай.
Aaronaught

3
Не упоминается, но есть много примеров (даже в Java Generics) функций, которые принимают возвращаемый тип в качестве параметра; например, в Common Lisp мы имеем (coerce var 'string)выходы a 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
Динамически типизированные языки похожи на « Ложку» в фильме «Матрица» . Не пытайтесь определить ложку как строку или число . Это было бы невозможно. Вместо этого ... только попытайтесь понять правду. Что нет ложки .
Reactgular

Ответы:


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, что является ужасной идеей, но законно), либо изобрести другой оператор / функцию, перегруженный целыми числами и строками. соответственно. У Haskell есть специальный полиморфизм через классы типов. Список, содержащий как целые числа, так и строки, гораздо сложнее (возможно, без языковых расширений), но это совсем другой вопрос.

2
Я не особенно впечатлен вашим вторым примером. Эти два объекта являются одним и тем же концептуальным «типом», просто в некоторых случаях определенные поля не определены или не заполнены. Вы можете представить это прекрасно даже в статически типизированном языке со nullзначениями.
Aaronaught

31

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

Во многих случаях, однако, есть рифма и причина, по которой возвращается тип, и эффект аналогичен перегрузке или параметрическому полиморфизму в системах статического типа. Другими словами, тип результата является предсказуемым, это просто не так просто выразить.

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


40
Мои два цента: я ненавижу, когда это происходит. Я видел библиотеки JS, которые возвращают ноль, когда результатов нет, объект для одного результата и массив для двух или более результатов. Таким образом, вместо того, чтобы просто циклически проходить по массиву, вы вынуждены определить, является ли он нулевым, и если нет, является ли он массивом, и если это так, сделать одно, иначе сделать что-то другое. Тем более, что в обычном смысле любой здравомыслящий разработчик собирается просто добавить объект в массив и перезапустить логику программы из обработки массива.
phyrfox

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

5
@Aaronaught С другой стороны, NaNкак и нулевой объект, он имеет тенденцию скрываться при возникновении ошибок, только чтобы они появлялись позже. Например, ваша программа может взорваться, если она NaNпопадет в условный цикл.
Изката

2
@Izkata: NaN и null ведут себя совершенно по-разному. Нулевая ссылка взорвется сразу же после доступа, NaN распространяется. Почему последнее происходит, очевидно, оно позволяет вам писать математические выражения, не проверяя результат каждого подвыражения. Лично я считаю ноль гораздо более разрушительным, потому что NaN представляет собой правильную числовую концепцию, без которой невозможно обойтись, и, математически, распространение - это правильное поведение.
Фоши

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 / абстракцию.
Микера

1
Я думаю, что вы придирчивы, в Python список и кортеж могут быть разных «типов», но они имеют одинаковый «тип утки», т.е. они поддерживают один и тот же набор операций. И именно в таких случаях именно поэтому была введена утка. Вы можете сделать long x = getInt () в Java тоже.
Kaerber

«Возвращение разных типов» означает «возвращение объектов с разными 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 #. Роб Конери смог успешно написать объектно-реляционный маппер в 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ключевое слово, но, тем не менее, это результат, возвращаемый методом. За проверенными исключениями это даже явно упоминается в сигнатуре типа.
Йорг Миттаг

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

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

4

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

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

Вместо того, чтобы возвращать строку или целое число, лучшим примером может быть возвращение спринклерной системы или кота. Это нормально, если весь вызывающий код собирается сделать, это вызвать functionInQuestion.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, которая в основном использует подход «вернуть ноль» - она ​​просто делает код PHP более подверженным ошибкам, если вы не проверяете все isset(), что является слишком большой нагрузкой.
scriptin

1
Кроме того, если вы вернетесь nullв случае ошибки, пожалуйста, укажите это в блоке документации (например @return boolean|null), поэтому, если я когда-нибудь найду ваш код, мне не придется проверять тело функции / метода.
scriptin

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
Я стараюсь быть немного более синтаксическим: «Поскольку даже для статически типизированного языка были введены средства Generics для решения этой проблемы, ясно, что написание функции, которая возвращает разные типы в динамическом языке, не является плохой идеей». Теперь лучше? Посмотрите ответ Роберта Харви или комментарий BRPocock, и вы поймете, что пример Обобщения связан с этим вопросом
thermz

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

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


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

По стечению обстоятельств я имею дело с API Java Rest, который я создаю. Я дал возможность одному ресурсу возвращать либо XML, либо JSON. Самый низкий тип общего знаменателя в этом случае - 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.