упаковка
Команды 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, отвечающую нашим конкретным потребностям.
Следующие шаги: определите повторяющиеся блоки в ваших скриптах и извлеките небольшие помощники с понятными именами.