60 функций PicoLisp. Часть 5: Определяем функции
Перевод. Оригинал здесь
Это восьмая часть цикла Введение в PicoLisp и пятая часть серии "60 функций PicoLisp".
В этой статье будет рассмотрено создание функций.
Так же, как переменные - это какие-либо данные, привязанные к имени символа, так и функции - это набор инструкций, который тоже привязан к имени функции.
Что такое функции в языке PicoLisp? Это кусочки кода, которые могут
быть выполнены. Если вы читали статьи учебного цикла по порядку, вы
наверное вводили команды в REPL. Наверняка вы заметили, что каждое
выражение, которые вы вводили, возвращает значение. Это та часть,
которая идёт после ->
.
Что мы знаем на текущий момент? Что в PicoLisp могут выполняться не только функции, но вообще любые выражения. Вот некоторые последствия такого поведения:
- Не нужно явным образом определять возвращаемое из функции значение. В процессе работы функции возвращение значений происходит по умолчанию.
- Можно создавать, хранить и изменять функции в переменных.
- Одни функции могут принимать другие в качестве аргументов, а также возвращать функцию, как результат вычисления.
Фактически, в PicoLisp полностью отсутствует разница между кодом и данными. В дальнейшем, когда мы будем обсуждать функциональное программирование, вы увидите интересное применение этой особенности.
Новые функции определяются с помощью de
. Не бывает учебных пособий без
функции "print "Hello, World!". Держите, вот она:
: (de hello ()
(prinl "Hello, World!") )
: (hello)
Hello World
-> "Hello World"
Пустой список ()
на первой строке означает, что у нашей функции нет
аргументов. Его можно спокойно заменить на NIL
, поведение останется
прежним.
Исполняемое тело функции состоит из одного выражения, располагающегося на второй строке.
Как видите, функция сначала печатает Hello, World!
, а затем
возвращает значение "Hello, World!"
По этой причине можно с лёгкостью передавать функцию в качестве аргумента в другое выражение, например, вот так:
: (println (hello))
Hello World
"Hello World"
-> "Hello World"
Как уже говорилось ранее, отступы и переводы строки служат лишь для повышения читаемости кода. Интерпретатором они игнорируются. Можно записать нашу функцию в одну строку, и она продолжит работать так же, как и прежде:
: (de hello() (prinl "Hello world"))
Функцию с одним аргументом можно определить следующим способом:
: (de hello (X)
(prinl "Hello " X) )
# hello redefined
Поскольку функция с именем "hello" уже была определена, picolisp уведомляет вас, что вы её переопределили. Такое уведомление может быть полезным. Например, в случае, если вы пытаетесь использовать символ, который уже определили ранее.
Давайте посмотрим, как работает наша новая функция:
: (hello "World")
Hello World
-> "World"
: (hello "Mia")
Hello Mia
-> "Mia"
Наша новая функция стала вычисляться всего в одно слово вместо фразы.
Так происходит потому, что функция prinl
вызвана с двумя аргументами.
Она берёт из один за другим и выводит на экран. Последний аргумент -
"World", именно его она возвращает, как результат вычисления.
Функции с двумя и болеё аргументами создаются аналогично:
: (de multiply (X Y) (* X Y))
: (multiply 3 4)
-> 12
Если вы хотите передавать в функцию переменное количество
аргументов, то вместо списка передайте символ @
. PicoLisp будет
обрабатывать поступающие аргументы соответствующим образом. Получить
доступ к аргументам внутри такой функции можно с помощью встроенных
функций args
, next
, arg
и rest
.
Давайте напишем функцию foo
, которая будет выводить число и его
квадрат для любого количества переданных ей чисел:
: (de foo @
(while (args) # Check if there are some args left
(let N (next)
(println N (* N N)) ) ) )
while
проверяет, остались ли ещё аргументы, let N (next)
присваивает
локальной переменной N значение очередного аргумента. Это значит, что N
определена только внутри конструкции (let ...)
, снаружи её значение не
существует и получить к нему доступ нельзя.
Давайте теперь запустим нашу функцию с пятью аргументами, два из которых просто будут числами, а три - выражениями, которые надо предварительно вычислить.
: (foo 2 (+ 2 3) (- 7 1) 1234 (* 9 9))
2 4
5 25
6 36
1234 1522756
81 6561
Следующий пример показывает работу функций args
и rest
:
: (de foo @
(while (args)
(println (next) (args) (rest)) ) )
: (foo 1 2 3)
1 T (2 3)
2 T (3)
3 NIL NIL
Функция arg
возвращает аргумент по его порядковому номеру:
: (de foo @
(println (arg 1) (arg 2))
(println (next))
(println (arg 1) (arg 2)) )
: (foo 'a 'b 'c)
a b
a
b c
Некоторые из вас наверняка ожидают, что в этом разделе пойдёт разговор о
лямбдах - специальных функциях, предназначенных для создания анонимных
функций. Однако PicoLisp не нуждается в подобного рода конструкциях. В
предыдущих статьях говорилось о том, что функция quote
, сокращенной
записью которой является одинарная кавычка '
, предотвращает вычисление
выражения. Именно с её помощью создаются анонимные функции в PicoLisp.
: ((quote (X) (* X X)) 9)
-> 81
: # equivalent to:
: ('((X)(* X X)) 9)
-> 81
В действительности, она не нужна. Ключевое слово setq
, которое
используется для определения переменных, может использоваться и для
создания функций:
: (setq f '((X) (* X X)))
: f
-> ((X) (* X X))
: (f 3)
-> 9
Обратите внимаеие: этот способ работает только когда вычисление
выражения блокируется функцией quote
. В противном случае интерпретатор
попытается вычислить X, что приведёт к ошибке:
: (setq f ((X) (* X X)))
!? (X)
X -- Undefined
Понимание такой взаимозаменяемости кода и данных (которое также называют гомоиконичностью, или гомоиконностью) поначалу может вызвать определённые трудности. Мы ещё не раз вернёмся к этой особенности языка позднее.
Системные команды могут быть вызваны с помощью функции call
. В случае
успеха она возвращает T
. При этом код завершения дочернего процесса
(который зависит от системы) сохраняется в глобальной переменной @@
.
: (call 'whoami)
mia
# Проверяем, существует ли файл и доступен ли он для чтения
: (when (call 'test "-r" "file.l")
(load "file.l") # Загружаем его содержимое
(call 'rm "file.l") ) # Удаляем файл
Поздравляем, вы почти добрались до конца серии "60 функций PicoLisp"!. Следующая (последняя) часть этой серии будет посвящена функциям работы со списками и строками.