После изучения данного урока вы сможете:
- Присваивать функции переменным;
- Передавать функции другим функциям;
- Создавать функции для создания других функций.
В Go функции можно присвоить переменным, передать функции другим функциям или даже написать функции для возвращения функций. Функции первого класса работают там же, где типы integer
, string
и прочие.
Рекомендуем вам супер TELEGRAM канал по Golang где собраны все материалы для качественного изучения языка. Удивите всех своими знаниями на собеседовании! 😎
Мы публикуем в паблике ВК и Telegram качественные обучающие материалы для быстрого изучения Go. Подпишитесь на нас в ВК и в Telegram. Поддержите сообщество Go программистов.
Содержание статьи
- Присваивание функции переменной
- Передача функции другой функции
- Объявление типов функции
- Замыкания (Closures) и анонимные функции
В данном уроке раскрывается потенциал использования функций первого класса как элементов теоретической программы «Станции экологического мониторинга Ровер (REMS)», что считывает данные температурных сенсоров.
Для сравнения подумаем о примере из реального мира. Мясо будет вкуснее с кетчупом. После приготовления мясного блюда вы можете найти домашний рецепт кетчупа, но зачем все так усложнять — сходите за кетчупом в обычный магазин.
Функции первого класса можно сравнить с мясом, которому нужно добавить кетчупа. Представим код, в котором функции makeMeat
нужно вызвать функцию для кетчупа, будь то makeKetchup
или openKetchup
. Функции для кетчупа также можно использовать отдельно, однако мясо без кетчупа будет не таким вкусным.
Помимо рецептов и сенсоров температуры, какие другие примеры изменения функции с помощью другой функции вы можете привести?
Присваивание функции переменной в Go
Сенсоры станции погоды предоставляют данные о температуре воздуха в диапазоне 150–300° K. У нас есть функции для конвертации градусов Кельвина в другие единицы измерения при наличии данных, однако при отсутствии специального сенсора, встроенного в компьютер (или Raspberry Pi), считывающего информацию, это может стать проблематично.
Пока мы можем использовать фальшивый сенсор, что будет возвращать псевдослучайные числа, однако в таком случае нужно будет найти способ использовать realSensor
или fakeSensor
взаимозаменяемо. В следующем примере именно это происходит. При создании такой программы различные реальные сенсоры также могут быть подключены, к примеру, для сбора данных как о температуре земли, так и для температуры воздуха.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
package main import ( "fmt" "math/rand" ) type kelvin float64 func fakeSensor() kelvin { return kelvin(rand.Intn(151) + 150) } func realSensor() kelvin { return 0 // TO-DO: внедрить реальный сенсор } func main() { sensor := fakeSensor // Присваивает функцию переменной fmt.Println(sensor()) sensor = realSensor fmt.Println(sensor()) } |
В данном примере переменная sensor
присваивает функцию fakeSensor
, но не сам результат вызова данной функции. Вызовы функции и метода всегда со скобками вроде fakeSensor()
, но здесь другой случай.
Теперь вызов sensor()
сразу вызовет realSensor
или fakeSensor
, в зависимости от того, к какой функции присваивается sensor
.
Переменная sensor
является типом той функции, что не принимает параметров и возвращает результат типа kelvin
. Если не полагаться на автоматическое присваивание типа, переменная сенсора может быть объявлена следующим образом:
1 |
var sensor func() kelvin |
В Листинге 1
вы можете заново назначить sensor
к realSensor
из-за совпадения с сигнатурой функции fakeSensor
. У обеих функций одинаковое количество параметров с тем же типом, они также возвращают значения.
Вопросы для проверки:
- В чем различие между присваиванием функции переменной и присваиванием результата вызываемой функции?
- Если бы существовала функция
groundSensor
, что возвращала бы температуруcelsius
, можно было бы ее присвоить кsensor
из Листинга 1?
Передача функции другой функции в Golang
Переменные могут отсылаться к функциям, а также передаваться функциям. Это значит, что Go допускает передачу одних функций другим.
Для фиксирования данных о температуре каждую секунду в Листинге 2 объявляется новая функция measureTemperature
, что принимает функцию сенсора в качестве параметра. Она периодически вызывает функцию сенсора, будь то fakeSensor
или realSensor
.
Возможность передачи функций представляет собой мощный способ, что позволяет поделить код на части. Без учета функций первого класса вы бы наверняка пришли к функциям measureRealTemperature
и measureFakeTemperature
, что содержат идентичный код.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
package main import ( "fmt" "math/rand" "time" ) type kelvin float64 func measureTemperature(samples int, sensor func() kelvin) { // measureTemperature принимает функцию в качестве второго параметра for i := 0; i < samples; i++ { k := sensor() fmt.Printf("%v° K\n", k) time.Sleep(time.Second) } } func fakeSensor() kelvin { return kelvin(rand.Intn(151) + 150) } func main() { measureTemperature(3, fakeSensor) // Передает название функции другой функции } |
Функция measureTemperature
принимает два параметра, второй параметр принадлежит к типу func() kelvin
. Объявление выглядит как объявление переменной того же типа:
1 |
var sensor func() kelvin |
Функция main
может передать название функции к measureTemperature
.
Вопрос для проверки:
В чем преимущество передачи функции другой функции?
Объявление типов функции в Golang
В Go есть возможность объявления нового типа для функции, что позволяет сократить и уточнить код, к которому она относится. Мы использовали тип kelvin
скорее для передачи единицы измерения температуры, чем для базового представления. То же самое может быть сделано для переданных функций:
1 |
type sensor func() kelvin |
В коде речь идет не о функции, что не принимает параметров, возвращая значение kelvin
, а о функциях sensor
. Данный тип можно использовать для сокращения кода. Таким образом следующее объявление:
1 |
func measureTemperature(samples int, s func() kelvin) |
Можно переписать подобным образом:
1 |
func measureTemperature(samples int, s sensor) |
В данном примере сложно увидеть улучшение, ведь теперь при просмотре строки кода нужно понимать, что собой представляет sensor
. Однако, если бы sensor
использовался в нескольких местах, или если бы у типа функции было несколько параметров, использование типа избавило бы от беспорядка.
Задание для проверки:
Перепишите следующую сигнатуру функции для использования типа функции:
1 |
func drawTable(rows int, getRow func(row int) (string, string)) |
Замыкания (Closures) и анонимные функции Golang
В Go анонимными функциями, или функциональными литералами называют функции без названия. В отличие от обычных функций, функциональные литералы являются замыканиями, потому что они сохраняют отсылки к переменным в окружающей области видимости.
Вы можете присвоить анонимную функцию переменной, и затем использовать данную переменную как любую другую функцию, как показано в примере ниже:
1 2 3 4 5 6 7 8 9 10 11 |
package main import "fmt" var f = func() { // Присваивает анонимную функцию переменной fmt.Println("Dress up for the masquerade.") } func main() { f() // Выводит: Dress up for the masquerade. } |
Объявленная переменная может быть в области видимости пакета или внутри функции, как показано в листинге ниже:
1 2 3 4 5 6 7 8 9 10 |
package main import "fmt" func main() { f := func(message string) { // Присваивает анонимную функцию переменной fmt.Println(message) } f("Go to the party.") // Выводит: Go to the party. } |
Можно объявить и задействовать анонимную функцию как показано ниже:
1 2 3 4 5 6 7 8 9 |
package main import "fmt" func main() { func() { // Объявляет анонимную функцию fmt.Println("Functions anonymous") }() // Вызов функции } |
Анонимные функции могут пригодиться, если нужно быстро написать функцию на ходу. Это может быть возвращение одной функции от другой функции. Хотя функция может возвращать существующую именованную функцию, объявлять и возвращать новую анонимную функцию может быть гораздо полезнее.
В Листинге 6 функция calibrate
настраивается на ошибки в показаниях температуры воздуха. С помощью функции первого класса calibrate
принимает фейковый или реальный сенсор в качестве параметра и возвращает функцию замены. Всякий раз, когда вызывается новая функция sensor
, вызывается исходная функция, а чтение корректируется смещением.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
package main import "fmt" type kelvin float64 // sensor function type type sensor func() kelvin func realSensor() kelvin { return 0 // TODO: имплементировать реальный сенсор } func calibrate(s sensor, offset kelvin) sensor { return func() kelvin { // Объявляет и возвращает анонимную функцию return s() + offset } } func main() { sensor := calibrate(realSensor, 5) fmt.Println(sensor()) Выводит: 5 } |
Анонимная функция в предыдущем листинге использует замыкания. Это отсылка к переменным s
и offset
, что функция calibrate
принимает в качестве параметров. Даже после возвращения функции calibrate
, переменные, зафиксированные замыканием, остаются. Таким образом, у sensor
все еще есть доступ к данным переменным. Анонимная функция нужна для размыкания переменных в области видимости, что объясняет термин замыкание.
Из-за того, что замыкание сохраняет ссылку на ближайшие переменные, а не копирует их значения, изменения с этими переменными отражаются в вызовах к анонимной функции:
1 2 3 4 5 6 7 8 9 |
var k kelvin = 294.0 sensor := func() kelvin { return k } fmt.Println(sensor()) // Выводит: 294 k++ fmt.Println(sensor()) // Выводит: 295 |
Имейте это в виду, особенно при использовании замыкания внутри циклов for.
Вопросы для проверки:
- Как по-другому называется анонимная функция в Go?
- На что способны замыкания, но не могут обычные функции?
Заключение
- Функции первого класса открывают новые возможности для разделения и повторного использования кода;
- Для создания функции на ходу используйте анонимные функции с замыканиями.
Итоговое задание для проверки:
Наберите Листинг 6 на Go Playground и посмотрите на программу в действии:
- Вместо передачи 5 в качестве аргумента для
calibrate
, объявите и передайте переменную. Измените переменную и убедитесь, что результатом вызовов кsensor()
по-прежнему является 5. Все из-за того, что параметрoffset
является копией аргумента (переданный через значение); - Используйте
calibrate
с функциейfakeSensor
из Листинга 2 для создания новой функцииsensor
. Вызовите новую функциюsensor
несколько раз, обратите внимание, что оригинальная функцияfakeSensor
по-прежнему вызывается каждый раз, выдавая в результате случайные значения.
Администрирую данный сайт с целью распространения как можно большего объема обучающего материала для языка программирования Go. В IT с 2008 года, с тех пор изучаю и применяю интересующие меня технологии. Проявляю огромный интерес к машинному обучению и анализу данных.
E-mail: vasile.buldumac@ati.utm.md
Образование
Технический Университет Молдовы (utm.md), Факультет Вычислительной Техники, Информатики и Микроэлектроники
- 2014 — 2018 Universitatea Tehnică a Moldovei, ИТ-Инженер. Тема дипломной работы «Автоматизация покупки и продажи криптовалюты используя технический анализ»
- 2018 — 2020 Universitatea Tehnică a Moldovei, Магистр, Магистерская диссертация «Идентификация человека в киберпространстве по фотографии лица»