Обгортання

Команди Scheme працюють на низькому рівні — навіть прості задачі можуть потребувати кількох кроків. Ця деталізація дає гнучкість: команди можна об’єднувати в невеликі багаторазові функції, які роблять саме те, що потрібно. Обгортання — не жорстке правило; це може бути простий псевдонім для частої команди або складна функція для цілого робочого процесу. Іноді обгортка лише покращує читабельність, іноді — повноцінна утиліта з кількома операціями.

Навіщо обгортати функції?

Ключові переваги:

  • Спрощення повторюваних задач — замість повторення низькорівневих команд обгорніть їх у помічника і використовуйте знову.
  • Краща читабельність — зрозумілі назви роблять код зрозумілим з першого погляду.
  • Інкапсуляція складності — довгі ланцюжки команд, вкладені цикли або складні повідомлення розбиваються на менші функції.
  • Зручніша підтримка — якщо базова команда зміниться, оновлюєте обгортку один раз; плагіни ізольовані від деталей.
  • Повторне використання коду — кожен помічник стає частиною бібліотеки; наступні скрипти пишуться швидше.

У міру зростання плагінів обгортки зберігають читабельність основної логіки та ізолюють повторювані деталі.

Обгортки також інтегруються в підсвічування синтаксису, наприклад у Visual Studio Code — це полегшує навігацію. У плагіні з власними функціями зелене підсвічування підтверджує, що функція коректно посилається на бібліотеку.

Якщо підтримуєте власну допоміжну бібліотеку, додайте імена функцій проєкту до підсвічування редактора — навігація та рефакторинг стануть швидшими.

Приклади:

Випадковий seed

;; Призначення: повертає випадкове ціле для seed фільтра
(define (random-seed)
  (msrg-rand))

msrg-rand можна викликати напряму, але обгортка random-seed покращує читабельність: назва одразу пояснює призначення.

Окреме визначення random-seed дозволяє використовувати його в будь-якому плагіні, централізуючи реалізацію. Якщо спосіб генерації seed зміниться — оновлюєте одну функцію.

Наприклад, перехід на random:

;; Призначення: повертає випадкове ціле для seed фільтра
(define (random-seed)
  (random 1000))

Назва функції не змінюється — скрипти працюють без правок. Код залишається гнучким, зручним у підтримці та читабельним.

Експорт JPEG

Функція експорту JPEG у Scheme має багато параметрів для точного контролю збереження. Зазвичай важливі лише ім’я файлу та якість — тому функцію можна обгорнути.

;; Призначення: зберегти зображення як JPEG із заданою якістю
(define (file-jpg-save image file quality)
  (let ((export-file (if (has-substring? file ".jpg")
                         file
                         (string-append file ".jpg")))) ;; Уникнути jpg.jpg
    (debug-message "Exporting: " export-file)
    (file-jpeg-export #:run-mode RUN-NONINTERACTIVE
                      #:image image
                      #:file export-file
                      #:options -1
                      #:quality (* 0.01 quality)
                      #:smoothing 0.0
                      #:optimize 1
                      #:progressive 1
                      #:cmyk 0
                      #:sub-sampling "sub-sampling-1x1"
                      #:baseline 1
                      #:restart 0
                      #:dct "integer")))

У цій обгортці більшість параметрів експорту зафіксовані; залишаються лише ім’я файлу та якість. Читабельність вища, збереження — простіше.

Якщо експортер Lumi зміниться, оновлюєте одну функцію, а не кожен скрипт, що експортує JPEG.

Використання обгортки

Щоб експортувати JPEG у плагіні, підключіть бібліотеку та викличте власну функцію:

(file-jpg-save image "/home/mark/pictures/my-picture" 85)

Код залишається чистим і адаптованим; JPEG експортується з мінімальними зусиллями.

Заміна car

Функція car може бути неочевидною і схильною до помилок — легко застосувати її до вектора або не-списку. Обгортка робить код надійнішим:

;; Призначення: повертає перший елемент списку або вектора.
;;              Попереджає, якщо вхід недійсний або порожній.
(define (first-item collection)
  (cond
    ;; Непорожній список
    ((and (list? collection) (not (null? collection)))
     (list-ref collection 0))
    ;; Непорожній вектор
    ((and (vector? collection) (> (vector-length collection) 0))
     (vector-ref collection 0))
    ;; Недійсний або порожній вхід
    (else
     (begin
       (warning-message "first-item: Expected a non-empty list or vector, but received: " collection)
       #f))))

first-item безпечно отримує перший елемент і попереджає про некоректний вхід. Замість car зменшується ризик помилок і зростає ясність.

Навіщо ця обгортка?

  • Запобігає збоям — уникає помилок від car на не-списках.
  • Підтримує списки і вектори — ширше застосування.
  • Змістовні попередження — допомагає знайти проблеми введення.
  • Читабельність — назва передає призначення.

Інкапсуляція в first-item робить плагіни надійнішими. Звісно, це питання смаку — можна комфортно використовувати car, caar, cadr напряму.

Обгортка вже обгорнутої функції

Обгортка поверх обгортки покращує читабельність. Для пари координат pixel-coords (list 100 200) можна написати:

(first-item pixel-coords)

щоб отримати x, але це не дуже виразно. Краще обгорнути first-item:

;; Призначення: повернути x-координату — для читабельності
(define (x-coord pixel-coords)
  (first-item pixel-coords))

;; Призначення: повернути y-координату — для читабельності
(define (y-coord pixel-coords)
  (second-item pixel-coords))

Навіщо такий підхід?

  • Ясність коду — функції описують призначення замість загальних car/cdr.
  • Підтримка — якщо координати стануть векторами, оновлюєте лише ці дві функції.
  • Узгодженістьx-coord і y-coord роблять скрипт зрозумілим з першого погляду.

Замість загального Scheme:

(car pixel-coords)  ;; x-координата
(cadr pixel-coords) ;; y-координата

Пишемо «наш» Scheme:

(x-coord pixel-coords)
(y-coord pixel-coords)

Низькорівневі функції з осмисленими іменами зменшують плутанину та помилки.

Готові обгортки: stdlib утиліт

Lumi постачає набір готових обгорток, які завантажуються автоматично під час запуску — вони доступні в будь-якому плагіні або в консолі Scheme без (load ...). Бібліотеки (common.scm, files.scm, gegl.scm, images.scm, layers.scm, parasites.scm, paths.scm) побудовані за тим самим принципом: чіткі назви, прихований шаблонний код, єдине місце оновлення при зміні базової команди.

Наприклад, images.scm дає image-get-open-list як читабельну обгортку навколо сирого виклику PDB, а files.scm — помічники побудови шляхів замість ланцюжків string-append.

Кожне експортоване ім’я, docstring і бібліотеку-джерело можна переглянути в Оглядачі утиліт (Довідка → Програмування → Оглядач утиліт). Це практична демонстрація обгортання в масштабі та джерело шаблонів для власної бібліотеки.

Висновок

Обгортання функцій спрощує розробку на Scheme: скрипти читабельніші, зручніші в підтримці та надійніші. Інкапсулюючи складність і показуючи лише потрібне, ми структуруємо написання плагінів.

Основні висновки:

  • Менше повторів — багаторазові функції замість ручного копіювання команд.
  • Читабельність — добре названі обгортки пояснюють код.
  • Інкапсуляція — деталі низького рівня всередині обгортки; основний скрипт чистий.
  • Підтримка — зміна базової функції = оновлення обгортки, а не кожного скрипта.
  • Повторне використання — особиста бібліотека зростає; розробка прискорюється.

Послідовне обгортання змінює підхід до плагінів Scheme — модульніше та виразніше середовище скриптів. З цими принципами можна вдосконалювати власний «діалект» Scheme під конкретні потреби.

Наступний крок: знайдіть повторювані блоки у скриптах і винесіть невеликих помічників із зрозумілими іменами.