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

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

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

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