LINUX.ORG.RU

Common lisp, GTK и лапшелогика

 , ,


1

3

Вот есть код:

(let ((some (trick 'construct)))
  (if (very-bad? some)
      (trick 'good-bye)
      (progn (when (bad? some)
               (setf some
                     (trick 'fix some)))
             (trick 'show some))))

В данный момент код в теле функций и форма с trick - вызов функции с созданием gtk окна, работой в этом окне, закрытием и, уже после, trick возвращает значение.

Сейчас вот думаю чтобы проделать всё в рамках одного создания-закрытия окна. Как всем известно, что таким образом с gtk в теле функции может быть что-то типа такого:

(low-level-init-gtk)
(let ((window (build-...)))
  ;; Здесь создаём объекты и рассовываем лямбды для отрисовки
  ;; и обработки событий
  ...
  
  ;; ииии
  (gtk-main-loop))

Ещё хочу это обернуть в unwind-protect чтобы высвобождать систему звука, например. Тут уже мало того формы с trick перестают быть просто вызовами функций, так вообще всю логику из первого куска кода нужно раздробить и размазать по обработчикам. Без этого только продолжения использовать? Или есть другие практики описания такой логики?

★★★★★

Или есть другие практики описания такой логики?

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

(defun trick (action after) ...)

(trick 'construct
  (lambda (some)
    (if (very-bad? some)
      (trick 'good-bye)
      (if (bad? some)
          (trick 'fix some (lambda (res) (trick 'show some)))
          (trick 'show some)))))
monk ★★★★★ ()
Ответ на: комментарий от monk

Пока что trick работает в виде функции - открывается окно, пользователь делает работу, закрывает окно и функция возвращает значение. Сейчас думаю сделать то же самое, но в рамках одного окна. Т.е. как не крути а возврат из «функции» trick после события в рамках gtk-main-loop. Мне туда семафоры из многопоточности ставить, что-ли?

ados ★★★★★ ()
Ответ на: комментарий от monk

Ага, я понял. Тут на один вызов trick одна лямбда. В таком случае приходится отказываться от гибкости в виде loop в коде и, если можно, обходить это всякими рекурсиями.

ados ★★★★★ ()
Последнее исправление: ados (всего исправлений: 1)
Ответ на: комментарий от ados

Альтернатива: весь код с trick вынеси во второй поток.

Тогда в потоке GUI у тебя ввод данных с пробуждением второго потока после ввода, а в потоке с trick вся обработка и перенаправление потоку GUI данных для отображения.

monk ★★★★★ ()
Ответ на: комментарий от monk

А в мейнстриме вот такое с GUI это нормальная практика?

Но что-то мне подсказывает, что это есть самая верная альтернатива. Ведь этот поток управления с trick - это тот поток на который можно спихнуть сложные и продолжительные вычисления и операции, это если мы не говорим о простых программах без такого. Команда из гуя пошла, вычисления или загрузки пошли, а гуй при этом остаётся отзывчивым и может тем временем инфу о прогрессе показывать.

ados ★★★★★ ()
Последнее исправление: ados (всего исправлений: 1)
Ответ на: комментарий от EugeneBas

ИМХО отступы и точки с запятой не так мешают восприятию как частокол из скобок

Мне, наоборот, кажется удобным, что формы ограничены скобками и можно четко видеть их границы, не гадая, где оно началось и где закончится. В редакторе с подсветкой парных скобок, конечно.

И особенно нравится, что можно наконец-то забыть про заучивание приоритетов операторов.

Nervous ★★★★★ ()
Ответ на: комментарий от ados

Это ещё довольно простой случай когда можно спихивать такие задачи. В общем случае получается что-то вроде клиент-серверной модели, когда между потоком с GUI циклом и потоками с продолжительными операциями связывается ещё один поток или основной - он то и программируется вот такими trick инструкциями. В этом случае к программе удобно подключать потоки с альтернативными интерфейсами: веб, консоль.

ados ★★★★★ ()
Ответ на: комментарий от den73

Вряд ли. cl-async даёт цикл событий в основном потоке. Там где он мог бы помочь, там можно было бы и glib-овский таймер использовать. Да и два цикла событий в одном потоке вряд ли уживутся.

monk ★★★★★ ()
Ответ на: комментарий от EugeneBas

ИМХО отступы и точки с запятой не так мешают восприятию как частокол из скобок

(trick 'construct
  (lambda (some)
    (if (very-bad? some)
      (trick 'good-bye)
      (if (bad? some)
          (trick 'fix (lambda (res) (trick 'show res)) some)
          (trick 'show null some)))))

читается примерно одинаково с

trick(construct,
   [](data some) {
     if(very_bad(some))
       trick(good_bye);
     else if(bad(some))
       trick(fix, [](data res) { trick(show, res) }, some);
     else
       trick(show, nullptr, some);
   });

Причём лисп версия содержит 24 скобки, а си++ – 28.

monk ★★★★★ ()
Ответ на: комментарий от monk

И

(defvar triples
  (with-yield
    (loop for z from 1 do
      (loop for x from 1 to z do
        (loop for y from x to z
          when (= (+ (* x x) (* y y))
                  (* z z))
          do (yield (list x y z)))))))

явно читабельнее, чем

auto triples =
    for_each(iota(1), [](int z) {
        return for_each(iota(1, z+1), [=](int x) {
            return for_each(iota(x, z+1), [=](int y) {
                return yield_if(x*x + y*y == z*z,
                    make_tuple(x, y, z));
                });
            });
        });
monk ★★★★★ ()
Последнее исправление: monk (всего исправлений: 1)
Ответ на: комментарий от no-such-file

Конечный автомат?

Это похоже на другой языка. Тогда trick переименовывается в switch-state и получается что-то вроде:

(build-finite-state-machine
 :init-state construct
 :states
 ((construct (some)
    (if (very-bad? some)
        (switch-state 'good-bye)
        (switch-state (if (bad? some)
                          'fix
                          'show)
                      some)))
  (good-bye ())
  (fix (some)
    (switch-state 'show some))
  (show (some))))

В результате получается объект, из которого событие из цикла gtk берёт соответствующую лямбду. Дополнительные потоки исполнения не требуются.

ados ★★★★★ ()
Последнее исправление: ados (всего исправлений: 3)
Ответ на: комментарий от ados

Дополнительные потоки исполнения не требуются.

Только в том случае, если very-bad?, switch-state и bad? выполняются достаточно быстро (меньше 0,1 секунды). Иначе всё равно интерфейс будет подвисать. Хотя иногда именно это и требуется (например, если предполагается, что пользователь не должен в это время ничего нажимать).

monk ★★★★★ ()
Ответ на: комментарий от monk

ну это если писать на плюсах в таком стиле, то да, разница не велика, но я бы сделал вот так:

auto fixCallback = [show](data res) {
    trick(show, res);
};
auto constructCallback = [good_bye, fix, fixCallback, show](data some) {
    if(very_bad(some)) trick(good_bye);
    else if(bad(some)) trick(fix, fixCallback, some);
    else trick(show, nullptr, some);
};
trick(construct, constructCallback);

ИМХО так читабельнее

EugeneBas ★★ ()
Ответ на: комментарий от EugeneBas

В этом стиле:

(labels ((fix-callback (res) 
         (trick show res))
        ((construct-callback (some)
         (cond
           ((very-bad? some) (trick 'good-bye))
           ((bad? some) (trick 'fix #'fix-callback some))
           (t (trick 'fix null some))))
  (trick 'construct #'construct-callback))

В си++ при чтении не видно, что fix, construct, good-bye являются простыми знечениями (разве что их в стиле enum в си сделать FIX, CONSTRUCT, GOOF-BYE). И не видно, что обратные вызовы не используются после вызова trick(construct, ). Зато необходимо вручную перечислять захватываемые переменные.

monk ★★★★★ ()
Ответ на: комментарий от monk

ну есть некоторая неоднозначность, да, но ни разу батхёрта на этой почве не испытывал

а по поводу захвата, не сказал бы, что перечисление пременных, захватываемых лямбдой - это проблема, это явность, а явность всегда лучше, чем неявность действий

EugeneBas ★★ ()
Ответ на: комментарий от EugeneBas

а по поводу захвата, не сказал бы, что перечисление пременных, захватываемых лямбдой - это проблема, это явность, а явность всегда лучше, чем неявность действий

Поэтому вместо

auto constructCallback(data some) 

теперь пишут

auto constructCallback = [good_bye, fix, fixCallback, show](data some)

?

Ведь именованная лямбда по сути обычная функция. Тогда для единообразия надо и trick объявить также и тоже внести в список :-)

ну есть некоторая неоднозначность, да, но ни разу батхёрта на этой почве не испытывал

Ну вот. Кто к чему привык. В Scheme скобки можно делать разные и писать почти как в Си:

(let* ([fix-callback {lambda (res) 
         (trick show res)}]
       [construct-callback {lambda (some)
         (cond
           [(very-bad? some) (trick 'good-bye)]
           [(bad? some) (trick 'fix fix-callback some)]
           [t (trick 'fix null some)])}])
  (trick 'construct construct-callback))
monk ★★★★★ ()