SICP. Высшие функции

В прошлой статье про простые процессы мы разобрались, что функции это просто шаблоны для подстановки значений.

Такое же можно делать с функциями — передать её в другую функцию как аргумент и вернуть как значение. Такие функции называются высшими и они выше по уровню абстракции, чем обычные функции

Вот например, мы ищем сумму ряда чисел и сумму ряда квадратов чисел через рекурсивные функции

# Функция для поиска суммы чисел в последовательности
def sum_sequence(start, end):
    if start > end:
        return 0
    return start + sum_sequence(start + 1, end)


# Функция для поиска суммы квадратов чисел
def sum_square_sequence(start, end):
    if start > end:
        return 0
    return start ** 2 + sum_square_sequence(start + 1, end)


print(sum_sequence(1, 10))  # => 55
print(sum_square_sequence(1, 10))  # => 385

1. Пишем схему вычислений

Мы описали абстракцию суммы двух разных рядов, но схема вычислений везде одинаковая: проверяем условие, если start > end то возвращаем 0, иначе возвращаем сумму текущего значения и значение вызова функции.

Переделаем это всё в высшую функцию и назовём sum. Она будет принимать четыре аргумента:

  • current_change — функция для изменения значения start на текущем шаге
  • next_change — функция для изменения start на следующий шаг
  • start — значение нижней границы
  • end — значение верхней границы

Появляется проблема: чтобы использовать функцию sum нужны дополнительные функции, потому что изменить значения вручную не получится. Пишем дополнительные функции и упаковываем в код

# Функция для увеличения значения на единицу
def inc(x):
    return x + 1


# Функция для поиска суммы чисел в последовательности
def sum_sequence(start, end):
    # Возвращает переданное значение
    def identity(x):
        return x

    return sum(identity, inc, start, end)


# Функция для поиска суммы квадратов чисел в последовательности
def sum_square_sequence(start, end):
    # Возводит значение в квадрат
    def square(x):
        return x * x

    return sum(square, inc, start, end)


def sum(current_change, next_change, start, end):
    if start > end:
        return 0
    return current_change(start) + sum(current_change, next_change, next_change(start), end)


print(sum_sequence(1, 10))  # => 55
print(sum_square_sequence(1, 10))  # => 385

2. Рефакторим код

Всё работает, но это выглядит не красиво. Тут помогут анонимные функции — лямбды, в математике и программировании их обычно используют как одноразовые объекты.
Команда lambda создаёт функцию без имени и если её никто не использует, то транслятор её пропускает и не тратит память.

# интерпретатор просто создаст функцию и потом удалит её из памяти, она никак не используется
lambda x: x * x

# "x" – аргумент, после него пишется то, что вернёт функция. Работает как return
square = lambda x: x * x 
print(square(4))  # => 16

square = lambda x: x * x по сути то же самое, что и обычное объявление def square(x), поэтому линтер пайтона будет предлагать переписать функцию через def.

Переписываем код ещё раз с помощью лямбда функций

def sum_sequence(start, end):
    return sum(lambda x: x, lambda x: x + 1, start, end)


def sum_square_sequence(start, end):
    return sum(lambda x: x * x, lambda x: x + 1, start, end)


def sum(current_change, next_change, start, end):
    if start > end:
        return 0
    return current_change(start) + sum(current_change, next_change, next_change(start), end)


print(sum_sequence(1, 10))  # => 55
print(sum_square_sequence(1, 10))  # => 385

Теперь красиво, но это придуманный пример для наглядности. Сделаем что-то наглядное, например точки на графике.

Делаем координаты

Напишем свою библиотеку для работы с точками, самой крутой абстракцией будет точка. У точки есть две координаты: (x, y), чтобы хранить их создадим новый тип данных — пару.
Для кода возьмём имена из Скима: cons, car и cdr.
cons — создаёт список
car — достаёт первый элемент из списка
cdr — достаёт все остальные элементы

Функции car и cdr работают благодаря замыканию, мы разбирались с ним в статье про области видимости.

Теперь у нас есть новый тип данных — пара, его можно использовать для абстракции всего, что можно представить через пару чисел — точка, дробное число, комплексное число и куча всего остального.

Дальше забываем как устроена пара и используем её интерфейс чтобы написать реализацию точки

С помощью абстракции код становится легче для понимания, выглядит красивее и рефакторится без боли: мы можем изменить реализацию пары и нам не придётся переписывать код точки и всего остального над ней.

Теперь мы можем работать с абстракцией точки, нам не важно как она устроена внутри и почему работает, мы просто знаем как её использовать. Чтобы увидеть наши точки, импортируем модуль pyplot из библиотеки matplotlib.

Через свою библиотеку мы создали три точки с координатами (2, 4), (4, 6), (6, 4), а через pyplot нарисовали их
Поделиться
Отправить
Запинить
 27   1 мес   sicp
Популярное