пятница, 14 мая 2010 г.

Продолжаем мучить Common Lisp

Продолжаю мучить Common Lisp, хотя ему всё равно - он железный, а вот себя я мучаю гораздо сильнее. Уж слишком головоломный язык, даже небольшой изученной мной частичке Common Lisp трудно уместиться в моей голове, это что-то находящееся за гранью моего предыдущего опыта.

1. Пересчёт секунд, минут и часов

Закрепляем уже пройденный материал, пытаясь применить уже известное в чуть-чуть более практической, приземлённой задаче.
;; Переводит часы, минуты и секунды в секунды
(defun hms-to-seconds (h m s)
       (+ (* h 3600) (* m 60) s))

;; Переводит секунды в часы, минуты и секунды
(defun seconds-to-hms (sec)
       (list (floor (/ sec 3600))
             (floor (/ (mod sec 3600) 60))
             (mod sec 60)))

;; Проверка правильности показаний часов:
;; часы не показывают отрицательные числа
;; а минуты и секунды не могут быть больше 59
(defun hms-ok (h m s)
       (and (>= h 0)
            (>= m 0)
            (>= s 0)
            (< m 60)
            (< s 60)))

;; Нормализовать часы, минуты и секунды:
;; перевести их в вид, пригодный для
;; отображения на часах
(defun normalize-hms (h m s)
       (seconds-to-hms (hms-to-seconds h m s)))

;; Перевести секунды в минуты,
;; возвращается количество полных минут
(defun seconds-to-minutes (sec)
       (floor (/ sec 60))) 

;; Перевести секунды в часы,
;; возвращается количество полных часов
(defun seconds-to-hours (sec)
       (floor (/ sec 3600)))

;; Получить показания секундной стрелки часов
(defun s-from-seconds (sec)
       (mod sec 60))

;; Получить показания минутной стрелки часов
(defun m-from-seconds (sec)
       (mod (floor (/ sec 60)) 60))

;; другой вариант одноимённой функции
;(defun m-from-seconds (sec)
;       (floor (/ (mod sec 3600) 60)))

;; Получить показания часовой стрелки часов
(defun h-from-seconds (sec)
       (floor (/ sec 3600)))

;; другой вариант одноимённой функции
;(defun seconds-to-hms (sec)
;       (list (h-from-seconds sec)
;             (m-from-seconds sec)
;             (s-from-seconds sec)))
2. Пример использования замыканий

Трудно объяснить всю нижеследующую магию простыми словами. Здесь используются анонимные функции (они же lambda), передача ссылки на функцию - это ещё можно понять. Но вот переменные в Common Lisp довольно необычны.

Я толком не усвоил терминологию, но своими словами могу сказать следующее: существуют глобальные переменные и локальные переменные. Глобальные переменные доступны из любого места программы, локальные - только в пределах области видимости.

Интересная особенность заключается в том, что можно в любом месте программы создать дополнительную область видимости, внутри которой переопределить значение переменной. Это делается с помощью оператора let. Все обращения к этой переменной внутри блока будут происходить к локальному же значению этой переменной. Вне оператора let переменная восстанавливает своё прежнее значение. Но при этом, в нижеследующем примере, анонимная функция сохраняет внутри себя переопределённую с помощью оператора let переменную и её значение. Это и называется замыканием. Очень необычно.
;;;; Пример использования замыканий

;; Функция, возвращающая фукцию-счётчик
;; Начальное значение счётчика - 0
(defun get-counter ()
       (let ((n 0))
            (function (lambda ()
                              (setf n (+ n 1))))))

;; Более лаконичный аналог предыдущей функции
;(defun get-counter ()
;       (let ((n 0))
;            #'(lambda ()
;                      (incf n))))

;; Переменная с первой функцией-счётчиком
(setf counter1 (get-counter))

;; Переменная со второй функцией-счётчиком
(setf counter2 (get-counter))

;; Более лаконичный вариант двух предыдущих присваиваний
;(setf counter1 (get-counter)
;      counter2 (get-counter))

;;; Попеременно вызываем счётчики
;;; Счётчики при каждом вызове увеличиваются на 1
(funcall counter1)

(funcall counter2)

(funcall counter1)

(funcall counter1)

(funcall counter1)

(funcall counter2)
В этом примере функция counter1 будет наращивать значение своего счётчика, а функция counter2 - значение своего.

3. Чуть более сложный пример использования замыканий

Здесь уже используются ассоциативные массивы, а функция get-counter-methods возвращает в ассоциативном массиве три функции, замкнутые на одной переменной.
;;;; Ещё один пример использования замыканий

;; Функция возвращает ассоциативный список
;; из трёх функций, замкнутых на одну переменную
(defun get-counter-methods ()
  (let ((n 0))
    (list
     :increase #'(lambda () (incf n))
     :decrease #'(lambda () (decf n))
     :value #'(lambda () n))))

;; Создаём счётчик, содержащий ассоциативный
;; список трёх функций
(setf counter (get-counter-methods))

;;; Далее вызываем функции и следим
;;; за состоянием счётчика
(funcall (getf counter :increase))

(funcall (getf counter :value))

(funcall (getf counter :decrease))

(funcall (getf counter :value))

Комментариев нет:

Отправить комментарий