Мой основной язык статически типизирован (Java). В Java вы должны возвращать один тип из каждого метода. Например, у вас не может быть метода, который условно возвращает String
или условно возвращает Integer
. Но в JavaScript, например, это очень возможно.
На статически типизированном языке я понимаю, почему это плохая идея. Если возвращается каждый метод Object
(общий родительский элемент, от которого наследуются все классы), то вы и компилятор понятия не имеете, с чем имеете дело. Вы должны будете обнаружить все свои ошибки во время выполнения.
Но в динамически типизированном языке может даже не быть компилятора. В динамически типизированном языке для меня не очевидно, почему функция, которая возвращает несколько типов, является плохой идеей. Мой опыт работы со статическими языками заставляет меня избегать написания таких функций, но я боюсь, что я слишком внимателен к функции, которая может сделать мой код чище так, как я его не вижу.
Изменить : я собираюсь удалить свой пример (пока я не могу придумать лучшего). Я думаю, что это заставляет людей отвечать на вопрос, который я не пытаюсь сделать.
источник
(coerce var 'string)
выходы astring
или(concatenate 'string this that the-other-thing)
аналогично. Я написал такие вещи,ThingLoader.getThingById (Class<extends FindableThing> klass, long id)
как хорошо. И там я могу вернуть только то, что подклассы того, что вы просили:loader.getThingById (SubclassA.class, 14)
может вернуть то,SubclassB
что расширяетсяSubclassA
...Ответы:
В отличие от других ответов, существуют случаи, когда возврат различных типов является приемлемым.
Пример 1
В некоторых статически типизированных языках это связано с перегрузками, поэтому мы можем считать, что существует несколько методов, каждый из которых возвращает предопределенный фиксированный тип. В динамических языках это может быть та же функция, реализованная как:
Та же функция, разные типы возвращаемого значения.
Пример 2
Представьте, что вы получаете ответ от компонента OpenID / OAuth. Некоторые поставщики OpenID / OAuth могут содержать больше информации, например, возраст человека.
У других будет минимум, будь то адрес электронной почты или псевдоним.
Опять та же функция, разные результаты.
Здесь преимущество возврата различных типов особенно важно в контексте, где вас не интересуют типы и интерфейсы, но какие объекты на самом деле содержат. Например, давайте представим, что сайт содержит зрелый язык. Тогда
findCurrent()
можно использовать так:Реорганизация этого кода в код, где у каждого провайдера будет своя собственная функция, которая будет возвращать четко определенный фиксированный тип, не только ухудшит базу кода и приведет к дублированию кода, но также не принесет никакой выгоды. Можно в конечном итоге делать ужасы, как:
источник
sum
примера.sum :: Num a => [a] -> a
. Вы можете суммировать список всего, что является числом. В отличие от javascript, если вы попытаетесь сложить что-то, что не является числом, ошибка будет обнаружена во время компиляции.Iterator[A]
есть методdef sum[B >: A](implicit num: Numeric[B]): B
, который снова позволяет суммировать любые числа и проверяется во время компиляции.+
строки (путем реализацииNum
, что является ужасной идеей, но законно), либо изобрести другой оператор / функцию, перегруженный целыми числами и строками. соответственно. У Haskell есть специальный полиморфизм через классы типов. Список, содержащий как целые числа, так и строки, гораздо сложнее (возможно, без языковых расширений), но это совсем другой вопрос.null
значениями.В целом, это плохая идея по тем же причинам, что моральный эквивалент в статически типизированном языке - плохая идея: вы не знаете, какой конкретный тип возвращается, поэтому вы не знаете, что можно сделать с результатом (кроме несколько вещей, которые можно сделать с любой ценностью). В системе статических типов у вас есть проверенные компилятором аннотации возвращаемых типов и тому подобное, но в динамическом языке все еще существуют те же знания - они просто неформальны и хранятся в мозгах и документации, а не в исходном коде.
Во многих случаях, однако, есть рифма и причина, по которой возвращается тип, и эффект аналогичен перегрузке или параметрическому полиморфизму в системах статического типа. Другими словами, тип результата является предсказуемым, это просто не так просто выразить.
Но обратите внимание, что могут быть другие причины, по которым конкретная функция плохо спроектирована: например,
sum
функция, возвращающая ложное значение на недопустимых входах, является плохой идеей, прежде всего потому, что это возвращаемое значение бесполезно и подвержено ошибкам (0 <-> ложная путаница).источник
NaN
это "контрапункт" вообще. NaN на самом деле является допустимым входным значением для любого вычисления с плавающей запятой. Вы можете сделать все,NaN
что вы могли бы сделать с фактическим числом. Правда, конечный результат указанного расчета не может быть очень полезным для вас, но это не приведет к странным ошибкам во время выполнения , и вам не нужно проверять его все время - вы можете просто проверить один раз в конце серия расчетов. NaN - это не другой тип , это просто специальное значение , вроде нулевого объекта .NaN
как и нулевой объект, он имеет тенденцию скрываться при возникновении ошибок, только чтобы они появлялись позже. Например, ваша программа может взорваться, если онаNaN
попадет в условный цикл.NaN
значение (а) на самом деле не является другим типом возвращаемого значения и (б) имеет четко определенную семантику с плавающей запятой, и поэтому действительно не может рассматриваться как аналог возвращаемого значения с переменной типизацией. Это не так уж и отличается от нуля в целочисленной арифметике; если ноль «случайно» проскальзывает в ваших вычислениях, то вы, скорее всего, в итоге получите либо ноль в результате, либо ошибку деления на ноль. Являются ли значения "бесконечности", определяемые с плавающей точкой IEEE, также злыми?В динамических языках не нужно спрашивать, возвращать ли разные типы, но объекты с разными API . Поскольку большинство динамических языков на самом деле не заботятся о типах, они используют различные варианты типизации утиных команд .
При возврате разных типов имеет смысл
Например, этот метод имеет смысл:
Потому что и файл, и список строк являются (в Python) итераторами, которые возвращают строку. Очень разные типы, один и тот же API (если кто-то не пытается вызывать файловые методы в списке, но это другая история).
Вы можете вернуть условно
list
илиtuple
(tuple
это неизменный список в Python).Формально даже занимаюсь
или же:
возвращает разные типы, так как и Python,
None
и Javascriptnull
являются типами сами по себе.Все эти варианты использования будут иметь аналог в статических языках, функция просто вернет правильный интерфейс.
При возврате объектов с разными API условно это хорошая идея
Что касается того, является ли возвращение разных API хорошей идеей, IMO в большинстве случаев не имеет смысла. Единственный разумный пример, который приходит на ум, - это что-то близкое к тому, что сказал @MainMa : когда ваш API может предоставлять различное количество деталей, может иметь смысл возвращать больше деталей, когда они доступны.
источник
do_file
в любом случае возвращается итерируемая строка.iter()
как список, так и файл, чтобы убедиться, что результат может использоваться только в качестве итератора в обоих случаях.None or something
является убийцей для оптимизации производительности, выполняемой такими инструментами, как PyPy, Numba, Pyston и другими. Это один из принципов Python, который делает Python не очень быстрым.Ваш вопрос заставляет меня хотеть немного поплакать. Не для примера использования, который вы предоставили, а потому, что кто-то невольно зайдет слишком далеко. Это в нескольких шагах от смехотворно не поддерживаемого кода.
Тип случая использования условия ошибки имеет смысл, и нулевой шаблон (все должно быть шаблоном) в статически типизированных языках делает то же самое. Ваш вызов функции возвращает
object
или возвращаетnull
.Но это небольшой шаг , чтобы сказать : «Я буду использовать это , чтобы создать шаблон фабрики » и возвращать либо
foo
илиbar
или вbaz
зависимости от настроения функции. Отладка этого станет кошмаром, когда звонящий ожидает,foo
но ему далиbar
.Так что я не думаю, что вы закрыты. Вы должны быть осторожны в использовании возможностей языка.
Раскрытие информации: мой опыт работы со статически типизированными языками, и я обычно работал в больших, разнообразных командах, где потребность в поддерживаемом коде была довольно высокой. Так что моя точка зрения, вероятно, искажена.
источник
Использование Generics в Java позволяет вам возвращать другой тип, сохраняя при этом безопасность статического типа. Вы просто указываете тип, который вы хотите вернуть, в параметре универсального типа вызова функции.
Можно ли использовать подобный подход в Javascript - это, конечно, открытый вопрос. Поскольку Javascript является языком с динамической типизацией, возвращение
object
представляется очевидным выбором.Если вы хотите знать, где может работать сценарий динамического возврата, когда вы привыкли работать на языке со статической типизацией, подумайте над поиском
dynamic
ключевого слова в C #. Роб Конери смог успешно написать объектно-реляционный маппер в 400 строках кода, используяdynamic
ключевое слово.Конечно, все, что на
dynamic
самом деле делает, - это оборачиваетobject
переменную с некоторой безопасностью типа времени выполненияисточник
Думаю, плохая идея возвращать разные типы условно. Один из способов, который часто встречается для меня, заключается в том, что функция может возвращать одно или несколько значений. Если требуется вернуть только одно значение, может показаться разумным просто вернуть значение, а не упаковывать его в массив, чтобы избежать необходимости распаковывать его в вызывающей функции. Тем не менее, это (и большинство других случаев этого) накладывает на вызывающего абонента обязанность различать и обрабатывать оба типа. Функция будет легче рассуждать, если она всегда возвращает один и тот же тип.
источник
«Плохая практика» существует независимо от того, типизирован ли ваш язык. Статический язык делает больше, чтобы отвлечь вас от этих методов, и вы можете найти больше пользователей, которые жалуются на «плохую практику» в статическом языке, потому что это более формальный язык. Однако основные проблемы существуют в динамическом языке, и вы можете определить, являются ли они обоснованными.
Вот нежелательная часть того, что вы предлагаете. Если я не знаю, какой тип возвращается, я не могу сразу использовать возвращаемое значение. Я должен "открыть" что-то об этом.
Часто такой вид переключения в коде просто плохая практика. Это делает код сложнее для чтения. В этом примере вы видите, почему выбрасывание и отлов исключений очень популярны. Другими словами: если ваша функция не может делать то, что она говорит, она не должна возвращаться успешно . Если я вызываю вашу функцию, я хочу сделать это:
Поскольку первая строка успешно возвращается, я предполагаю, что на
total
самом деле содержит сумму массива. Если он не возвращается успешно, мы переходим на другую страницу моей программы.Давайте использовать другой пример, который не только о распространении ошибок. Возможно, sum_of_array пытается быть умным и в некоторых случаях возвращает читабельную строку, например «Это моя комбинация локеров!» тогда и только тогда, когда массив [11,7,19]. У меня проблемы с придумыванием хорошего примера. Во всяком случае, та же проблема применима. Вы должны проверить возвращаемое значение, прежде чем что-либо делать с ним:
Вы можете утверждать, что было бы полезно, чтобы функция возвращала целое число или число с плавающей точкой, например:
Но эти результаты не являются различными типами, насколько вы обеспокоены. Ты будешь относиться к ним как к числам, и это все, что тебя волнует. Поэтому sum_of_array возвращает тип числа. Вот что такое полиморфизм.
Так что есть некоторые практики, которые вы можете нарушать, если ваша функция может возвращать несколько типов. Знание их поможет вам определить, должна ли ваша конкретная функция возвращать несколько типов в любом случае.
источник
На самом деле, довольно часто возвращаются различные типы даже в статически типизированном языке. Вот почему, например, у нас есть типы союзов.
Фактически, методы в Java почти всегда возвращают один из четырех типов: какой-то объект или
null
исключение, или они никогда не возвращаются вообще.Во многих языках условия ошибок моделируются как подпрограммы, возвращающие либо тип результата, либо тип ошибки. Например, в Scala:
Это глупый пример, конечно. Тип возврата означает «либо вернуть строку, либо десятичную дробь». По соглашению левый тип - это тип ошибки (в данном случае строка с сообщением об ошибке), а правый тип - тип результата.
Это похоже на исключения, за исключением того факта, что исключения также являются конструкциями потока управления. На самом деле они эквивалентны по выразительной силе
GOTO
.источник
Unit
(метод не требуется возвращать вообще). Правда, вы на самом деле не используетеreturn
ключевое слово, но, тем не менее, это результат, возвращаемый методом. За проверенными исключениями это даже явно упоминается в сигнатуре типа.GOTO
фактически равными по выразительности ).Ни в одном ответе еще не упоминаются твердые принципы. В частности, вы должны следовать принципу подстановки Лискова, что любой класс, получающий тип, отличный от ожидаемого, может по-прежнему работать с тем, что он получает, без каких-либо действий для проверки того, какой тип возвращается.
Таким образом, если вы добавляете некоторые дополнительные свойства к объекту или оборачиваете возвращаемую функцию каким-то декоратором, который все еще выполняет то, для чего предназначалась исходная функция, у вас все хорошо, если никакой код, вызывающий вашу функцию, никогда не полагается на это поведение, в любом пути кода.
Вместо того, чтобы возвращать строку или целое число, лучшим примером может быть возвращение спринклерной системы или кота. Это нормально, если весь вызывающий код собирается сделать, это вызвать functionInQuestion.hiss (). Фактически у вас есть неявный интерфейс, который ожидает вызывающий код, и динамически типизированный язык не заставит вас сделать интерфейс явным.
К сожалению, ваши коллеги, вероятно, так и сделают, поэтому вам, вероятно, придется все равно выполнять ту же работу в вашей документации, за исключением того, что не существует общепринятого, краткого, машинно-анализируемого способа сделать это - как это бывает, когда вы определяете интерфейс на языке, который их имеет.
источник
Единственное место, где я вижу себя отправляющим разные типы, - для неверного ввода или «исключений для бедняков», где «исключительное» условие не очень исключительное. Например, из моего репозитория служебных функций PHP этот сокращенный пример:
Функция номинально возвращает BOOLEAN, однако она возвращает NULL при неверном вводе. Обратите внимание, что начиная с PHP 5.3, все внутренние функции PHP ведут себя так же . Кроме того, некоторые внутренние функции PHP возвращают FALSE или INT при номинальном вводе, см .:
источник
null
, потому что (а) оно громко терпит неудачу , так что с большей вероятностью оно будет исправлено на этапе разработки / тестирования, (б) его легко обработать, потому что я могу поймать все редкие / неожиданные исключения один раз для все мое приложение (в моем фронт-контроллере) и просто зарегистрируйте его (я обычно отправляю электронные письма команде разработчиков), так что это можно исправить позже. И я на самом деле ненавижу эту стандартную библиотеку PHP, которая в основном использует подход «вернуть ноль» - она просто делает код PHP более подверженным ошибкам, если вы не проверяете всеisset()
, что является слишком большой нагрузкой.null
в случае ошибки, пожалуйста, укажите это в блоке документации (например@return boolean|null
), поэтому, если я когда-нибудь найду ваш код, мне не придется проверять тело функции / метода.Я не думаю, что это плохая идея! В отличие от этого наиболее распространенного мнения и, как уже указывалось Робертом Харви, статически типизированные языки, такие как Java, ввели Generics именно для ситуаций, подобных той, о которой вы спрашиваете. На самом деле Java старается поддерживать (где это возможно) безопасность типов во время компиляции, а иногда Generics избегает дублирования кода, почему? Потому что вы можете написать тот же метод или тот же класс, который обрабатывает / возвращает разные типы . Я приведу очень краткий пример, чтобы показать эту идею:
Java 1.4
Java 1.5+
В динамически типизированном языке, поскольку у вас нет безопасности типов во время компиляции, вы абсолютно свободны в написании того же кода, который работает для многих типов. Поскольку даже в языке со статической типизацией для решения этой проблемы были введены средства Generics, очевидно, что написание функции, возвращающей разные типы в динамическом языке, - неплохая идея.
источник
Разработка программного обеспечения - это искусство и ремесло управления сложностью. Вы пытаетесь сузить систему в тех точках, которые можете себе позволить, и ограничить возможности в других точках. Интерфейс функции - это контракт, который помогает управлять сложностью кода, ограничивая знания, необходимые для работы с любым фрагментом кода. Возвращая различные типы, вы значительно расширяете интерфейс функции, добавляя к ней все интерфейсы различных типов, которые вы возвращаете, и добавляя неочевидные правила относительно того, какой интерфейс возвращается.
источник
Perl часто использует это, потому что то, что делает функция, зависит от ее контекста . Например, функция может вернуть массив, если используется в контексте списка, или длину массива, если используется в месте, где ожидается скалярное значение. Из учебника, который является первым хитом для "контекста Perl" , если вы делаете:
Тогда @now является переменной массива (это то, что означает @) и будет содержать массив типа (40, 51, 20, 9, 0, 109, 5, 8, 0).
Если вместо этого вы вызываете функцию таким образом, что ее результатом должен быть скаляр, с помощью (переменные $ являются скалярами):
тогда он делает что-то совершенно другое: $ now будет что-то вроде «Пт 9 января 20:51:40 2009».
Другой пример, который я могу вспомнить, - реализация REST API, где формат возвращаемого значения зависит от того, что хочет клиент. Например, HTML или JSON или XML. Хотя технически это все потоки байтов, идея похожа.
источник
String
. Теперь люди просят документацию по типу возврата. Инструменты Java могут генерировать его автоматически, если я возвращаю класс, но, поскольку я выбрал,String
я не могу сгенерировать документацию автоматически. В ретроспективе / морали истории, я хотел бы вернуть один тип для метода.В динамическом мире это все о наборе утки. Самое ответственное, что можно сделать публично или публично, - это обернуть потенциально разные типы в оболочку, которая дает им одинаковый интерфейс.
Иногда может иметь смысл выкачивать разные типы вообще без универсальной обертки, но, честно говоря, за 7 лет написания JS я не нашел, что это было бы разумно или удобно делать очень часто. В основном это то, что я бы делал в контексте замкнутых сред, таких как внутренняя часть объекта, где все складывается. Но это не то, что я делал достаточно часто, чтобы вспомнить любые примеры.
В основном, я бы посоветовал вам вообще перестать думать о типах. Вы имеете дело с типами, когда вам нужно на динамическом языке. Больше не надо. В этом весь смысл. Не проверяйте тип каждого отдельного аргумента. Это то, что я хотел бы сделать только в среде, где тот же метод может дать противоречивые результаты неочевидными способами (поэтому определенно не делайте что-то подобное). Но важен не тот тип, а то, что ты мне даешь.
источник