Как получить тип возвращаемого значения функции
Перейти к содержимому

Как получить тип возвращаемого значения функции

  • автор:

 

Возвращаемые значения функций

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

Необходимые навыки: Базовая компьютерная грамотность, знание основ HTML и CSS, JavaScript first steps, Functions — reusable blocks of code.
Цели: Понять что такое возвращаемое значение функции и как его использовать.

Что из себя представляют возвращаемые значения?

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

Мы уже видели этот блок кода в нашей первой статье про функции. Мы вызываем функцию replace() на строке myText и передаём ей 2 параметра — заменяемую подстроку и подстроку, которой будем заменять. Когда функция завершит выполнение, она вернёт значение, которым является новая строка со сделанными в ней заменами. В коде выше мы сохраняем это возвращаемое значение как значение переменной newString .

Если вы посмотрите на функцию replace() на MDN reference page, вы увидите секцию под названием Return value. Очень важно знать и понимать какие значения возвращаются функциями, так что мы пытаемся включать эту информацию везде, где это возможно.

Некоторые функции не возвращают значения( на наших reference pages, возвращаемое значение обозначено как void или undefined в таких случаях). Например, в функции displayMessage() которую мы сделали в прошлой статье, в результате выполнения функции не возвращается никакого значения. Функция всего лишь отображает что-то где-то на экране.

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

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

Чтобы вернуть значение своей функции, вы должны использовать ключевое слово return. Мы видели это в действии недавно — в нашем примере random-canvas-circles.html. Наша функция draw() отрисовывает где-то на экране 100 случайных кружков.

Внутри каждой итерации есть 3 вызова функции random() . Это сделано чтобы сгенерировать случайное значение для текущей координаты x, y и для радиуса. Функция random() принимает 1 параметр (целое число) и возвращает случайное число в диапазоне от 0 до этого числа. Выглядит это вот так:

Тоже самое может быть написано вот так:

Но первую версию написать быстрее и она более компактна.

Мы возвращаем результат вычисления Math.floor(Math.random()*number) каждый раз когда функция вызывается. Это возвращаемое значение появляется в момент вызова функции и код продолжается. Так, например, если мы выполним следующую строчку:

и 3 вызова random() вернут значения 500, 200 и 35, соответственно, строчка будет выполнена как если бы она была такой:

Сначала выполняются вызовы функции random() , на место которых подставляются возвращаемые ей значения, а затем выполнятся сама строка.

Активное обучение: наша собственная, возвращающая значение функция

Теперь напишем нашу собственную возвращающую значение функцию.

  1. Первым делом, сделайте локальную копию файла function-library.html из GitHub. Это простая HTML страничка, содержащая текстовое поле <input> и параграф Также там есть элемент <script> в котором мы храним в 2ух переменных ссылки на оба HTML-элемента. Это маленькая страничка позволит вам ввести число в text box и отобразит различные, относящиеся к нему числа в параграфе ниже.
  2. Теперь добавим несколько полезных функций в элемент <script> . Ниже двух существующих строчек JavaScript, добавьте следующие описания функций:

Примечание: Если у вас проблемы с работой данного примера, не стесняйтесь сверить ваш код с работающей версией finished version on GitHub (или смотрите живой пример), или спросите нас.

К этому моменту мы хотели бы чтобы вы написали парочку собственных функций и добавили их в библиотеку. Как на счёт квадратного или кубического корня числа или длины окружности круга с длиной радиуса равной числу num ?

Это упражнение привнесло парочку важных понятий в изучении того, как использовать ключевое слово return . В дополнение:

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

Заключение

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

Если в статье есть что-то что вы не поняли, не стесняйтесь перечитать статью ещё раз или свяжитесь с нами для получения помощи.

Как получить тип аргумента функции или ее возвращаемый тип?

Очередной странный вопрос. Навеян книгой Discovering Modern C++ Готтшлига. Пытаюсь написать шаблон, который бы возвращал функтор, применяющий некоторую функцию рекурсивно предопределенное количество раз. Примерно так:

Так как использование в духе

душу не греет, сделал такую функцию:

Функция сама по переданным значениям выводит типы, так что можно вычислять как

Но мне не понравилось, что каждый раз создается объект Recurs. . Хотелось бы получать его один раз, и вызывать как функтор — типа,

И вот тут у меня затык. Я написал примерно так —

но что мне теперь писать вместо . . В принципе, меня устроит в данном случае и тип аргумента f , и тип ее возвращаемого значения (для рекурсии они должны совпадать), но как их записать?

И еще — насколько я помню, decltype(auto) — это уже C++14. А можно ли такое сотворить на версиях языка помладше, и как?

8.8 – Вывод типа для функций

Когда эта функция скомпилируется, компилятор определит, что x + y вычисляется как int , а затем убедится, что тип возвращаемого значения соответствует объявленному типу возвращаемого значения функции (или что тип возвращаемого значения может быть преобразован в объявленный тип возвращаемого значения).

Поскольку компилятор уже должен вывести тип возвращаемого значения из инструкции return , в C++ 14 ключевое слово auto было расширено, чтобы иметь возможность выводить тип возвращаемого значения функции из инструкций возврата в теле функции. Это работает за счет использования ключевого слова auto вместо типа возвращаемого значения функции.

Поскольку x + y вычисляется как int , компилятор определит, что эта функция должна иметь тип возвращаемого значения int .

При использовании возвращаемого типа auto все инструкции return должны возвращать один и тот же тип, в противном случае возникнет ошибка. Например:

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

В отличие от вывода типа для объектов, не существует единого мнения о лучших практиках использования вывода типа, возвращаемого функцией. При использовании вывода типа с объектами инициализатор всегда присутствует как часть той же инструкции, поэтому обычно не слишком трудно определить, какой тип будет выводиться. С функциями дело обстоит иначе – при просмотре прототипа функции нет контекста, который помог бы указать, какой тип она возвращает. Хорошая IDE для программирования должна четко указывать, какой выведен тип для функции, но при отсутствии этого функционала пользователю фактически придется копаться в самом теле функции, чтобы определить, какой тип она возвращает. Вероятность совершения ошибки здесь повышается.

Лучшая практика

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

Синтаксис, завершающийся возвращаемым типом

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

Рассмотрим следующую функцию:

Используя auto , его можно было бы записать как:

В этом случае auto не выполняет вывод типа – это просто часть синтаксиса, использующего завершение типом возвращаемого значения.

Зачем вам это нужно?

Приятно, когда все имена функций выстраиваются в одну линию:

Синтаксис, завершающийся возвращаемым типом, также требуется для некоторых расширенных функций C++, таких как лямбда-выражения (которые мы рассмотрим в уроке «11.13 – Введение в лямбды (анонимные функции)»).

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

Вывод типа не может использоваться для типов параметров функции

Многие начинающие программисты пробуют писать что-то вроде этого:

К сожалению, вывод типа не работает для параметров функции, и до C++20 показанная выше программа не компилируется (вы получите сообщение об ошибке, что параметры функции не могут иметь тип auto ).

В C++20 ключевое слово auto было расширено, чтобы указанная выше программа компилировалась и работала правильно, однако в этом случае auto не вызывает вывод типа. Скорее, оно запускает другую функцию, называемую шаблонами функций, которая была разработана для обработки таких случаев.

Связанный контент

Мы рассмотрим шаблоны функций в уроке «8.13 – Шаблоны функций» и обсудим использование auto в контексте шаблонов функций в уроке «8.15 – Шаблоны функций с несколькими шаблонными типами».

Карманная книга по TypeScript. Часть 4. Подробнее о функциях

image

Обратите внимание: для большого удобства в изучении книга была оформлена в виде прогрессивного веб-приложения.

Функции — это основные строительные блоки любого приложения, будь то функции, импортируемые из другого модуля, или методы класса. В TS существует несколько способов описания того, как фукнции вызываются.

Тип функции в форме выражения (function type expressions)

Простейшим способом описания типа функции является выражение. Такие типы похожи на стрелочные функции:

Выражение (a: string) => void означает «функция с одним параметром a типа string , которая ничего не возвращает». Как и в случае с определением функции, если тип параметра не указан, он будет иметь значение any .

Обратите внимание: название параметра является обязательным. Тип функции (string) => void означает «функция с параметром string типа any «!

Разумеется, для типа функции можно использовать синоним:

Сигнатуры вызова (call signatures)

В JS функции, кроме того, что являются вызываемыми (callable), могут иметь свойства. Однако, тип-выражение не позволяет определять свойства функции. Для описания вызываемой сущности (entity), обладающей некоторыми свойствами, можно использовать сигнатуру вызова (call signature) в объектном типе:

Обратите внимание: данный синтаксис немного отличается от типа-выражения функции — между параметрами и возвращаемым значением используется : вместо => .

Сигнатуры конструктора (construct signatures)

Как известно, функции могут вызываться с ключевым словом new . TS считает такие функции конструкторами, поскольку они, как правило, используются для создания объектов. Для определения типов таких функций используется сигнатура конструктора:

Некоторые объекты, такие, например, как объект Date , могут вызываться как с, так и без new . Сигнатуры вызова и конструктора можно использовать совместно:

Общие функции или функции-дженерики (generic functions)

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

Функция делают свою работу, но, к сожалению, типом возвращаемого значения является any . Было бы лучше, если бы функция возвращала тип элемента массива.

В TS общие типы или дженерики (generics) используются для описания связи между двумя значениями. Это делается с помощью определения параметра Type в сигнатуре функции:

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

Предположение типа (inference)

Мы можем использовать несколько параметров типа. Например, самописная версия функции map может выглядеть так:

Обратите внимание, что в приведенном примере TS может сделать вывод относительно типа Input на основе переданного string[] , а относительно типа Output на основе возвращаемого number .

Ограничения (constraints)

Ограничение, как следует из названия, используется для ограничения типов, принимаемых параметром типа.

Реализуем функцию, возвращающую самое длинное из двух значений. Для этого нам потребуется свойство length , которое будет числом. Мы ограничим параметр типа типом number с помощью ключевого слова extends :

Мы позволяем TS предполагать тип значения, возвращаемого из функции longest .

Поскольку мы свели Type к < length: number >, то получили доступ к свойству length параметров a и b . Без ограничения типа у нас бы не было такого доступа, потому что значения этих свойств могли бы иметь другой тип — без длины.

Типы longerArr и longerStr были выведены на основе аргументов. Запомните, дженерики определяют связь между двумя и более значениями одного типа!

 

Наконец, как мы и ожидали, вызов longest(10, 100) отклоняется, поскольку тип number не имеет свойства length .

Работа с ограниченными значениями

Вот пример распространенной ошибки, возникающей при работе с ограничениями дженериков:

На первый взгляд может показаться, что все в порядке — Type сведен к < length: number >, и функция возвращает либо Type , либо значение, совпадающее с ограничением. Проблема состоит в том, что функция может вернуть объект, идентичный тому, который ей передается, а не просто объект, совпадающий с ограничением. Если бы во время компиляции не возникло ошибки, мы могли бы написать что-то вроде этого:

Определение типа аргументов

Обычно, TS делает правильные выводы относительно типов аргументов в вызове дженерика, но так бывает не всегда. Допустим, мы реализовали такую функцию для объединения двух массивов:

При обычном вызове данной функции с несовпадающими по типу массивами возникает ошибка:

Однако, мы можем вручную определить Type , и тогда все будет в порядке:

Руководство по написанию хороших функций-дженериков

Используйте параметры типа без ограничений

Рассмотрим две похожие функции:

Предполагаемым типом значения, возвращаемого функцией firstElement1 является Type , а значения, возвращаемого функцией firstElement2 — any . Это объясняется тем, что TS разрешает (resolve) выражение arr[0] с помощью ограничения типа вместо того, чтобы ждать разрешения элемента после вызова функции.

Правило: по-возможности, используйте параметры типа без ограничений.

Используйте минимальное количество параметров типа

Вот еще одна парочка похожих функций:

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

Правило: всегда используйте минимальное количество параметров типа.

Параметры типа должны указываться дважды

Иногда мы забываем, что функция не обязательно должна быть дженериком:

Вот упрощенная версия данной функции:

Запомните, параметры типа предназначены для связывания типов нескольких значений.

Правило: если параметр типа появляется в сигнатуре функции только один раз, то, скорее всего, он вам не нужен.

Опциональные параметры (optional parameters)

Функции в JS могут принимать произвольное количество аргументов. Например, метод toFixed принимает опциональное количество цифр после запятой:

Мы можем смоделировать это в TS , пометив параметр как опциональный с помощью ? :

Несмотря на то, что тип параметра определен как number , параметр x на самом деле имеет тип number | undefined , поскольку неопределенные параметры в JS получают значение undefined .

Мы также можем указать «дефолтный» параметр (параметр по умолчанию):

Теперь в теле функции f параметр x будет иметь тип number , поскольку любой аргумент со значением undefined будет заменен на 10 . Обратите внимание: явная передача undefined означает «отсутствующий» аргумент.

Опциональные параметры в функциях обратного вызова

При написании функций, вызывающих «колбеки», легко допустить такую ошибку:

Указав index? , мы хотим, чтобы оба этих вызова были легальными:

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

Поэтому попытка вызова такой функции приводит к ошибке:

В JS при вызове функции с большим (ударение на первый слог) количеством аргументов, чем указано в определении фукнции, дополнительные параметры просто игнорируются. TS ведет себя аналогичным образом. Функции с меньшим количеством параметров (одного типа) могут заменять функции с большим количеством параметров.

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

Перегрузка функции (function overload)

Некоторые функции могут вызываться с разным количеством аргументов. Например, мы можем написать функцию, возвращающую Date , которая принимает время в мс (timestamp, один аргумент) или день/месяц/год (три аргумента).

В TS такую функцию можно реализовать с помощью сигнатур перегрузки (overload signatures). Для этого перед телом функции указывается несколько ее сигнатур:

В приведенном примере мы реализовали две перегрузки: одну, принимающую один аргумент, и вторую, принимающую три аргумента. Первые две сигнатуры называются сигнатурами перегрузки.

Затем мы реализовали функцию с совместимой сигнатурой (compatible signature). Функции имеют сигнатуру реализации (implementation signature), но эта сигнатура не может вызываться напрямую. Несмотря на то, что мы написали функцию с двумя опциональными параметрами после обязательного, она не может вызываться с двумя параметрами!

Сигнатуры перегрузки и сигнатура реализации

Предположим, что у нас имеется такой код:

Почему в данном случае возникает ошибка? Дело в том, что сигнатура реализации не видна снаружи (за пределами тела функции). Поэтому при написании перегруженной функции всегда нужно указывать две или более сигнатуры перегрузки перед сигнатурой реализации.

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

Правила реализации хороших перегрузок функции

Рассмотрим функцию, возвращающую длину строки или массива:

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

Поскольку обе перегрузки имеют одинаковое количество аргументов и один и тот же тип возвращаемого значения, мы можем реализовать такую «неперегруженную» версию данной функции:

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

Правило: по-возможности используйте объединения вместо перегрузок функции.

Определение this в функциях

TS «понимает», что значением this функции user.becomeAdmin является внешний объект user . В большинстве случаев этого достаточно, но порой нам требуется больше контроля над тем, что представляет собой this . Спецификация JS определяет, что мы не можем использовать this в качестве названия параметра. TS использует это синтаксическое пространство (syntax space), позволяя определять тип this в теле функции:

Обратите внимание: в данном случае мы не можем использовать стрелочную функцию.

Другие типы, о которых следует знать

void представляет значение, возвращаемое функцией, которая ничего не возвращает. Если в теле функции отсутствует оператор return или после этого оператора не указано возвращаемого значения, предполагаемым типом возвращаемого такой функцией значения будет void :

В JS функция, которая ничего не возвращает, «неявно» возвращает undefined . Однако, в TS void и undefined — это разные вещи.

Обратите внимание: void — это не тоже самое, что undefined .

object

Специальный тип object представляет значение, которое не является примитивом ( string, number, boolean, symbol, null, undefined ). object отличается от типа пустого объекта ( <> ), а также от глобального типа Object . Скорее всего, вам никогда не потребуется использовать Object .

Правило: object — это не Object . Всегда используйте object !

Обратите внимание: в JS функции — это объекты: они имеют свойства, Object.prototype в цепочке прототипов, являются instanceof Object , мы можем вызывать на них Object.keys и т.д. По этой причине в TS типом функций является object .

unknown

Тип unknown представляет любое значение. Он похож на тип any , но является более безопасным, поскольку не позволяет ничего делать с неизвестным значением:

Это бывает полезным для описания типа функции, поскольку таким способом мы можем описать функцию, принимающую любое значение без использования типа any в теле функции. Другими словами, мы можем описать функцию, возвращающую значение неизвестного типа:

never

Некоторые функции никогда не возвращают значений:

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

never также появляется, когда TS определяет, что в объединении больше ничего не осталось:

Function

Глобальный тип Function описывает такие свойства как bind , call , apply и другие, характерные для функций в JS . Он также имеет специальное свойство, позволяющее вызывать значения типа Function — такие вызовы возвращают any :

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

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

Оставшиеся параметры и аргументы

Оставшиеся параметры (rest parameters)

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

Оставшиеся параметры указываются после других параметров с помощью . :

В TS неявным типом таких параметров является any[] , а не any . Любая аннотация типа для них должна иметь вид Array<T> или T[] , или являться кортежем.

Оставшиеся аргументы (rest arguments)

Синтаксис распространения (синонимы: расширение, распаковка) (spread syntax) позволяет передавать произвольное количество элементов массива. Например, метод массива push принимает любое количество аргументов:

Обратите внимание: TS не считает массивы иммутабельными. Это может привести к неожиданному поведению:

Самым простым решением данной проблемы является использование const :

Деструктуризация параметров (parameter destructuring)

Деструктуризация параметров используется для распаковки объекта, переданного в качестве аргумента, в одну или более локальную переменную в теле функции. В JS это выглядит так:

Аннотация типа для объекта указывается после деструктуризации:

Для краткости можно использовать именованный тип:

Возможность присвоения функций переменным

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

Контекстуальная типизация (contextual typing), основанная на void , не запрещает функции что-либо возвращать. Другими словами, функция, типом возвращаемого значения которой является void — type vf = () => void , может возвращать любое значение, но это значение будет игнорироваться.

Все приведенные ниже реализации типа () => void являются валидными:

Когда возвращаемое любой из этих функций значение присваивается переменной, она будет хранить тип void :

Поэтому следующий код является валидным, несмотря на то, что Array.prototype.push возвращает число, а Array.prototype.forEach ожидает получить функцию с типом возвращаемого значения void :

Существует один специальный случай, о котором следует помнить: когда литеральное определение функции имеет тип возвращаемого значения void , функция не должна ничего возвращать:

Облачные серверы от Маклауд быстрые и безопасные.

Зарегистрируйтесь по ссылке выше или кликнув на баннер и получите 10% скидку на первый месяц аренды сервера любой конфигурации!

 

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *