Процесс работы
С консолью script-fu я работаю опосредованно, т.е весь код я пишу в текстовом редакторе, в моём случае это Emacs, а далее получившийся код копирую через буфер обмена в консоль, а результаты выведенные в консоль, если они чем то примечательны копирую через буфер обмена обратно в Emacs.
Обычно если я завершаю какую либо функционально обособленную группу функций я помещаю её в отдельный файл, библиотеку. А вот уже для загрузки библиотек я использую комманды загрузки:
;;задаём путь для загрузки библиотек
;;(define path-home "D:")
(define path-home (getenv "HOME"))
(define path-lib (string-append path-home "/work/gimp/lib/"))
(define path-work (string-append path-home "/work/gimp/"))
;;загружаем библиотеки
(load (string-append path-lib "util.scm"))
(load (string-append path-lib "defun.scm"))
(load (string-append path-lib "struct2.scm"))
Вывод данных
Как только начинаешь работать, т.е писать более менее сложные функции, в интерпретаторе script-fu осознаешь необходимость иметь простое средство для вывода различных данных, без него просто не возможно что-то сделать, т.к. основным методом отладки здесь являтся вывод промежуточных результатов.
В script-fu есть несколько функций для перевода данных в текстовое представление и вывода его на консоль. Обычно пишут так:
(print 23)
23
#t
> (prin1 23)
23#t
(prin1 (string-append "number is " (number->string 23)))
"number is 23"#t
(print (string-append "list: " (apply string-append (map atom->string '(1 2 3)))))
"list: 123"
но вывод смешанных данных превращается в сущий кошмар из цепочек вызова функций преобразования данных в строку, объединения строк и печати, ну а корректный вывод списка и вовсе невыполнимая задача.
Нам нужна функция которая инкапсулирует весь это процесс преобразований, за простым и понятным интерфейсом, хотелось бы иметь функцию на подобии printf или format, которая сразу может распечатать всё что угодно в одном вызове. Здесь нам не понадобятся макросы, а лишь пара вспомогательных функций которые переводят любой(ну практически любой) набор данных тинисхемы в строку:
(define (to-str elem)
(cond
((string? elem) elem)
((and (atom? elem)
(not (vector? elem))) (atom->string elem))
((list? elem)
(string-append "("
(apply string-append
(insert-between-elements
(map to-str elem) " "))
")"))
((vector? elem)
(string-append "#("
(apply string-append
(insert-between-elements
(map to-str
(vector->list elem)) " "))
")"))
))
;;полезная функция обработки списков, применяет функцию двух аргументов,
;;к списку, для первого применения используется начальный элемент инит
(define (fold f init lst)
(do ((rez init (f rez (car l)))
(l lst (cdr l)))
((null? l) rez)))
(define (insert-between-elements lst new-elem)
(reverse
(fold (lambda (prev elem)
(if (not (null? prev))
(cons elem (cons new-elem prev))
(cons elem prev)))
'()
lst))
)
(define (prn . args)
(display
(apply string-append
(map to-str
args))))
(to-str `((1 2 3 "hello" 'world 23 (q 3 e ,#(1 2 3) 4))))
;;"((1 2 3 hello (quote world) 23 (q 3 e #(1 2 3) 4)))"
(prn "Hello" " " "World!" "\n")
;;Hello World!
;;#t
(prn "x: " '(12 3 12 (23 q a b d) "next" "prev" q123) ", всё!" "\n")
;;x: (12 3 12 (23 q a b d) next prev q123), всё!
Это немного упрощённый вариант функции которой я пользуюсь, позволяющей печатать практически любые данные script-fu.
Не поймите меня не правильно, что тинисхема совсем "голая", нет! Например в файле инициализации определена функция foldr, аналог приведённой мной функции fold. Но я предпочитаю использовать итерационное определение обработки списка, а foldr имеет рекурсивное определение. Обратите внимание на цикл do в функции fold, у него отсутствует "тело" цикла. А что такое тело цикла? Это либо операции с "побочным эффектом", либо различные присваивания. Построение цикла в подобном стиле считается истинно функциональным. И с учётом того, что тинисхема нигде не обещает, что она имеет оптимизацию хвостовой рекурсии, постоянно использовать рекурсивные решения, это опрометчивый шаг.
Я поместил эти функции в файл util.scm. Кстати библиотеку функций я выложил на gitflic.ru
Отладка#
Отладка в Script-fu сводиться к распечатке промежуточных сообщений, позволяющих локализовать неисправность. А благодаря написанной выше функции, этот процесс значительно облегчается.
(define-m (reverse-str str)
(prn "run reverse-str with: " str "\n")
(list->string (reverse (string->list str))))
(reverse-str "смешарики")
;;run reverse-str with: смешарики
;;"икирашемс"#
Таким образом сегодня мы рассмотрели небольшую но очень важную функцию универсальной печати, которая значительно облегчит нам дальнейшую работу со script-fu.
Комментарии (9)
easimonenko
03.11.2024 16:27Первая мысль: а нет ли режима для работы с GIMP из GNU Emacs? Оказалось, что есть: https://www.emacswiki.org/emacs/GimpMode Не пробовали?
IisNuINu Автор
03.11.2024 16:27пробовал, не понравилось, по разным причинам. Или ГИМП не совместим, или еще какие не нужные артефакты выводятся. не понравилось. не помню, там вроде script-fu надо стартовать в режиме сервера, а он без консоли, ну иногда хочется что то и в консоли ввести. Ну вообщем не понравилось, но Вы совершенно правы, так работать можно. Может быть если бы подольше с этим поковырялся и разобрался бы получше, так и полюбил бы такой режим.
vadimr
Любая реализация Scheme обязана иметь оптимизацию хвостовой рекурсии. Более того, do - это макрос, реализуемый через рекурсивный вызов letrec, в чём можно убедиться, если есть возможность посмотреть код порождаемой им лямбды.
В этом отношении язык Scheme близок к лямбда-исчислению, где единственным механизмом повторения является рекурсия (ну или, совсем строго говоря, комбинатор неподвижной точки).
RodionGork
всё правильно, есть в ней оптимизация хвостовой рекурсии, хотя это не написано большими буквами в доке. уронить её правильно написанной функцией позволяющей TCO не удастся - это легко проверить :) полагаю унаследовано ещё от minischeme.
vadimr
Это написано большими буквами в стандарте R5RS, на основании которого сделан TinyScheme (Script-Fu). Ну и содержательно это связано с тем, как вообще в Scheme реализуются рекурсивные вызовы (через продолжения, в отличие от классического Лиспа).
RodionGork
по-моему несколько лет назад когда я в него заглядывал он не 100% покрывал R5RS и с тех пор вряд ли что-то добавилось, поэтому такие заключения стоит делать с осторожностью. но TCO есть.
vadimr
Конечно, неполное соответствие стандарту формально позволяет не соответствовать и в этой части. Но если из Scheme выкинуть TCO, то из-за этого очень много придётся переписывать руками в части реализации конструкций языка. Например, тот же макрос do, всю механику call/cc. Да собственно все синтаксические диаграммы Scheme строятся относительно tail context. Так что это была бы масса бессмысленной работы ради жалкого результата.
IisNuINu Автор
в чём то вы конечно правы, должна, но как у нас говорят не обязана. А вот правы вы в том, что (простите за тафтологию) совершенно правы!!! Я никогда не обращал внимание на раскрытие именованного макроса, думал это специальная форма - именованный лет, а он и правда расскрывается до letrec c определением лямбды.
А я то дурак в исходном коде копался никак не мог разобраться, когда там создаётся новый фрейм.
И кстати у меня там на gitflic лежит интерпретатор лямбда выражений из лямбда исчисления, как раз для демонстрации работы Y комбинатора писал.
vadimr
Обязана-обязана :) В этом отличие Scheme от Лиспа.