Сигнатура функции — Основы PHP
В этом уроке мы научимся работать с сигнатурой функции. Также мы узнаем, как функция принимает и возвращает значения. Мы разберем функции abs() и round() .
Функция abs()
Функция abs() , которая возвращает абсолютное значение, принимает параметр — число. Если вызывать abs() без параметров, то PHP выдаст следующее:
Так интерпретатор сообщает, что функция ожидает один параметр, а мы вызвали ее без параметров.
Параметрами abs() могут быть только числа. Если мы попробуем передать в нее строку, это приведет к следующей ошибке:
Результат вызова этой функции — тоже всегда число. Другая функция может иметь иное число параметров и другие типы параметров. Например, может существовать функция, которая принимает три параметра: число, строку и еще одно число.
Откуда мы знаем, сколько и каких параметров нужно функции abs() и какого типа будет возврат? Мы посмотрели в сигнатуру этой функции. Сигнатура определяет входные параметры и их типы, а также выходной параметр и его тип.
О функции abs() можно почитать в официальной документации PHP. В разделе «Описание» есть такой текст:
Это сигнатура функции и короткое пояснение на русском языке.
Информация расшифровывается так:
- Функция называется abs
- Функция принимает параметр: число (num)
- Функция возвращает число
- Функция возвращает абсолютное значение num
Если параметров больше одного, то передавать их можно только в той последовательности, в которой они определены в сигнатуре. Любая функция возвращает всегда только одно значение. Это ограничение существует на уровне языка, и не может нарушаться.
Аргументы по умолчанию
Рассмотрим функцию round() . Она округляет переданное число:
Мы передали в нее два аргумента: число и точность округления. 0 означает, что округление будет до целого значения.
Чаще всего нужно округлять именно до целого числа, поэтому создатели функции round сделали второй аргумент необязательным и задали ему внутри функции значение по умолчанию 0 . Значит, можно не указывать второй аргумент, а результат будет тем же:
А если нужна другая точность, то можно передать аргумент:
Если функция в PHP принимает необязательные аргументы, то они всегда стоят после обязательных. Их количество может быть любым. Это зависит от самой функции. Но такие аргументы всегда идут рядом и в конце списка аргументов.
Signature (functions)
A function signature (or type signature, or method signature) defines input and output of functions or methods.
A signature can include:
-
and their types
- a return value and type that might be thrown or passed back
- information about the availability of the method in an object-oriented program (such as the keywords public , static , or prototype ).
In depth
Signatures in JavaScript
JavaScript is a loosely typed or a dynamic language. That means you don’t have to declare the type of a variable ahead of time. The type will get determined automatically while the program is being processed. A signature in JavaScript can still give you some information about the method:
- The method is installed on an object called MyObject .
- The method is installed on the prototype of MyObject (thus it is an instance method) as opposed to being a static method.
- The name of the method is myFunction .
- The method accepts one parameter, which is called value and is not further defined.
Signatures in Java
In Java, signatures are used to identify methods and classes at the level of the virtual machine code. You have to declare types of variables in your code in order to be able to run the Java code. Java is strictly typed and will check any parameters at compilation time if they are correct.
Что такое сигнатура функции? Signature (сигнатура) — это что?
Как известно, интерфейс прикладного программирования, именуемый API, включает в себя библиотеки функций и классов с описанием семантики и сигнатуры (signature) . В данной статье мы поговорим, что же такое сигнатура и для чего она нужна. Об этом написано уже много слов, но мы уверены, что чтение нашего текста тоже не будет для вас бесполезным.
Сигнатура — это часть общего объявления функции, которая позволяет средствам трансляции выполнять идентификацию этой самой функции среди других. В разных языках программирования есть различные представление о сигнатуре (signature).
Сигнатура (signature): какая она бывает?
Существует как сигнатура реализации, так и сигнатура вызова (обычно эти понятия различают).
Signature вызова в большинстве случаев формируется из синтаксической конструкции вызова функции, при этом учитывается сигнатура области её видимости, а также имя функции и последовательность фактических типов аргументов в самом вызове и в типе результата.
Если говорить о сигнатурах (signatures) реализации, то здесь участвуют следующие элементы, входящие в синтаксическую конструкцию объявления функции: — имя; — последовательность формальных типов аргументов; — спецификатор области видимости функции.
Signature в разных языках программирования
В языке программирования С++ простая функция распознаётся компилятором по последовательности типов её аргументов и её имени, что и составляет в данном языке сигнатуру или сигнату функции. И если функция — это метод некоторого класса, то в Signature участвует и имя класса.
Если говорить о языке программирования Java, то тут сигнатура метода составляется из его имени и последовательности типа параметров. То есть тип значение в signature не участвует.
Однако давайте подробнее остановимся на том, зачем нужна сигнатура в JavaScript.
Signature в JavaScript: особенности применения signature
Когда программист на Javascript овладевает самыми глубокими секретами функционального программирования, он всё чаще встречает стрелки с типом, которые написаны над функциями. Первая мысль: «Что такое? Я же мастер по динамически типизированному Javascript, который свободен от ограничений типов».
На самом деле, всё просто, а такие записи не что иное, как сигнатура типов. С помощью signature можно рассказать о функции, причём сама по себе сигнатура значит в функциональном программировании гораздо больше, чем можно подумать.
Почему Signature полезна в коде?
Signature определяет возвращаемые и входящие типы для функции, включая иногда типы, число и порядок аргументов, которые содержатся в функции. Таким образом, signature используется для отслеживания работы функции.
Сигнатура типов основана на системе Хиндли-Милнера. Если вы обнаружите функцию, которая задокументирована Signature и будете уметь понимать её, это даст вам самое наглядное представление о работе данной функции.
Signature и простые функции
Смотрим пример использования signature:
В вышеуказанном примере функция принимает строку, возвращая число. И если мы посмотрим на этот участок кода с signature внимательнее, то увидим следующее: 1. Вначале записывается имя функции, потом :: . 2. Далее перед стрелкой signature записывается входящий тип. 3. После этого возвращаемый тип записывается после стрелки signature либо в самом конце.
Собственно говоря, вполне нормально, когда функция имеет множественные signatures, пока это удобно. Но если она становится чересчур гибкой, следует использовать произвольные переменные Хиндли-Милнера.
Выводы о signature
Умение понимать signature полезно как в JavaScript, так и в прочих функциональных языках. И если нам нужно заимствовать любую чистую функцию, мы можем всего лишь обратиться к её signature, чтобы понять, с каким участком кода нам надо работать.
More on Functions
Functions are the basic building block of any application, whether they’re local functions, imported from another module, or methods on a class. They’re also values, and just like other values, TypeScript has many ways to describe how functions can be called. Let’s learn about how to write types that describe functions.
Function Type Expressions
The simplest way to describe a function is with a a function type expression. These types are syntactically similar to arrow functions:
The syntax (a: string) => void means «a function with one parameter, named a , of type string, that doesn’t have a return value». Just like with function declarations, if a parameter type isn’t specified, it’s implicitly any .
Note that the parameter name is required. The function type (string) => void means «a function with a parameter named string of type a «!
Of course, we can use a type alias to name a function type:
Call Signatures
In JavaScript, functions can have properties in addition to being callable. However, the function type expression syntax doesn’t allow for declaring properties. If we want to describe something callable with properties, we can write a call signature in an object type:
Note that the syntax is slightly different compared to a function type expression — use : between the parameter list and the return type rather than => .
Construct Signatures
JavaScript functions can also be invoked with the new operator. TypeScript refers to these as constructors because they usually create a new object. You can write a construct signature by adding the new keyword in front of a call signature:
Some objects, like JavaScript’s Date object, can be called with or without new . You can combine call and construct signatures in the same type arbitrarily:
Generic Functions
It’s common to write a function where the types of the input relate to the type of the output, or where the types of two inputs are related in some way. Let’s consider for a moment a function that returns the first element of an array:
This function does its job, but unfortunately has the return type any . It’d be better if the function returned the type of the array element.
In TypeScript, generics are used when we want to describe a correspondence between two values. We do this by declaring a type parameter in the function signature:
By adding a type parameter T to this function and using it in two places, we’ve created a link between the input of the function (the array) and the output (the return value). Now when we call it, a more specific type comes out:
Inference
Note that we didn’t have to specify T in this sample. The type was inferred — chosen automatically — by TypeScript.
We can use multiple type parameters as well. For example, a standalone version of map would look like this:
Note that in this example, TypeScript could infer both the type of the E type parameter (from the given string array), as well as the type O based on the return value of the function expression.
Constraints
We’ve written some generic functions that can work on any kind of value. Sometimes we want to relate two values, but can only operate on a certain subset of values. In this case, we can use a constraint to limit the kinds of types that a type parameter can accept.
Let’s write a function that returns the longer of two values. To do this, we need a length property that’s a number. We constrain the type parameter to that type by writing an extends clause:
There are a interesting few things to note in this example. We allowed TypeScript to infer the return type of longest . Return type inference also works on generic functions.
Because we constrained T to < length: number >, we were allowed to access the .length property of the a and b parameters. Without the type constraint, we wouldn’t be able to access those properties because the values might have been some other type without a length property.
The types of longerArray and longerString were inferred based on the arguments. Remember, generics are all about relating two or more values with the same type!
Finally, just as we’d like, the call to longest(10, 100) is rejected because the number type doesn’t have a .length property.
Working with Constrained Values
Here’s a common error when working with generic constraints:
It might look like this function is OK — T is constrained to < length: number >, and the function either returns T or a value matching that constraint. The problem is that the function promises to return the same kind of object as was passed in, not just some object matching the constraint. If this code were legal, you could write code that definitely wouldn’t work:
Specifying Type Arguments
TypeScript can usually infer the intended type arguments in a generic call, but not always. For example, let’s say you wrote a function to combine two arrays:
Normally it would be an error to call this function with mismatched arrays:
If you intended to do this, however, you could manually specify T :
Guidelines for Writing Good Generic Functions
Writing generic functions is fun, and it can be easy to get carried away with type parameters. Having too many type parameters or using constraints where they aren’t needed can make inference less successful, frustrating callers of your function.
Push Type Parameters Down
Here are two ways of writing a function that appear similar:
These might seem identical at first glance, but firstElement1 is a much better way to write this function. Its inferred return type is T , but firstElement2 ‘s inferred return type is any because TypeScript has to resolve the arr[0] expression using the constraint type, rather than «waiting» to resolve the element during a call.
Rule: When possible, use the type parameter itself rather than constraining it
Use Fewer Type Parameters
Here’s another pair of similar functions:
We’ve created a type parameter F that doesn’t relate two values. That’s always a red flag, because it means callers wanting to specify type arguments have to manually specify an extra type argument for no reason. F doesn’t do anything but make the function harder to read and reason about!
Rule: Always use as few type parameters as possible
Type Parameters Should Appear Twice
Sometimes we forget that function doesn’t need to be generic:
We could just as easily have written a simpler version:
Remember, type parameters are for relating the types of multiple values. If a type parameter is only used once in the function signature, it’s not relating anything.
Rule: If a type parameter only appears in one location, strongly reconsider if you actually need it
Optional Parameters
Functions in JavaScript often take a variable number of arguments. For example, the toFixed method of number takes an optional digit count:
We can model this in TypeScript by marking the parameter as optional with ? :
Although the parameter is specified as type number , the x parameter will actually have the type number | undefined because unspecified parameters in JavaScript get the value undefined .
You can also provide a parameter default:
Now in the body of f , x will have type number because any undefined argument will be replaced with 10 . Note that when a parameter is optional, callers can always pass undefined , as this simply simualtes a «missing» argument:
Optional Parameters in Callbacks
Once you’ve learned about optional parameters and function type expressions, it’s very easy to make the following mistakes when writing functions that invoke callbacks:
What people usually intend when writing index? as an optional parameter is that they want both of these calls to be legal:
What this actually means is that callback might get invoked with one argument. In other words, the function definition says that the implementation might look like this:
In turn, TypeScript will enforce this meaning and issue errors that aren’t really possible:
In JavaScript, if you call a function with more arguments than there are parameters, the extra arguments are simply ignored. TypeScript behaves the same way. Functions with fewer parameters (of the same types) can always take the place of functions with more parameters.
When writing a function type for a callback, never write an optional parameter unless you intend to call the function without passing that argument
Function Overloads
Some JavaScript functions can be called in a variety of argument counts and types. For example, you might write a function to produce a Date that takes either a timestamp (one argument) or a month/day/year specification (three arguments).
In TypeScript, we can specify a function that can be called in different ways by writing overload signatures. To do this, write some number of function signatures (usually two or more), followed by the body of the function:
In this example, we wrote two overloads: one accepting one argument, and another accepting three arguments. These first two signatures are called the overload signatures.
Then, we wrote a function implementation with a compatible signature. Functions have an implementation signature, but this signature can’t be called directly. Even though we wrote a function with two optional parameters after the required one, it can’t be called with two parameters!
Overload Signatures and the Implementation Signature
This is a common source of confusion. Often people will write code like this and not understand why there is an error:
Again, the signature used to write the function body can’t be «seen» from the outside.
The signature of the implementation is not visible from the outside. When writing an overloaded function, you should always have two or more signatures above the implementation of the function.
The implementation signature must also be compatible with the overload signatures. For example, these functions have errors because the implementation signature doesn’t match the overloads in a correct way:
Writing Good Overloads
Like generics, there are a few guidelines you should follow when using function overloads. Following these principles will make your function easier to call, easier to understand, and easier to implement.
Let’s consider a function that returns the length of a string or an array:
This function is fine; we can invoke it with strings or arrays. However, we can’t invoke it with a value that might be a string or an array, because TypeScript can only resolve a function call to a single overload:
Because both overloads have the same argument count and same return type, we can instead write a non-overloaded version of the function:
This is much better! Callers can invoke this with either sort of value, and as an added bonus, we don’t have to figure out a correct implementation signature.
Always prefer parameters with union types instead of overloads when possible
Other Types to Know About
There are some additional types you’ll want to recognize that appear often when working with function types. Like all types, you can use them everywhere, but these are especially relevant in the context of functions.
void represents the return value of functions which don’t return a value. It’s the inferred type any time a function doesn’t have any return statements, or doesn’t return any explicit value from those return statements:
In JavaScript, a function that doesn’t return any value will implicitly return the value undefined . However, void and undefined are not the same thing in TypeScript. See the reference page Why void is a special type for a longer discussion about this.
void is not the same as undefined .
object
The special type object refers to any value that isn’t a primitive ( string , number , boolean , symbol , null , or undefined ). This is different from the empty object type < >, and also different from the global type Object . You can read the reference page about The global types for information on what Object is for — long story short, don’t ever use Object .
object is not Object . Always use object !
Note that in JavaScript, function values are objects: They have properties, have Object.prototype in their prototype chain, are instanceof Object , you can call Object.keys on them, and so on. For this reason, function types are considered to be object s in TypeScript.
unknown
The unknown type represents any value. This is similar to the any type, but is safer because it’s not legal to do anything with an unknown value:
This is useful when describing function types because you can describe functions that accept any value without having any values in your function body.
Conversely, you can describe a function that returns a value of unknown type:
never
Some functions never return a value:
The never type represents values which are never observed. In a return type, this means that the function throws an exception or terminates execution of the program.
never also appears when TypeScript determines there’s nothing left in a union.
Function
The global type Function describes properties like bind , call , apply , and others present on all function values in JavaScript. It also has the special property that values of type Function can always be called; these calls return any :
This is an untyped function call and is generally best avoided because of the unsafe any return type.
If need to accept an arbitrary function but don’t intend to call it, the type () => void is generally safer.
Rest Parameters and Arguments
Background reading: Rest Parameters and Spread Syntax
Rest Parameters
In addition to using optional parameters or overloads to make functions that can accept a variety of fixed argument counts, we can also define functions that take an unbounded number of arguments using rest parameters.
A rest parameter appears after all other parameters, and uses the . syntax:
In TypeScript, the type annotation on these parameters is implicitly any[] instead of any , and any type annotation given must be of the form Array<T> or T[] , or a tuple type (which we’ll learn about later).
Rest Arguments
Conversely, we can provide a variable number of arguments from an array using the spread syntax. For example, the push method of arrays takes any number of arguments:
Note that in general, TypeScript does not assume that arrays are immutable. This can lead to some surprising behavior:
The best fix for this situation depends a bit on your code, but in general a const context is the most straightforward solution:
Parameter Destructuring
You can use parameter destructuring to conveniently unpack objects provided as an argument into one or more local variables in the function body. In JavaScript, it looks like this: