упаковка

Команды Scheme работают на низком уровне, а это означает, что даже простые задачи могут потребовать нескольких шагов. Однако такая детализация обеспечивает гибкость: мы можем объединять команды в небольшие многократно используемые функции, которые делают именно то, что нам нужно. Упаковка — это не черно-белая концепция; он может варьироваться от простых псевдонимов для часто используемых команд до более сложных функций, управляющих целыми рабочими процессами. Иногда оболочка — это просто удобная функция для улучшения читаемости, а в других случаях она превращается в полнофункциональную утилиту, инкапсулирующую несколько операций.

Зачем переносить функции?

У функций-оберток есть несколько ключевых преимуществ:

  • Упрощает повторяющиеся задачи. Вместо повторения низкоуровневых команд оберните их во вспомогательную функцию и используйте ее повторно.
  • Улучшает читаемость. Если дать нашим обернутым функциям понятные и описательные имена, наш код будет легче понять с первого взгляда.
  • Инкапсулирует сложность. Вместо того, чтобы иметь дело с длинными загадочными списками команд, глубоко вложенными циклами или сложными операторами сообщений, мы можем разбить их на более мелкие, хорошо структурированные вспомогательные функции.
  • Повышает удобство обслуживания. Если основные функции команды изменяются, нам нужно обновить нашу обернутую функцию только один раз, изолируя наши плагины от деталей этих изменений.
  • Поощряет повторное использование кода. Каждый помощник становится частью вашей библиотеки, что ускоряет написание и отладку будущих сценариев.

По мере роста ваших плагинов оболочки помогают сохранять читабельность основной логики и изолировать повторяющиеся детали.

Еще одним преимуществом функций-оберток является их интеграция в средство подсветки синтаксиса, такое как Visual Studio Code. Это улучшает читаемость и навигацию, делая сценарии более понятными. В плагине, использующем пользовательские функции, любая функция, выделенная зеленым цветом, подтверждает, что на нее правильно ссылаются из нашей библиотеки.

Если вы поддерживаете собственную вспомогательную библиотеку, рассмотрите возможность добавления имен функций вашего проекта в подсветку синтаксиса вашего редактора. Это ускоряет навигацию и рефакторинг.

Примеры:

Случайное начальное число

;; Назначение: Возвращает случайное целое для инициализации фильтра
(define (random-seed)
  (msrg-rand))

Хотя мы могли бы использовать msrg-rand непосредственно в нашем коде, обертывание его внутри функции под названием random-seed улучшает читаемость. Дав функции четкое и описательное имя, становится легче с первого взгляда понять ее назначение.

Кроме того, определение random-seed как отдельной функции позволяет нам использовать ее в любом месте наших плагинов, одновременно централизуя реализацию в одном месте. Если нам когда-нибудь понадобится изменить способ генерации начального числа, нам нужно будет только обновить эту функцию, оставив остальную часть нашего кода нетронутой.

Например, если мы решим вместо этого переключиться на random:

;; Назначение: Возвращает случайное целое для инициализации фильтра
(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 может быть загадочной и подвержена ошибкам сценария. Легко ошибочно применить 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 и подобные функции Scheme.

Обертывание упакованной функции

Обертывание уже обернутой функции может еще больше улучшить читаемость и удобство обслуживания. Например, при работе с парами координат, такими как пиксели-координаты (список 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))

Зачем использовать этот подход?

  • Повышает ясность кода. Вместо использования общих функций доступа к спискам мы явно определяем функции, описывающие их назначение.
  • Улучшает удобство обслуживания. Если наше представление координат изменится (например, используя векторы вместо списков), нам нужно будет обновить только эти небольшие функции.
  • Способствует единообразию. Использование координаты x и y-координаты упрощает чтение и понимание сценария.

Теперь вместо написания 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.

Вы можете просмотреть каждое экспортированное имя, прочитать его документацию и посмотреть, из какой библиотеки оно взято, в Браузер служебных программ (Справка → Программирование → Браузер служебных программ). Это практическая демонстрация масштабируемой упаковки и полезный источник шаблонов, которые можно позаимствовать при создании собственной вспомогательной библиотеки.

Заключение

Обертывание функций — это мощный способ упростить разработку Scheme, делая сценарии более читабельными, удобными в обслуживании и надежными. Инкапсулируя сложность и раскрывая только необходимые детали, мы создаем более структурированный подход к написанию плагинов.

Основные выводы из этого подхода:

  • Упрощает повторяющиеся задачи. Вместо повторения низкоуровневых команд вручную мы создаем функции многократного использования.
  • Улучшает читаемость кода. Правильно названные оболочки облегчают понимание сценариев.
  • Инкапсулирует сложность. Детали низкого уровня обрабатываются внутри оболочки, сохраняя основной скрипт чистым.
  • Повышает удобство обслуживания. Если основные функции изменяются, нам нужно обновить только оболочку, а не каждый скрипт, который на нее опирается.
  • Стимулирует повторное использование и согласованность. Наша личная библиотека функций со временем расширяется, что делает разработку быстрее и эффективнее.

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

Следующие шаги: определите повторяющиеся блоки в ваших скриптах и извлеките небольшие помощники с понятными именами.