Callback (програмування)

Матеріал з Вікіпедії — вільної енциклопедії.
Перейти до: навігація, пошук

У програмуванні, колбек є частиною виконуваного коду, що передається як аргумент до іншого коду, який має викликати цей код у відповідь (call back), тобто виконати аргумент у певний момент часу. Виклик функції відбувається для організації дії у відповідь — негайної у випадку синхронного колбеку (наприклад, ітерування колекції) або затриманої у випадку асинхронного колбеку (наприклад, виклик обробника подій після приходу даних з клавіатури). В усіх випадках намір полягає у тому, щоб вказати функцію або підпрограму як сутність, що, в залежності від мови, може бути більш чи менш схожою на змінну.

Мови програмування підтримують колбеки по-різному, часто реалізуючи їх як підпрограми, анонімні функції, блоки, або вказівники на функції.

Часто колбеки використовують, щоб передавати замикання.
Колбек часто повертається на рівень того, хто його викликав.

Проектування[ред.ред. код]

Є два типи колбеків, вони відрізняються способом контролю потоку даних під час виконання: блокуючі колбеки (відомі як синхронні колбеки і відкладені колбеки (також відомі як асинхронні колбеки). У той час, як блокуючі колбеки викликаються перед тим, як функція поверне результат, відкладені колбеки можуть виконуватися вже опісля того, як функція повернула результат. Відкладені колбеки часто використовуються в контексті операцій вводу-виводу або обробки подій і викликаються шляхом переривання одного та більшої кількості потоків чи іншим потоком у випадку багатопотоковості. Відповідно до своєї природи, блокуючі колбеки можуть працювати без переривань чи багатопотоковості, вони рідко використовуються для синхронізції або делегації роботи іншому потокові.

Колбеки використовуються, щоб програмувати у віконних системах. У цьому випадку застосунок надає посилання на певну функцію колбеку, яку має викликати операційна система, яка потім викликає властиві даному застосункові функції у відповідь на події на зразок кліків мишкою чи натисканння на клавіші. Тут основною проблемою є керування привілеями та безпекою: у той час, як функція викликається операційною системою, вона не має виконуватися з тими ж привілеями, що й система. Для вирішення цієї проблеми використовуються кільця захисту[en].

Наочні приклади[ред.ред. код]

Уяви ситуацію: увечері ви маєте зустрітися з друзями у кав'ярні. Оскільки для тебе дуже важливо правильно вибрати столик, то ти кажеш чоловікові (або дружині): по дорозі на роботу зайди до кав'ярні і зателефонуй мені — порадимося щодо столика, коли будеш на місці і по телефону опишеш мені, які столики там є.

Це — приклад колбеку. До функції "вибериСтолик()" ти додаєш колбек-функцію "порадьсяЗіМноюПоТелефону()" і ось можливий псевдо-код для цього:

function chooseBestTable( callBack ) { 
    var freeTables = [tableOne, tableTwo, tableThree];
    callBack( freeTables );
}

Або: Уяви, що ти печеш паску. Поставила пектися і пішла читати про колбеки, а щоб не прогавити її, ставиш на телефоні нагадування на 30 хвилин: "перевірити духовку". Тут вже маємо приклад колбеку: є процес випікання (немиттєвий), початок цього процесу (функція "початиВипікання()") і його колбек — твоє нагадування (функція "перевіритиВипікання()"). Колбек спрацює - і ти перевіриш духовку, можливо, паска вже готова. Без колбеку тобі прийшлося б сидіти усі 30 хвилин над духовкою. З колбеком можна дозволити собі трохи відволіктися і робити інші справи, бо колбек спрацює і нагадає.

Втілення[ред.ред. код]

Форма колбеків може бути різною у різних мовах програмування:

  • В assembly, C, C++, Pascal, Modula2 і подібних до них мовах на машинному рівні передається вказівник на функцію як аргумент іншої (зовнішньої чи внутрішньої) функції. Це підтримується більшістю компіляторів та дає перевагу використання разом різних мов без спеціальних бібліотек чи класів-обгорток. Прикладом може бути Windows API, що більш чи менш напряму доступний з багатьох мов, компяляторів та асемблерів. Зворотньою стороною є те, що програміст має розуміти машинний рівень, також не працює контроль багатьох типів.
  • C++ дозволяє об'єктам надавати їх власні реалізації операції виклику функцій. Стандартна бібліотека шаблонів приймає ці об'єкти (що звуться функторами), так само, як і вказівники функцій, як параметри для багатьох поліморфних алгоритмів.
  • Багато інтерпретованих мов, на кшталт JavaScript, Lua, Python, Perl[1][2] та PHP, просто дозволяють передавати об'єкт функції.
  • CLI-мови типу C# і VB.NET надають безпечне щодо типів включаюче посилання, "делегата", щоб визначити жорстко-типізований вказівник на функцію. Вони можуть використовуватися як колбеки.
  • Події і обробники подій у мовах .NET надають загальний синтаксис колбеків.
  • Функціональні мови в цілому підтримують функції першого класу, які можуть передаватися як колбеки до інших функцій, зберігатися як дані або повертатися з функцій.
  • Деякі мови, такі як Algol 68, Perl, Python, Ruby, Smalltalk, C++11 та пізніші, новіші версії C# та VB.NET, а також більшість функціональних мов, дозволяють неіменованим блокам коду (анонімним функціям) передаватися замість посилань на функції, визначені деінде.
  • В деяких мовах, типу Scheme, ML, JavaScript, Perl, Smalltalk, PHP (з версії 5.3.0),[3] C++11 та пізніших, та в багатьох інших, такі функції можуть бути замиканнями, тобто вони можуть використовувати та змінювати ті змінні, що були локально визначені у тому ж контексті, в якому була визначена функція.
  • В об'єктно-орієнтованих мовах програмування без функції багатозначних аргументів, як Java до версії 1.7,[citation needed] колбеки можна симулювати, передаючи екземпляр абстрактного класу або інтерфейсу, у якому отримувач викличе один чи більше методів, у той час як той, хто викликає, надає конкретну реалізацію. Такі об'єкти є насправді пачкою колбеків, плюс дані, які вони мають обробити [прояснити]. Вони корисні у реалізації різних шаблонів дизайну типу Відвідувач, Оглядач, та Стратегія.


Java[ред.ред. код]

public interface CalcCallback {
    int plus(int x, int y);
}

public class Test2 {

    private Test test;

    public Test2(Test test) {
        this.test = test;
    }

    public void calc() {
        CalcCallback callback = new CalcCallback() {

            @Override
            public int plus(int x, int y) {
                return x + y;
            }
        };

        test.calcViaCallback(callback);
    }
}

public class Test {

    int x = 5;
    int y = 12;

    public void calcViaCallback(CalcCallback callback) {
        int sum = callback.plus(x, y);

        System.out.println(sum);
    }
}

public class Main {

    public static void main(String[] args) {
        Test test = new Test();

        Test2 test2 = new Test2(test);

        test2.calc();
    }
}

JavaScript[ред.ред. код]

Колбеки використовуються в мовах подібних до JavaScript, включаючи підтримку функцій як колбеків через js-ctypes[4] і в компонентах типу addEventListener.[5] Ось простий приклад колбеку:

function someAction(x, y, someCallback) {
    return someCallback(x, y);
}

function calcProduct(x, y) {
    return x * y;
}

function calcSum(x, y) {
    return x + y;
}
// alerts 75, the product of 5 and 15
alert(someAction(5, 15, calcProduct));
// alerts 20, the sum of 5 and 15
alert(someAction(5, 15, calcSum));

Спочатку функція someAction визначається з аргументом-колбеком someCallback. Потім, як можливий колбек для someAction, визначається функція calcProduct. Інші функції також можуть використовуватися для  someCallback, наприклад calcSum. У цьому прикладі someAction() викликається двічі: спочатку використовуючи як колбеку calcProduct, і ще раз — як колбек маючи calcSum. Функції повертають відповідно добуток та суму, що потім відображаються на екрані за допомогою alert.

У цьому простому прикладі колбеки використовуються переважно для демонстрації принципу. Можна викликати колбеки як звичайні функції: calcProduct(x, y). Колбеки корисні тоді, коли функція має виконати дії перед викликом колбека, або коли функція не має (або не може мати) результату для повернення через return, як у випадках асинхронного JavaScript (побудованого на таймерах) або запитів XMLHttpRequest. Корисні приклади можна знайти в бібліотеках JavaScript типу jQuery, де метод each() проходить по об'єкту типу масива, а перший аргумент є колбеком, що виконується на кожній ітерації.

Swift[ред.ред. код]

func relatedValue(value: Bool, callback: Int -> ()) {
    callback(value ? 150 : 25)
}

// скорочений вигляд замикання, без захоплення посилання на змінні 
relatedValue(true) { value in
    println("result value is: \(value)")
}

// або
relatedValue(true, callback: { value in
    println("result value is: \(value)")
})

// також приклад застосування при ініціалізації об'єкта
class A {
    var someValue: String?

    init(completion: A -> Void) {
        completion(self)
    }
}

let a = A {
    $0.someValue = "This is initial value!"
}

Scala[ред.ред. код]

def callbackMethod(a: Int, b: Int, c: Int => Unit) = {
   c(a + b)
}

callbackMethod(2, 2, { result => println(result) })

C[ред.ред. код]

#include <stdio.h>
 
// В мові С колбеки реалізуються шляхом передачі вказівника на функцію
// Визначимо тип callback_t, що являтиме собою вказівник на функцію, що
// повертає int і приймає як аргументи два числа типу int
typedef int (* callback_t)(int, int);
 
int apply_callback(int x, int y, callback_t some_callback)
{
   return (*some_callback)(x, y);
}
 
int add_callback(int x, int y)
{
    return x + y;
}
 
int mul_callback(int x, int y)
{
    return x * y;
}
 
int main() 
{
    printf("10 + 35 = %d\n", apply_callback(10, 35, add_callback));
    printf("10 * 35 = %d\n", apply_callback(10, 35, mul_callback));
    return 0;
}

C#[ред.ред. код]

class CallbackExample
{
        // Ключове слово "delegate" у C# означає, що ви задаєте визначення нового типу. Цей тип наслідується від System.Delegate, котрий надає механізм виклику методів за їх посиланням
        // Коли ви вказуєте метод, що узгоджується з сигнатурою, визначеною делегатом, як аргумент відповідного типу, буде створено новий екземпляр делегованого типу, всередині якого буде збережено вказівник на переданий метод
        // Надалі ви можете використовувати цей екземпляр як звичайний метод (метод Calculate добре демонструє це)
	public delegate int BinaryOperationCallbackDelegate(int operand1, int operand2);

        // Узгоджується з сигнатурою BinaryOperationCallbackDelegate, тому може використовуватись як аргумент типу BinaryOperationCallbackDelegate
	public int AddCallback(int operand1, int operand2)
	{
		return operand1 + operand2;
	}

        // Узгоджується з сигнатурою BinaryOperationCallbackDelegate, тому може використовуватись як аргумент типу BinaryOperationCallbackDelegate
	public int SubstractCallback(int operand1, int operand2)
	{
		return operand1 - operand2;
	}

        // Вимагає операнди, потрібні для методів типу BinaryOperationCallbackDelegate і сам метод цього типу для його зворотного виклику
	public int Calculate(int operand1, int operand2, BinaryOperationCallbackDelegate callback)
	{
		int result = callback(operand1, operand2);
		return result;
	}
}

class Program
{
	static void Main(string[] args)
	{
		const int operand1 = 10;
		const int operand2 = 20;

		CallbackExample callbackExample = new CallbackExample();

		Console.WriteLine(callbackExample.Calculate(operand1, operand2, callbackExample.AddCallback));
		Console.WriteLine(callbackExample.Calculate(operand1, operand2, callbackExample.SubstractCallback));

		Console.ReadLine();
	}
}

У більшості випадків вам цілком вистачить вбудованих делегатів типу: Action, Function, Predicate

Python[ред.ред. код]

TODO: Колбеки у Python

def someAction(x, y, someCallback):
    return someCallback(x, y)


def calcProduct(x, y):
    return x * y


def calcSum(x, y):
    return x + y

# prints 75, the product of 5 and 15
print someAction(5, 15, calcProduct)
# prints 20, the sum of 5 and 15
print someAction(5, 15, calcSum)

#example using lambdas
numbers = [ num for num in range(20) ]

# ітерація по списку, і відсіювання елементів відповідно до значення, яке поверне лямбда фугкція (предикат)
# непарні числа
odd_numbers = list(filter(lambda n: n % 2 == 1, numbers))

# парні числа
even_numbers = list(filter(lambda n: n % 2 == 0, numbers))

# вивід двох нових списків із відсіяними значеннями
print('odd numbers', odd_numbers)
print('even numbers', even_numbers)

Ruby[ред.ред. код]

# Викликає переданий блок коду, передаючи вхідний параметр у блок, як вхідний аргумент
# Params:
# +param+:: параметр, який буде переданий блоку коду
def call_block_with_param(param)
	yield param
end

# Викликає передану процедуру, передаючи вхідний параметр у неї, як вхідний аргумент
# Params:
# +param+:: параметр, який буде переданий у процедуру
# +procedure+:: +Proc+ процедура, яку потрібно викликати із переданим аргументом
def call_proc_with_param(param, procedure)
	procedure.call param
end

# Викликає переданий лямбда вираз, передаючи вхідний параметр у неї, як вхідний аргумент
# Params:
# +param+:: параметр, який буде переданий у лямбду
# +lamda+:: лямбда вираз, який потрібно викликати із переданим аргументом
def call_lambda_with_param(param, lamda)
	lamda.call param
end

call_block_with_param ("Hello") { |param| puts "#{param}" }

procedure = Proc.new do |param|
	puts "#{param}"
end
call_proc_with_param("Hello", procedure)

call_lambda_with_param("Hello", lambda { |param| puts "#{param}" } )

Lua[ред.ред. код]

-- функція, яка опрацьовує елементи переданого їй масиву по черзі, і формує новий масив перетворених функцією callback елементів
function processArray(array, callback)
  local outputTable = {}
  for i, elem in ipairs(array) do
    local tmp = callback(elem)
    table.insert(outputTable, tmp)
  return outputTable
end

function square(x)
  return x * x
end

local numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9 }
local squares = processArray(numbers, square)

for i, number in squares do
  print(number, ", ")
end

Дивіться також[ред.ред. код]

Зноски[ред.ред. код]

  1. Perl Cookbook - 11.4. Taking References to Functions. Процитовано 2008-03-03. 
  2. Advanced Perl Programming - 4.2 Using Subroutine References. Процитовано 2008-03-03. 
  3. PHP Language Reference - Anonymous functions. Процитовано 2011-06-08. 
  4. Callbacks. Mozilla Developer Network. Процитовано 13 December 2012. 
  5. Creating Javascript Callbacks in Components. Mozilla Developer Network. Процитовано 13 December 2012. 

Посилання[ред.ред. код]