Анонимные функции и замыкания в Golang

После изучения данного урока вы сможете:

  • Присваивать функции переменным;
  • Передавать функции другим функциям;
  • Создавать функции для создания других функций.

В Go функции можно присвоить переменным, передать функции другим функциям или даже написать функции для возвращения функций. Функции первого класса работают там же, где типы integer, string и прочие.

Форум Гоферов

Мы работаем над форумом для программистов на Golang. Очень нужны модераторы которые хотят помочь с ответами для новичков и помочь в развитии Go-сообщества.

Go на Форум

Уроки, статьи и Видео

Мы публикуем в паблике ВК и Telegram качественные обучающие материалы для быстрого изучения Go. Подпишитесь на нас в ВК и в Telegram. Поддержите сообщество Go программистов.

Go в ВК Go в Telegram

Содержание статьи

В данном уроке раскрывается потенциал использования функций первого класса как элементов теоретической программы «Станции экологического мониторинга Ровер (REMS)», что считывает данные температурных сенсоров.

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

Функции первого класса можно сравнить с мясом, которому нужно добавить кетчупа. Представим код, в котором функции makeMeat нужно вызвать функцию для кетчупа, будь то makeKetchup или openKetchup. Функции для кетчупа также можно использовать отдельно, однако мясо без кетчупа будет не таким вкусным.

Помимо рецептов и сенсоров температуры, какие другие примеры изменения функции с помощью другой функции вы можете привести?

Присваивание функции переменной в Go

Сенсоры станции погоды предоставляют данные о температуре воздуха в диапазоне 150–300° K. У нас есть функции для конвертации градусов Кельвина в другие единицы измерения при наличии данных, однако при отсутствии специального сенсора, встроенного в компьютер (или Raspberry Pi), считывающего информацию, это может стать проблематично.

Пока мы можем использовать фальшивый сенсор, что будет возвращать псевдослучайные числа, однако в таком случае нужно будет найти способ использовать realSensor или fakeSensor взаимозаменяемо. В следующем примере именно это происходит. При создании такой программы различные реальные сенсоры также могут быть подключены, к примеру, для сбора данных как о температуре земли, так и для температуры воздуха.

В данном примере переменная sensor присваивает функцию fakeSensor, но не сам результат вызова данной функции. Вызовы функции и метода всегда со скобками вроде fakeSensor(), но здесь другой случай.

Теперь вызов sensor() сразу вызовет realSensor или fakeSensor, в зависимости от того, к какой функции присваивается sensor.

Переменная sensor является типом той функции, что не принимает параметров и возвращает результат типа kelvin. Если не полагаться на автоматическое присваивание типа, переменная сенсора может быть объявлена следующим образом:

В Листинге 1 вы можете заново назначить sensor к realSensor из-за совпадения с сигнатурой функции fakeSensor. У обеих функций одинаковое количество параметров с тем же типом, они также возвращают значения.

Вопросы для проверки:

  1. В чем различие между присваиванием функции переменной и присваиванием результата вызываемой функции?
  2. Если бы существовала функция groundSensor, что возвращала бы температуру celsius, можно было бы ее присвоить к sensor из Листинга 1?

Передача функции другой функции в Golang

Переменные могут отсылаться к функциям, а также передаваться функциям. Это значит, что Go допускает передачу одних функций другим.

Для фиксирования данных о температуре каждую секунду в Листинге 2 объявляется новая функция measureTemperature, что принимает функцию сенсора в качестве параметра. Она периодически вызывает функцию сенсора, будь то fakeSensor или realSensor.

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

Функция measureTemperature принимает два параметра, второй параметр принадлежит к типу func() kelvin. Объявление выглядит как объявление переменной того же типа:

Функция main может передать название функции к measureTemperature.

Вопрос для проверки: 

В чем преимущество передачи функции другой функции?

Объявление типов функции в Golang

В Go есть возможность объявления нового типа для функции, что позволяет сократить и уточнить код, к которому она относится. Мы использовали тип kelvin скорее для передачи единицы измерения температуры, чем для базового представления. То же самое может быть сделано для переданных функций:

В коде речь идет не о функции, что не принимает параметров, возвращая значение kelvin, а о функциях sensor. Данный тип можно использовать для сокращения кода. Таким образом следующее объявление:

Можно переписать подобным образом:

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

Задание для проверки:

Перепишите следующую сигнатуру функции для использования типа функции:

Замыкания (Closures) и анонимные функции Golang

В Go анонимными функциями, или функциональными литералами называют функции без названия. В отличие от обычных функций, функциональные литералы являются замыканиями, потому что они сохраняют отсылки к переменным в окружающей области видимости.

Вы можете присвоить анонимную функцию переменной, и затем использовать данную переменную как любую другую функцию, как показано в примере ниже:

Объявленная переменная может быть в области видимости пакета или внутри функции, как показано в листинге ниже:

Можно объявить и задействовать анонимную функцию как показано ниже:

Анонимные функции могут пригодиться, если нужно быстро написать функцию на ходу. Это может быть возвращение одной функции от другой функции. Хотя функция может возвращать существующую именованную функцию, объявлять и возвращать новую анонимную функцию может быть гораздо полезнее.

В Листинге 6 функция calibrate настраивается на ошибки в показаниях температуры воздуха. С помощью функции первого класса calibrate принимает фейковый или реальный сенсор в качестве параметра и возвращает функцию замены. Всякий раз, когда вызывается новая функция sensor, вызывается исходная функция, а чтение корректируется смещением.

Анонимная функция в предыдущем листинге использует замыкания. Это отсылка к переменным s и offset, что функция calibrate принимает в качестве параметров. Даже после возвращения функции calibrate, переменные, зафиксированные замыканием, остаются. Таким образом, у sensor все еще есть доступ к данным переменным. Анонимная функция нужна для размыкания переменных в области видимости, что объясняет термин замыкание.

Из-за того, что замыкание сохраняет ссылку на ближайшие переменные, а не копирует их значения, изменения с этими переменными отражаются в вызовах к анонимной функции:

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

Вопросы для проверки:

  1. Как по-другому называется анонимная функция в Go?
  2. На что способны замыкания, но не могут обычные функции?

Заключение

  • Функции первого класса открывают новые возможности для разделения и повторного использования кода;
  • Для создания функции на ходу используйте анонимные функции с замыканиями.

Итоговое задание для проверки:

Наберите Листинг 6 на Go Playground и посмотрите на программу в действии:

  • Вместо передачи 5 в качестве аргумента для calibrate, объявите и передайте переменную. Измените переменную и убедитесь, что результатом вызовов к sensor() по-прежнему является 5. Все из-за того, что параметр offset является копией аргумента (переданный через значение);
  • Используйте calibrate с функцией fakeSensor из Листинга 2 для создания новой функции sensor. Вызовите новую функцию sensor несколько раз, обратите внимание, что оригинальная функция fakeSensor по-прежнему вызывается каждый раз, выдавая в результате случайные значения.

Понравилась статья?

Поддержи наш проект, чтобы мы могли создать больше хорошего контента!