У Common Lisp є поняття Принтера, яке охоплює всі функції для запису текстових представлень даних Lisp. Одна з них - функція format, яка дає програмісту змогу форматувати дані на власний розсуд.
Можливості format і Принтера Lisp загалом - це велика тема; тут ми розглянемо лише основи використання format.
Функція format приймає щонайменше два аргументи: потік, у який треба друкувати, і керуючий рядок тексту (англ. string), що визначає, що і як друкувати.
Потоки - це окреме поняття, про яке йтиметься в іншому місці. Але поки що достатньо знати, що друк відбувається в потік, і що для цього аргументу є два особливі значення: t і nil. t означає друкувати в стандартний вивід (насправді в потік, з яким звʼязана змінна *standard-output*). а nil означає створити рядок тексту і повернути його.
Зауважмо, що format завжди обчислюється в nil, крім того останнього випадку, коли як потік указано nil. Це поширений спосіб просто виконати інтерполяцію або побудову рядка тексту.
(format nil "hello world") ; => "hello world"
(format t "hello world") ; => NIL (but "hello world" is printed to standard output)
Керуючий рядок тексту може містити «директиви формату», які вказують format, що друкувати і як інтерпретувати будь-які інші параметри format. Певною мірою керуючий рядок тексту - це програма, яку format інтерпретує й виконує. Усі директиви починаються з ~, за яким іде символ (наприклад, ~A, ~D, ~&). Регістр символу не має значення.
Більшість директив формату просто вставляють аргумент format у вихідні дані. Кажуть, що директива «споживає» аргумент. Деякі директиви не споживають аргументів або споживають більше ніж один. Одна директива навіть дозволяє перестрибувати по списку аргументів або пропускати аргументи.
Дуже універсальна директива формату - ~A. Вона виводить версію даних, «зрозумілу людині».
(format nil "Value = ~a" 10) ; => "10"
(format nil "hello ~a" "world") ; => "hello world"
(format nil "The list is: ~A" (list 1 2 3)) ; => "The list is: (1 2 3)"
Ще одна корисна директива формату - ~S. Вона виводить версію даних, «зрозумілу машині». Це може бути дуже корисним, якщо ми хочемо мати змогу інтерпретувати створені вихідні дані як дані Lisp. Для багатьох речей вона матиме такий самий вигляд, як ~A, але зауважмо, що, наприклад, для рядків тексту вихідні дані міститимуть лапки (екрановані, коли вони входять до іншого рядка тексту), щоб значення можна було прочитати назад як параметр-рядок.
(format nil "~s" 10) ; => "10"
(format nil "~s" "hello world") ; => "\"hello world\""
(format nil "~s" (list 1 2 3)) ; => "(1 2 3)"
Ще дві дуже корисні директиви - ~% і ~&. Перша виводить символ нового рядка, а друга - «свіжий рядок». Різниця в тому, що ~% завжди виводить символ нового рядка, а ~& виведе його, якщо ми зараз не на початку рядка. Жодна з них не споживає аргументів.
(format nil "~%new~%lines") ; => "
new
lines"
(format nil "~&new~&lines" ; => "new
lines"
І нарешті, ~~ - проста директива, яка виводить літеральний символ ~.
Як і майже всім, хто працює, Layla the Lisp Alien іноді потрібно робити звіти, щоб ділитися інформацією з іншими. Вона просить нас допомогти їй відформатувати дані, які вона має для цих звітів.
Перше, що потрібно Layla, це функція format-quarter-value, яка приймає два параметри: календарний квартал і значення, та форматує їх для звіту. Вона повинна обчислюватися в рядок тексту (англ. string).
(format-quarter-value "last" 3.14) ; => "The value last quarter: 3.14"
(format-quarter-value "this" 0) ; => "The value this quarter: 0"
Layla дякує нам за функцію format-quarter-value і каже, що вона буде дуже корисною в наступній функції, яка знадобиться.
У наступному звіті потрібно показати квартал і значення за два квартали, виведені на двох рядках.
Також вихідні дані потрібно спрямувати в потік, наданий як перший аргумент.
Наприклад, (format-two-quarters t "last" 3.14 "this" 0) виведе на стандартний вивід таке:
The value last quarter: 3.14
The value this quarter: 0
Звернімо увагу на порожні рядки навколо.
Layla забула розповісти нам, що Lisp Aliens люблять бачити значення у вигляді, який можна прочитати як дані Lisp. Тож їм потрібна нова функція, результат якої можна прочитати як дані Lisp (знову ж таки у вказаний потік):
(format-two-quarters-for-reading nil "last" 3.14 "this" 0) ; =>
"(\"The value last quarter: 3.14\" \"The value this quarter: 0\")"
Зареєструйтеся на Exercism, щоб вивчати й опановувати Common Lisp, а також 27 концепцій92 вправи та справжнє наставництво від людей, і все це безкоштовно.