Процесс работы

С консолью 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)


  1. vadimr
    03.11.2024 16:27

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

    Любая реализация Scheme обязана иметь оптимизацию хвостовой рекурсии. Более того, do - это макрос, реализуемый через рекурсивный вызов letrec, в чём можно убедиться, если есть возможность посмотреть код порождаемой им лямбды.

    В этом отношении язык Scheme близок к лямбда-исчислению, где единственным механизмом повторения является рекурсия (ну или, совсем строго говоря, комбинатор неподвижной точки).


    1. RodionGork
      03.11.2024 16:27

      всё правильно, есть в ней оптимизация хвостовой рекурсии, хотя это не написано большими буквами в доке. уронить её правильно написанной функцией позволяющей TCO не удастся - это легко проверить :) полагаю унаследовано ещё от minischeme.


      1. vadimr
        03.11.2024 16:27

        Это написано большими буквами в стандарте R5RS, на основании которого сделан TinyScheme (Script-Fu). Ну и содержательно это связано с тем, как вообще в Scheme реализуются рекурсивные вызовы (через продолжения, в отличие от классического Лиспа).


        1. RodionGork
          03.11.2024 16:27

          R5RS, на основании которого сделан TinyScheme

          по-моему несколько лет назад когда я в него заглядывал он не 100% покрывал R5RS и с тех пор вряд ли что-то добавилось, поэтому такие заключения стоит делать с осторожностью. но TCO есть.


          1. vadimr
            03.11.2024 16:27

            Конечно, неполное соответствие стандарту формально позволяет не соответствовать и в этой части. Но если из Scheme выкинуть TCO, то из-за этого очень много придётся переписывать руками в части реализации конструкций языка. Например, тот же макрос do, всю механику call/cc. Да собственно все синтаксические диаграммы Scheme строятся относительно tail context. Так что это была бы масса бессмысленной работы ради жалкого результата.


    1. IisNuINu Автор
      03.11.2024 16:27

      в чём то вы конечно правы, должна, но как у нас говорят не обязана. А вот правы вы в том, что (простите за тафтологию) совершенно правы!!! Я никогда не обращал внимание на раскрытие именованного макроса, думал это специальная форма - именованный лет, а он и правда расскрывается до letrec c определением лямбды.

      (get-closure-code fold)
      ;; (lambda (f init lst)
      ;;   (letrec ((gensym-17 (lambda (rez l)
      ;;                         (if (null? l)
      ;;                             (begin rez)
      ;;                             (begin
      ;;                               (gensym-17 (f rez (car l)) (cdr l)))))))
      ;;     (gensym-17 init lst)))

      А я то дурак в исходном коде копался никак не мог разобраться, когда там создаётся новый фрейм.

      И кстати у меня там на gitflic лежит интерпретатор лямбда выражений из лямбда исчисления, как раз для демонстрации работы Y комбинатора писал.


      1. vadimr
        03.11.2024 16:27

        Обязана-обязана :) В этом отличие Scheme от Лиспа.


  1. easimonenko
    03.11.2024 16:27

    Первая мысль: а нет ли режима для работы с GIMP из GNU Emacs? Оказалось, что есть: https://www.emacswiki.org/emacs/GimpMode Не пробовали?


    1. IisNuINu Автор
      03.11.2024 16:27

      пробовал, не понравилось, по разным причинам. Или ГИМП не совместим, или еще какие не нужные артефакты выводятся. не понравилось. не помню, там вроде script-fu надо стартовать в режиме сервера, а он без консоли, ну иногда хочется что то и в консоли ввести. Ну вообщем не понравилось, но Вы совершенно правы, так работать можно. Может быть если бы подольше с этим поковырялся и разобрался бы получше, так и полюбил бы такой режим.