Программы
Колбеки и функции высших порядков

Колбеки и функции высших порядков

Немного функционального программирования в Си

Да, я знаю, что "не приятно" в Си делать функции высших порядков (когда функции используют другие функции в качестве аргументов)... И на самом деле это частое заблуждение! Частый случай использования функций – навешивание колбеков – вызовов, которые должны случиться, когда произойдёт другое действие. Например, приход нового сетевого пакета. Да множество функций API сетевогои системного программирования просто эксплуатирует колбеки и тут и там! А это как раз частный случай функций высшего порядка...

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

Функции как аргументы функций

Пожалуй, самый простой пример, когда полезно использовать функцию как агрумент другой функции – сортировка. Например, когда мы в Python сортируем итерабельные объекты:

>>> lst = [122, 43, 1, 452, 87, 5]
>>> sorted(lst, key=lambda x: len(str(x)))
[1, 5, 43, 87, 122, 452]

– мы передаём параметром функцию, которая "оценивает" каждый элемент на "больше-меньше". Другой подход (более каноничный) мы можем увидеть в языке Perl:

my @lst = (122, 43, 1, 452, 87, 5);
@result = sort { length($a) <=> length($b) } @lst;
print join(" ", @result);
^D
1 5 43 87 122 452

– здесь мы передаём функции sort функцию сравнения от двух сравниваемых значений $a и $b, которая вернёт -1 - если $b > $a, 0 - если $b == $a, 1 - если $a > $b.

На Си это будет выглядеть так:

#include <stdio.h>
#include <stdlib.h>  // Подключаем для qsort
#include <string.h>

int cmpfunc (const void *a, const void *b) {
    char a_str[10];
    char b_str[10];

    sprintf(a_str, "%d", *(int *)a);
    sprintf(b_str, "%d", *(int *)b);

    return strlen(a_str) - strlen(b_str);
}

int main () {
    int values[] = { 122, 43, 1, 452, 87, 5 };

    qsort(values, 6, sizeof(int), cmpfunc);

    for(int n = 0; n < 6; n++) {   
        printf("%d ", values[n]);
    }

    return(0);
}

Прототип функции qsort (man qsort):

void qsort(         // Функция ничего не вернёт
    void *base,     // Указатель на что-либо
    size_t nitems,  // Беззнаковое целое для "размеров"
    size_t size,
    int (*compar)(const void *, const void *)
);

Параметры:

  • base − указатель на первый элемент массива, который мы сортируем.
  • nitems − количество элементов массива.
  • size − размер в байтах каждого элемента массива.
  • compar − функция сравнения 2-х элементов.

Напишем свою функцию высшего порядка

map – применить функцию к каждому элементу "коллекции".

#include <stdio.h>
#include <stdlib.h>

void map(
  void *from,
  void *to,
  size_t count,
  size_t size,
  void (*modify)(void *, void *)
) {
  /**
   * Адресной арифметики на void-указателях нет.
   * Поэтому преобразуем в байт-указатели.
   */
  char *from_bytes = (char *)from;
  char *to_bytes = (char *)to;

  for (int i = 0; i < count; ++i) {
    modify(from_bytes + i * size, to_bytes + i * size);
  }
}

/**
 * Можем описать любую функцию, реализующую прототип,
 * которая всё также будет изменять каждый элемент
 * в исходном массиве и кладёт в результирующий.
 */
void modify_int(void *from, void *to) {
  int *a = (int *)from;
  int *b = (int *)to;
  *b = (*a) * (*a);
}

int main() {
  int a[] = {1, 2, 3, 4, 5};
  int b[5];

  map(a, b, 5, sizeof(int), modify_int);

  for (int i = 0; i < 5; ++i) {
    printf("%d\n", b[i]);
  }

  return 0;
}

Callback

Часто бывает нужно выполнить какое-то действие после другого (что очевидно :)). Что не очевидно – не всегда это можно сделать последовательными вызовами 2-х функций. Подробнее это мы рассмотрим на практике в следующих темах – "обратные вызовы" – частый инструмент API в Си библиотеках.

Чисто технически это не сильно отличается от функций высшего порядка.

ДЗ

Написать функции filter, reduce - аналоги функций python.

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

Ключевое слово Void в Си

Значений у Void в Си несколько. Давайте рассмотрим их

Читать »

Структуры и объединения

Базовые структуры в Си. Общие принципы работы с ними

Читать »
Фото Python: Встроенные типы данных (list, set, dict, etc)

Python: Встроенные типы данных (list, set, dict, etc)

В Python есть множество встроенных типов данных. Их использование значительно упрощает жизнь и ускоряет разработку программных продуктов.

Фото Python: типы данных, переменные, логическое ветвление и циклы

Python: типы данных, переменные, логическое ветвление и циклы

Первая часть заметок о Python. О базовых типах, переменных, ветвлении и циклах.

Фото Как сделать свою middleware в Django (с примерами)

Как сделать свою middleware в Django (с примерами)

Middleware или "промежуточное программное обеспечение" - элегантный способ установить общие правила обработки запросов и ответов приложения. Давайте напишем парочку middleware, чтобы понять, как они работают.

Фото Как настроить отправку почты из Django

Как настроить отправку почты из Django

Письма об ошибках, отчёты на почту, восстановление паролей - всё это полезно при работе с сайтом. Django предоставляет удобный способ это сделать с минимумом настроек!

Фото Добавляем постраничную пагинацию на Django сайт

Добавляем постраничную пагинацию на Django сайт

На сайтах часто встречаются многостраничные объекты: список товаров, список заметок и т.д. Поэтому важно уметь добавить навигацию по страницам на Django-проекте.

Фото Новый оператор match-case в Python

Новый оператор match-case в Python

В новой версии Python (3.10) появится новый оператор. Новый оператор сопоставления по шаблону (match-case).

Фото Нет слов, одни... однострочники

Нет слов, одни... однострочники

На днях вышел пост со списком полезных однострочников для JavaScript программистов. Памятуя Perl-овую молодость, заглянул туда.

Фото Добавляем переменные в контекст Django шаблонов (свой контекст-процессор)

Добавляем переменные в контекст Django шаблонов (свой контекст-процессор)

В Django вы можете передавать данные в шаблоны посредством контекстов. Контекст передаётся из контроллера (view в терминах Django), однако, если одни и те же данные нужны в разных местах, лучше сделать свой контекст-процессор.