Когда мы рассказывали про функциональное программирование, то привели в пример несколько языков, которые подходят для этого лучше всего. В этом списке есть особенный язык — Lisp. Особенный он потому, что именно с него и началось всё функциональное программирование.
Что такое Lisp
Lisp — это язык программирования, который сейчас используется для обработки и анализа данных. Язык очень старый, но иногда используется и сейчас для узкоспециализированных задач типа машинного обучения.
Язык высокоуровневый, то есть код написан более-менее человеческим языком и поддерживает сложную логику и работу со строками и числами в привычном нам формате.
Что работает на Lisp
На Lisp написан софт, который мониторит состояние самолётов — например «Боингов» и «Эйрбасов». Также Lisp лежит в основе софта, который анализирует и распределяет ресурсы в лондонском метрополитене.
На Lisp работает система обработки данных Apache Storm, текстовый анализатор Grammarly. На этом же языке сделан Circle CI — система постоянной выкатки новых версий софта (CI/CD).
А ещё Lisp используется тут:
- AutoCAD — язык использует внутренние команды для автоматического проектирования.
- Emacs — многофункциональный текстовый редактор, в котором Lisp выступает и как язык, на котором написан сам Emacs, и как язык внутренней обработки текста.
- Audacity — программы для работы со звуком.
На Lisp не пишут игры и приложения, это не язык для создания сложных экранных интерфейсов или быстрого прототипирования мобильных приложений.
Как и зачем появился Lisp
В 1950-х программисты и математики вплотную начали работать над искусственным интеллектом. Нейросети тогда были чисто математической моделью без практического применения, поэтому учёные взяли за основу такую идею:
Чтобы искусственный интеллект работал как настоящий, он должен быть устроен точно так же. А раз наш интеллект основан на мыслях, которые мы можем выразить словами, то нам нужно научить компьютер разбираться в словах, их смыслах и взаимосвязи между ними. Проще говоря, чтобы компьютер мог выразить свою мысль словами точно так же, как это делает человек.
Все наши мысли можно сформулировать в виде предложений, которые состоят из слов, а слова — из букв. Получается, что искусственный интеллект должен легко оперировать словами и символами и знать, в каком порядке их надо выстроить.
В то время ни один из существующих языков не позволял работать со строками и символами на таком уровне, поэтому Джон Маккарти в 1958 написал первую версию языка Lisp. Это название — сокращение от LISt Processing language, что переводится как «язык обработки списков». Списком может быть что угодно: символ, слово, предложение, другой список или даже список функций.
Пример кода
Чтобы сразу было понятно, чем этот язык отличается от других, вот два примера кода на Lisp. Первый обрабатывает два комплексных числа:
; функция сложения двух комплексных чисел
(defun AddCom(Com1 Com2)
(cons (+ (car Com1)(car Com2))
(cons (+ (cadr Com1)(cadr Com2)) NIL)))
; функция вычитания двух комплексных чисел
(defun SubCom(Com1 Com2)
(cons (- (car Com1)(car Com2))
(cons (- (cadr Com1)(cadr Com2)) NIL)))
; функция сравнения двух комплексных чисел
(defun EqCom(Com1 Com2)
(and (= (car Com1)(car Com2))
(= (cadr Com1)(cadr Com2))))
; функция умножения двух комплексных чисел
(defun MultCom(Com1 Com2)
(cons (- (* (car Com1)(car Com2))
(* (cadr Com1)(cadr Com2)))
(cons (+ (* (car Com1)(cadr Com2))
(* (cadr Com1)(car Com2))) NIL)))
; функция деления комплексных чисел
(defun DivCom(Com1 Com2)
(let ((z (+ (* (car Com2)(car Com2))
(* (cadr Com2)(cadr Com2))) ))
(cons (/ (+ (* (car Com1)(car Com2))
z)(* (cadr Com1)(cadr Com2)))
(cons (/ (- (* (cadr Com1)(car Com2))
z) (* (car Com1)(cadr Com2)))
NIL ))))
; вычисление суммы чисел 2+3i и 1.5-8i
(AddCom '(2 3) '(1.5 -8))
; вычисление произведения чисел 3-i и 3+i
(MultCom '(3 -1) '(3 1))
А вот код, который сам анализирует с точки зрения математики введённую строку и выполняет все математические операции:
(Defun weight (x)
(COND ((EQ x '+) 1)
((EQ x '-) 1)
((EQ x '*) 2)
((EQ x '\) 2)
((EQ x '/) 2)
((EQ x '^) 3)
(T 5)) )
(Defun opcode (op)
(COND ((EQ op '+) '+)
((EQ op '-) '-)
((EQ op '*) '*)
((EQ op '\) '\)
((EQ op '/) '/)
((EQ op '^) '^)
(T (RAISEERROR (STRCAT "Неверен код операции " (OUTPUT op))))) )
(Defun inf-aux (ae operators operands)
(inf-iter (CDR ae) operators (CONS (CAR ae) operands)))
(Defun inf-iter (ae operators operands)
(PROG NIL
(COND ((AND (NULL ae) (NULL operators)) (RETURN (CAR operands))))
(COND ((AND
(NOT (NULL ae))
(OR (NULL operators) (GREATERP (weight (CAR ae)) (weight (CAR operators))))
)
(RETURN (inf-aux (CDR ae) (CONS (CAR ae) operators) operands))))
(RETURN (inf-iter ae (CDR operators)
(CONS (LIST (opcode (CAR operators))
(CADR operands) (CAR operands))
(CDDR operands))))))
(Defun inf2pref (x)
(PROG (hd tl cc xx rr)
(COND ((atomlist x) (RETURN (inf-aux x NIL NIL))))
(SETQ rr NIL)
(SETQ xx x)
LOOP (SETQ hd (CAR xx))
(SETQ tl (CDR xx))
(COND ((memb hd (QUOTE (SIN COS LOG EXP ATN ASN ACS SH CH SQR SIGN ABS)))
(PROGN (SETQ rr (APPEND rr (LIST (LIST hd (inf2pref (CAR tl))))))
(SETQ tl (CDR tl))))
((ATOM hd)
(SETQ rr (APPEND rr (LIST hd))))
(T (SETQ rr (APPEND rr (LIST (inf2pref hd))))))
(COND ((NULL tl)
(RETURN (inf-aux rr NIL NIL)))) (SETQ xx tl) (GO LOOP)))
(Defun CalcExpr (expression) (EVAL (inf2pref (PARSE expression))))
Штука в том, что математических символов может быть сколько угодно и они могут стоять в любом порядке — Lisp их все найдёт, проанализирует, сам выстроит как нужно, посчитает и выдаст ответ. А всё потому, что этот язык изначально был заточен на обработку строк и символов.
Вот что получится, если запустить второй пример и попробовать вычислить несколько выражений:
(calcExpr "5+(7-4)*5")
==> 20
(calcExpr "(sin(1))^2+(cos(1))^2")
==> 1.000000000000000E+0
Особенности языка
Скобки и списки. Про скобки в Lisp есть тысячи мемов и шуток, а всё потому, что круглые скобки — это обозначение списка. В Lisp в списке может быть что угодно: другие команды, слова, символы, функции, списки в списках и так далее. И всё это может быть даже внутри одного списка, а потом передаваться в работу в качестве параметра в другой список.
Функции можно объявить где угодно. Действительно где угодно — даже в середине строки (которая в Lisp — тоже список, который будет обработан). Код из-за этого может стать менее читаемым, но если вы пишете на Lisp, то читаемость любого кода для вас вообще не проблема.
(defun назвать (name lv) (eval (cons 'defun (cons name (cdr lv)))))
(назвать 'сложить '(lambda (x y) (+ x y)))
(сложить 5 7)
==>12
Создание своих правил. Главная задача языка Lisp – обработать ваши списки по вашим же правилам. Это самая суть функционального программирования: вы задаёте правила обработки, а язык сам разбирается, как и где их применить, куда передать результат и что с ним делать дальше.
В чём хорош Lisp
Суперсила классического Lisp – в его умении работать с текстом: разбирать его на составляющие, искать связи, делать выводы и всё такое. При этом текстом может быть что угодно:
- обычный текст;
- статьи и базы знаний;
- код на других языках;
- внутренние команды какой-то программы, например AutoCAD;
- команды для станков;
- управляющая последовательность байтов на сервере;
- база данных.
Сейчас чаще используют Common Lisp — потомок оригинального языка, в который добавили новые команды, структуры данных и возможности для императивного программирования. Common Lisp можно применять везде, где есть правила обработки данных — от медицины и работы над геномом человека до внутренних систем передачи данных внутри интернет-провайдера.