Файл: Основные правила работы с функциями: примеры и ограничения использования функций в различных языках программирования.pdf
Добавлен: 31.03.2023
Просмотров: 54
Скачиваний: 2
{
if (power == 0) return 1;
return number * pow(number, power - 1);
}
Третья функция:
int searchArray(int[] array, int value, int index = -1)
{
int _index = index == -1 ? 0 : index;
if (_index == array.Length) return -1;
if (array[_index] == value) return _index;
return searchArray(array, value, _index + 1);
}
Как видите отличий в первых двух функциях минимальны. В третьей есть два различия. Первое различие – значение по умолчанию аргумента ‘index’, это самый простой способ чтобы вычислить был ли аргумент передан или нет. Мы просто проверяем, если значение аргумента равно значению по умолчанию, значит аргумент не был передан. В другом случае значение передано. Если не было бы значения по умолчанию – вызов функции без третьего аргумента вызвал бы ошибку. Второе различие – имя локальной переменной ‘_index’ вместо ‘index’. В C# нельзя называть переменную тем же названием что и аргумент.
2.2 Функции обратного вызова
Функции обратного вызова (англ. callback) – функции, которые передаются как аргументы другим функциям, которые вызывают их [9]. Эта техника очень удобна, когда мы хотим вызвать определённый код после окончания или во время выполнения других функций. Эта техника также позволяет писать высоко-настраиваемые функции, которые позволяют использовать логику другой функции как аргумент. На пример, у нас есть функция, рисующая график. Эта функция может принимать другую функцию и на основе неё настраивать график.
Вот псевдокод этой функции:
function drawGraphic(callback) {
for(var x = 0; x < 1000; x++) {
drawPoint(x, callback(x));
}
}
Таким образом мы можем передать этой функции другую функцию. Например:
drawGraphic(Math.sin);
Функция может также быть написана нами.
function customFunction(x) {
// Здесь могут быть любые расчеты,
// Это всего лишь пример
return x * x + 26;
}
drawGraphic(customFunction);
Таким образом мы получаем настраиваемую (customizable) функцию, которая может рисовать разные графики опираясь на функцию, переданную ей аргументом. Напомню, что это псевдокод, он не будет работать в настоящем JavaScript.
А теперь рассмотрим несколько настоящих примеров, сначала в JavaScript, потом в C#.
В первом примере мы рассмотрим вариант имплементации встроенной функции массивов ‘map’ в JavaScript.
function map(array, callback) {
var result = [];
for(var i = 0; i < array.length; i++) {
result.push(callback(array[i]));
}
return result;
}
Эта функция получает массив и функцию обратного вызова, вызывает функцию обратного вызова на каждом элементе массива и на основе возвращённых значений выдаёт новый массив возвращённых значений.
Подробно разберём эту функцию:
var result = [];
Создаётся новый массив, который затем будет возвращён.
for(var i = 0; i < array.length; i++) {
result.push(callback(array[i]));
}
Цикл ‘for’ проходит по всему массиву и вызывает ‘callback’, передавая каждый элемент массива. Значение, возвращённое функцией обратного вызова, добавляется в массив ‘result’.
return result;
Массив ‘result’ возвращается из функции.
Теперь, когда мы рассмотрели функцию, рассмотрим примеры её использования.
function callback(value) {
return value * 2;
}
map([1, 2, 3], callback);
// Функция возвращает [2, 4, 6]
Чтобы каждый раз не объявлять функцию для вызова ‘map’, мы можем воспользоваться анонимными функциями JavaScript:
map([1, 2, 3], function(value) {
return value * 2;
});
// Функция возвращает [2, 4, 6]
Этот код работает так-же как и первый, но он более компактен и не объявляет лишних функций.
Рассмотрим ещё один пример использования функций обратного вызова. На сей раз это имплементация функции ‘filter’:
function filter(array, filterFunction) {
var result = [];
for(var i = 0; i < array.length; i++) {
if(filterFunction(array[i])) {
result.push(array[i]);
}
}
return result;
}
Эта функция, как следует из названия, фильтрует массив на основе функции обратного вызова и возвращает новый, отфильтрованный массив. Единственное отличие функции ‘filter’ от ‘map’ эти 3 строки кода:
if(filterFunction(array[i])) {
result.push(array[i]);
}
Здесь проверяется, если функция фильтр возвращает true, тогда элемент массива ‘array’ добавляется в массив ‘result’. Таким образом функция фильтрует массив на основе функции обратного вызова.
Рассмотрим также имплементацию функции ‘reduce’:
function reduce(array, callback) {
var accumulator = 0;
for(var i = 0; i < array.length; i++) {
accumulator = callback(accumulator, array[i]);
}
return accumulator;
}
Эта функция берёт массив и функцию обратного вызова, вызывает функция на всех элементах массива и возвращает одно результирующее значение [5]. Пример использования:
reduce([1, 2, 3, 4], function(accumulator, currentValue) {
return accumulator + currentValue;
});
// 1 + 2 + 3 + 4 = 10
// Функция вернёт значение 10
Разберём работу функции.
Эта строка создаёт переменную ‘accumulator’, которая и будет нашим результирующим значением.
accumulator = callback(accumulator, array[i]);
На этой строке мы передаём значение ‘accumulator’ и каждого элемента массива функции обратного вызова и присваиваем возвращённое значение переменной ‘accumulator’.
return accumulator;
В конце функции возвращаем значение ‘accumulator’.
Перейдём на C# и рассмотрим аналоги выше указанных функций.
Аналог функции ‘map’:
delegate int MapDelegate(int input);
int[] Map(int[] array, MapDelegate callback)
{
int[] result = new int[array.Length];
for(int i = 0; i < array.Length; i++)
{
result[i] = callback(array[i]);
}
return result;
}
int MapCallback(int x)
{
return x * 2;
}
int[] array = { 1, 2, 3, 4};
Map(array, MapCallback)
// { 2, 4, 6, 8 }
Здесь используется делегаты (англ. delegates). Так как C# язык со строгой типизацией, в нём нужно типизировать всё, в том числе и функции обратного вызова. Делегаты — это специальный тип, созданный для типизации функций [10]. Они описывают тип возвращаемого функцией значения и аргументов функции. С их помощью мы можем иметь строго-типизированные функции обратного вызова в аргументах. Они похожи на указатели функций в C++ [11]. Давайте разберём части кода с делегатами.
delegate int MapDelegate(int input);
Эта строка кода объявляет делегат, который инкапсулирует функцию, которая возвращает значение типа ‘int’ и получает один аргумент также типа ‘int’.
int[] Map(int[] array, MapDelegate callback)
Эта строка объявляет функцию ‘Map’, которая возвращает массив типа ‘int’ и получает два аргумента: массив типа ‘int’ и функцию типа ‘MapDelegate’.
result[i] = callback(array[i]);
Эта строка присваивает элементу массива значение, возвращённое делегатом ‘callback’.
Теперь рассмотрим другие различия версии C#.
int[] result = new int[array.Length];
На этой строке создаётся массив ‘result’, длинна которого равна длине массива ‘array’, так как функция ‘Map’ может менять значения, но не длину массива.
Теперь рассмотрим аналог функции ‘filter’․
using System.Collections.Generic;
delegate bool FilterDelegate(int input);
int[] Filter(int[] array, FilterDelegate callback)
{
List<int> result = new List<int>();
for(int i = 0; i < array.Length; i++)
{
if(callback(array[i]))
{
result.Add(array[i]);
}
}
return result.ToArray();
}
bool FilterCallback(int x)
{
return x > 3;
}
int[] array = { 1, 2, 3, 4, 5, 6};
Filter(array, FilterCallback); // { 4, 5, 6 }
Здесь основное отличие от JS версии – использование класса ‘List’ (не считая использование делегатов). Класс ‘List’ здесь использован так как в C# массивы не динамичны, а мы не можем точно знать размер нового массива после фильтрации. ‘List’ абстрагирует аллокацию памяти и работу с массивами, что мы используем для того, чтобы динамически добавлять элементы в массив [12].
Рассмотрим код, который взаимодействует с ‘List’.
using System.Collections.Generic;
‘System.Collections.Generic’ – пространство имен, содержащее класс ‘List’, используем оператор using что бы задействовать его в нашей программе.
List<int> result = new List<int>();
Эта строка инициализирует новый объект типа ‘List’. ‘<int>’ обозначает универсальный шаблон. Универсальные шаблоны – это техника, которая позволяет передать тип классу или функции как аргумент [13]. В данном случае используя универсальный шаблон мы обозначаем что наш лист будет нести в себе значения типа ‘int’. Универсальные шаблоны– очень удобно, когда есть один код, который работает между разными типами. Эта техника позволяет не переписывать новый класс или функцию для каждого типа.
result.Add(array[i]);
Эта строка динамически добавляет новый элемент в массив ‘result’, используя функцию ‘Add’ класса ‘List’.
return result.ToArray();
Эта строка конвертирует объект ‘List’ в массив типа ‘int’ и возвращает его из функции. Таким образом мы получаем чистый массив из функции.
Также рассмотрим аналог функции ‘reduce’:
delegate int ReduceDelegate(int accumulator, int value);
int Reduce(int[] array, ReduceDelegate reducer)
{
int accumulator = 0;
for(int i = 0; i < array.Length; i++)
{
accumulator = reducer(accumulator, array[i]);
}
return accumulator;
}
int Reducer(int accumulator, int value)
{
return accumulator + value;
}
int[] array = { 1, 2, 3, 4 };
Reduce(array, Reducer); // 10
Вывод 2
Рекурсия – техника вызова функцией самой себя. Она позволяет упростить решения алгоритмов, которые можно разделить на аналогичные простые задачи. Обычно алгоритмы с использованием рекурсии можно так же решить с помощью циклов.
Функции обратного вызова – концепция позволяющая использовать функции как аргументы. Она позволяет создавать высоко настраиваемые функции, которые берут часть своей логики из аргументов. Функции обратного вызова так-же позволяют выполнять данные функции после выполнения определённых операций.
Эти две техники позволяют использовать функции эффективнее, а также упрощают и укорачивают код.
Глава 3
Асинхронные функции
3.1 Теория
Часто в программировании часто требуется производить задачи, затрачивающие немалое время. Например, отправление HTTP запроса. В синхронной модели программирования программа должна была бы ждать, пока запрос не завершился бы, а потом уже продолжать выполнение. В асинхронной же модели программа может выполнять другие процессы, пока запрос не закончен [14]. Большинство современных языков программирование абстрагируют и упрощают асинхронное программирование до использования нескольких ключевых слов в функциях. Таким образом можно объявлять асинхронные функции, которые могут выполнятся параллельно.
Синтаксис асинхронного программирования очень похож в C# и JavaScript. В обоих языках используются ключевые слова ‘async’ и ‘await’, на которых и построено все асинхронные концепты [15]. Ключевое слово ‘async’ используется для объявления асинхронных функций, а ‘await’ для того, чтобы подождать пока асинхронная операция завершилась. ‘await’ может использоваться только в асинхронной функции.
3.2 Асинхронные функции в JavaScript
Асинхронное программирование в JavaScript строится на классе ‘Promise’. Один объект Promise – одна асинхронная операция в JavaScript [16].
Все асинхронные функции автоматически возвращают объекты Promise. Объекты Promise имеют два способа завершения: resolve и reject. Resolve – это когда операция завершена успешно, Reject – когда операция провалилась или завершилась ошибкой. Для создания Promise мы должны передать конструктору Promise функцию, которая получает два аргумента – Resolve и Reject, которые тоже являются функциями. С помощью этих функций мы можем завершить операцию с соответствующим результатом.
Рассмотрим пример использования:
const promise = new Promise(resolve => {
setTimeout(resolve, 500);
});
Здесь создаётся Promise, который через 500 миллисекунд завершится успешно.
const promise = new Promise(reject => {
setTimeout(reject, 500);
});
Это тот же Promise, однако этот завершается провалом.
Теперь, когда мы разобрались с ‘Promise’, рассмотрим сами асинхронные функции.
const sleep = time => new Promise(resolve =>
setTimeout(resolve, time)
);
async function asyncExample() {
console.log("Сейчас");
await sleep(5000); // 5000 миллисекунд = 5 секунд
console.log("Прошло 5 секунд");
}
asyncExample();
В этом примере мы сначала создаём функцию ‘sleep’, которая принимает время и возвращает Promise завершающийся через данное время. Затем мы создаём асинхронную функцию ‘asyncExample’, в которой используем функцию ‘sleep’ с помощью ключевого слова ‘await’.
Рассмотрим ещё одни пример:
async function getData() {
// Получить данные
const response = await fetch("api.myproject.com/getdata");
// Конвертировать в обьект JSON
const data = await response.json();
return data;
}
async function useData() {
const data = await getData();
// Работа с data-ой
}
В этом примере демонстрируется использование функции стандартной библиотеки ‘fetch’, эта функция используется для выполнения HTTP запросов в браузерном JavaScript [17]. В данном примере мы эффективно используем async и await для выполнения асинхронных операций.