14 октября 2011

Common Lisp tips.

Напоминаю, что Зак Бин открыл ресурс для хранения советов для common lisp.

15.10.11 Updated

Вот переводы некоторых.

Поменять местами

Простой путь поменять местами значения двух символов, a и b, выглядит так:

;; BOGUS
(setf temp a)
(setf a b)
(setf b temp)

psetf (параллельный setf) может сделать это за одну форму:

(psetf a b b a)

Но самая лучшая функция для этого rotatef:

(rotatef a b)

Перенаправить вывод

Есть функция, которая что-то выводит, но вы хотите, чтобы она выводила в другое место? Вы можете связать специальный символ *standard-output* в любых макросах, которые создают временные потоки.

Например, для перенаправления вывода в строку:

* (with-output-to-string (*standard-output*) 
    (print-marketing-report))
"Source,Hits
twitter,243
google,805
direct,47
"

Для перенаправления в файл:

* (with-open-file (*standard-output* #p"file.txt" :direction :output)
    (print-marketing-report))
NIL

Чтение чисел с плавающей точкой

Когда reader встречает число наподобие "3.0" без маркера экспоненты, он по умолчанию конвертирует его в single-float. Вы можете изменить тип используемый в конвертации связав символ *read-default-float-format* с другим типом числа с плавающей точкой.

Например:

* (/ 22.0 7.0)
3.142857

* (setf *read-default-float-format* 'double-float)
DOUBLE-FLOAT

* (/ 22.0 7.0)
3.142857142857143

При выводе также опускается маркер экспоненты, если тип выводимого числа совпадает с типом из *read-default-float-format*.

"Прикосновение" к файлу

* Прикосновение к файлу - действие, которое меняет дату модификации файла, если он существовал, иначе сздают файл.

Для создания пустого файла, как, например делает Unix команда touch, , вы можете использовать следующий код:

;; BOGUS
(close (open "foo.txt" :direction :output 
             :if-does-not-exist :create 
             :if-exists :append))

open принимает для аргумент :direction специальное значения для "прикосновения":

(open "foo.txt" :direction :probe :if-does-not-exist :create)

Если "foo.txt" не существует, он будет создан. Поток возвращается уже закрытым. Документация говорит следующее:

"[:probe] создание "no-directional" файлового потока; файловый поток создается и закрывается перед тем, как возвращается в качестве результата."

Многострочная строка форматирования

Вы можете разбить длинную стоку форматирования с помощью тильды ~ в концетильды ~ в конце каждой подстроки каждой подстроки. Например:

* (format t "It was the best of times, ~
             it was the worst of times.")
It was the best of times, it was the worst of times.

Тильда, перевод строки и все пробелы в следующей подстроке будут удалены при выводе, поэтому для выравнивания вы должны использовать пробелы перед тильдой.

"двоеточие" и "собака" модификаторы имеют дополнительный смысл:

При двоеточии, перевод строки игнорируется, но все пробелы на следующей подстроке не удаляются. При собаке перевод строки сохраняется, а все пробелы в начале подстроки игнорируются.

Преобразование символов в числа

Если у вас есть символ #\7 и вы хотите получить число 7, вы можете использовать форму (parse-integer (string char)) или ASCII-ориентированный алгоритм.

(- (char-int char) (char-int #\0))

В то время, как первый вариант будет давать правильный ответ всегда, второй вариант зависит от реализации. Спецификация описывает порядок символов в таблице, однако не дает никаких гарантий относительно возвращаемых значений функций char-int и char-code.

Все равно хотите их использовать? digit-char-p не только возвращает "истину", если первый аргумент является цифрой, но возвращает данную цифру для переданного символа.

* (digit-char-p #\7)
7

Это также работает с другими системами счислений:

* (digit-char-p #\a 16)
10

Если символ не является цифрой, digit-char-p возвращает nil.

:start и :end параметры для parse-integer

parse-integer принимает :start и :end аргументы, теперь вам не нужно вытаскивать подстроки из строки для передачи в функцию parse-integer. Например, для разбора такой строки, как "2011-10-01" в числа год, месяц и день, вы можете сделать так:

(defun parse-date (string)
  "Parse a date string in the form YYYY-MM-DD and return the
   year, month, and day as multiple values."
  (values (parse-integer string :start 0 :end 4)
          (parse-integer string :start 5 :end 7)
          (parse-integer string :start 8 :end 10)))

Сравнение нескольких объектов

Функции сравнения чисел =, /=, <, <=, >, >= могут принимает больше чем два аргумента. Теперь проверить, например, что числа составляют возврастающую последовательность, просто:

(< a b c d)
Для небольших списков чисел, используйте следующий алгоритм:
(apply #'< list)
Функции сравнения не чисел в основном принимают только два аргумента для сравнения, например, (string= x y z) неправильная форма, и для списка из трех и более объектов вы не можете применить данную функцию. Однако, вы можете сравнить попарно все элементы списка с помощью every. Например, для проверки все ли строки в списке эквивалентны с помощью string=.
(every #'string= list (rest list))
Следует отметить, что /= особенна; следующие вызовы не эквивалентны:
* (apply #'/= list)
T
* (every #'/= list (rest list))
T
Почему? Управление ходом исполнения У макроса "do" (do, do*, dolist, dotimes) тело, которое ведет себя как tagbody. Вы можете поместить got tags где угодно в теле и использовать go для перехода в отмеченные места. Это может быть полезно для пропусков, повторов или других изменений выполнения итерации. Например:
(dolist (users (get-user-list)) 
  :retry
  ...
  (when some-condition
    (go :retry)
  ...)

2 комментария:

  1. Предлагаю писать в конце статьи UPDATE ... если она была обновлена (не в данный момент, вообще), было бы полезно - чтобы не упустить, полезную информацию:)

    ОтветитьУдалить
  2. Поправил. Только updated поставил в начале.

    ОтветитьУдалить