picolisp.ru
Переключить темный/светлый/авто режим Переключить темный/светлый/авто режим Переключить темный/светлый/авто режим Back to homepage

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

Если данные и код - одно и то же, зачем в PicoLisp нужна функция de?

В действительности, она не нужна. Ключевое слово 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"!. Следующая (последняя) часть этой серии будет посвящена функциям работы со списками и строками.