LINUX.ORG.RU

Протащить результат через несколько вызовов функций

 ,


0

3

Приветствую!

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

Пример:

(defun bottom (x)
 (magic-shit (random x))
 x)

(defun middle (arg)
 (let ((x (random arg)))
  (if (= x 42)
      (bottom x)
      x)))

(defun top ()
 (magic-bind (value magic-arg) (middle 999)
  (if magic-arg
      (+ magic-arg value)
      (/ value 42))))

magic-shit - нечто, устанавливающее протаскиваемое значение, в данном случае это (random 42). magic-bind - нечто, позволяющее получить это значение. Форма записи мэджиков непринципиальна, принципиально не изменять middle-функцию.

Сперва была мысль воспользоваться механизмом возвращения нескольких значений, но это не взлетит в силу того, что в общем случае под это придется подгонять middle. Еще в голове вертится слово «замыкание», не знаю, может меня от перенапряжения коротнуло просто. Написать top и множество bottom-функций в одной let-форме и замкнуться на ее переменную не получится, как еще замкнуть я не знаю... Последний вариант - глобальная переменная, да.

Какие есть в лиспе механизмы решения такой задачи?

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

Третьего наверное не дано. Именно глобальные переменные только крайность (иначе + &optional magic-arg в middle).

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

Я просто написал максимально простой пример. В действительности у меня middle - это множество функций, большинство выполненны в функциональщине :) Но императивщина/функциональщина в данном случае дело десятое, хочется хорошего решения. Глобальные переменные тяжело сопровождать.

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

Глобальные переменные тяжело сопровождать.

Можно посмотреть лексические окружения(let block), но они вроде или не работают или просто матерятся при компиляции.

ados ★★★★★
()

Возвращать список?

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

Тяжёлый выбор, у меня он часто решается функциональщиной с multiple-value и &optional.

ados ★★★★★
()

Написать top и множество bottom-функций в одной let-форме и замкнуться на ее переменную не получится, как еще замкнуть я не знаю...

А нельзя что ли только установщик и получатель метки замкнуть?

(let ((mark nil))

  (defun set-mark (x) 
    (setf mark x))

  (defun get-mark ()
    mark))
anonymous
()

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

Мне так чисто тред поддержать, сам ничего не понимаю.

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

Почему бы не выплюнуть исключением?

Потому что одной из bottom-функций вычисление останавливаться не должно.

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

Да, окружения, в смысле, внутри функций, а не так как привёл тут анонимус.

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

Гм. Компиляция внутри анонимного пространства имён?

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

В лиспе исключения не совсем исключения, так что в данном конкретном случае это нормально

vasily_pupkin ★★★★★
()

Какие ограничения на bottom функции? Они свои или из внешних либ? Можно ли менять их сигнатуру?

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

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

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

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

staseg ★★★★★
() автор топика

Мне вот еще какой вариант пришел в голову, практически глобальная переменная, но гибко:


(defvar *bottom-top-transfer*)

(defun top ()
 (let* ((*bottom-top-transfer* nil)
        (value (middle 999)))
  (if *bottom-top-transfer*
      (+ *bottom-top-transfer* value)
      (/ value 42))))
staseg ★★★★★
() автор топика
Ответ на: комментарий от staseg

Слушай, я не понимаю твоего:

Глобальные переменные тяжело сопровождать.

когда есть такая весчь как пакеты.

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

Выглядит это как с глобальной переменной.

Сфига? Реализация скрыта за интерфейсом, состояние можешь контролировать как угодно.

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

Это еще что такое? Несколько меток? Ну стек сделай. Реализацией теперь можешь вертеть, как хочешь.

anonymous
()
Ответ на: комментарий от staseg

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

Ну тогда, лично я, использовал бы замыкания (т.к. не люблю глобальные переменные. Если я правильно понимаю, то этот код

(defvar *bottom-top-transfer*)

(defun top ()
 (let* ((*bottom-top-transfer* nil)
        (value (middle 999)))
  (if *bottom-top-transfer*
      (+ *bottom-top-transfer* value)
      (/ value 42))))

локально переопределяет *bottom-top-transfer*? Если да, то вполне нормально решение, имхо.

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

локально переопределяет *bottom-top-transfer*?

Ну да. По выходу из let-формы *переменная* примет прежнее значение. У этой штуки есть какое-то специальное название, но я забыл.

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

Это еще что такое? Несколько меток? Ну стек сделай. Реализацией теперь можешь вертеть, как хочешь.

top вызвал middle, который потом снова вызвал top. Каждому из top-ов должна прийти своя метка. Случай, когда в рамках одной цепочки вызовов под top разные bottom решат установить свою метку, не рассматриваем, это уже детали реализации, как ты сказал.

Но тогда придется замыкать еще и функции push/pop для управления стеком меток. Вот это и есть типичная возня вокруг глобальных переемнных, что я не люблю.

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

Протащить результат через несколько вызовов функций (комментарий)

_наименьший_! С ним справляется пакетная система, но в остальном от глобальных переменных больше головной боли, чем пользы.

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

Я не сильно понял, что именно означают параметры value и magic-arg для magic-bind, и с какими значениями они должны связываться, поэтому сделал так:

  1. value связывается с результатом вычисления выражения (expr в сигнатуре magic-bind)
  2. magic-arg связывается со значением аргумента вызванного ниже по стеку magic-shit
(define-condition magic-condition (error)
  ((value :initarg :value
          :initform (error "Must specify value")
          :reader magic-value)))

(defmacro magic-bind ((val arg) expr &body body)
  `(let (,arg)
     (handler-bind ((magic-condition
                     (lambda (c)
                       (invoke-restart 'magic
                                       (setf ,arg (magic-value c))))))
       (let ((,val ,expr))
         ,@body))))

(defmacro magic-shit (value)
  `(restart-case (error 'magic-condition :value ,value)
     (magic (val) val)))

(defun bottom (value)
  (let ((arg (random value)))
    (format t "in bottom (must bind to magic-arg): ~a~%" arg)
    (magic-shit arg)))

(defun middle (value)
  (+ value (bottom value)))

(defun top ()
  (magic-bind (value magic-arg) (middle 999)
    (if magic-arg
        (format t "Magic result: ~a~%" (list value magic-arg))
        (format t "No magic; only ~a~%" value))))
theNamelessOne ★★★★★
()
Ответ на: комментарий от theNamelessOne

fastfix: magic-shit делать макросом тут не нужно, работает и в виде функции:

(defun magic-shit (value)
  (restart-case (error 'magic-condition :value value)
    (magic (val) val)))
theNamelessOne ★★★★★
()
Ответ на: комментарий от theNamelessOne

Понял ты правильно. Теперь я буду пробовать понять, как это работает. До сих пор пользовался исключениями без рестартов.

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

top вызвал middle, который потом снова вызвал top. Каждому из top-ов должна прийти своя метка.

Сделай стек цепочек.

Но тогда придется замыкать еще и функции push/pop для управления стеком меток.

Стек-то сам не при делах. Тебе надо замкнуть, только то, что для задачи надо.

(let ((top-chains (make-stack)))
  
  (defun make-chain () 
    (push (make-marks-struct) top-chains))

  (defun get-chain () 
    (pop top-chains))

  (defun release-chain () 
    (pop top-chains))
  

  (defun set-mark (x) 
    (set (get top-chains) ...)))


Топ создает цепочку, боттомы устанавливают метки, топ читает свою цепочку и убирает за собой.

anonymous
()
Ответ на: комментарий от staseg

Понять тут несложно. Если взять за правило, что исключение разматывает стек вызовов до первого подходящего обработчика, то можно считать, что рестарт (рестарты объявляются в форме restart-case) предоставляет точку, в которую поток выполнения может возвратиться в результате работы обработчика исключения, связанного с рестартом с помощью формы handler-bind. Это позволяет логически отделить код, реализующий определенную стратегию восстановления после ошибки (restart-case), от кода, который будет выбирать стратегию восстановления (handler-bind).

Вообще, достаточно почитать соответствующую главу в PCL

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

Собственно, мы этим пользуемся. magic-shit устанавливает рестарт magic и кидает свой аргумент в качестве исключения, которое ловится обработчиком в magic-bind. Обработчик (лябмда в handler-bind) устанавливает значение magic-arg, используя значение, переданное в исключении, после чего возвращается к рестарту magic вниз по стеку, возвращая то же самое значение, которое в результате будет значением функции magic-shit.

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

Спасибо, анон, за старания, но ты меня не переубедил. Делать вручную то, что умеет сам язык не комильфо.

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

слишком наворочено.

и наследовать свой condition от error или serious-condition не стоит, если хочется свой протокол организовать поверх них. И нужно использовать вместо error - signal. Наследовать от error нужно только для своих ошибок.

я бы использовал динамическую переменную для проброса контекста между вызовами. имхо общепринятый метод в CL.

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

Спасибо за разъяснения. Рестарт нравится больше динамической переменной, на этом пока и остановлюсь.

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

Спасибо, анон, за старания, но ты меня не переубедил. Делать вручную то, что умеет сам язык не комильфо.

Пфф, там строчек десять будет. Как хочешь. Наиграешься с рестратами, читай: http://letoverlambda.com/index.cl/guest/chap2.html

anonymous
()
Ответ на: комментарий от gensym

и наследовать свой condition от error или serious-condition не стоит, если хочется свой протокол организовать поверх них. И нужно использовать вместо error - signal.

точно, спасибо

я бы использовал динамическую переменную для проброса контекста между вызовами. имхо общепринятый метод в CL.

Как вариант. Я просто сразу про рестарты подумал, когда увидел задачу

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

Рестарт нравится больше динамической переменной, на этом пока и остановлюсь.

Забудешь поймать magic-condition, наследованный от error, приземлишься в дебаггер.

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

Как вариант. Я просто сразу про рестарты подумал, когда увидел задачу

overkill же

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

Топ создает цепочку, боттомы устанавливают метки, топ читает свою цепочку и убирает за собой.

И, кстати, это можно и на других языках сделать. Только на Лиспе, вместо дополнительного бойлерплейта, у тебя получится красивенький мини eDSL:

(defun top ()
  (with-marks ()
    ...
    (get-marks)
    ...))

(defun bottom (x)
  ...
  (set-mark ...)
  ...)


В этом и есть сила Лиспа, а не в каких-то там фишках, которые можно за уши притянуть к своей конкретной задаче.

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

Анон, разукрашивать код макросами я умею и LoL тоже прочитал, спасибо j-a-t-a / Кому книжки?

Любой из трех вариантов одиноково легко ложится на макры типа with-marks. Просто я хочу избежать потенциальных траходромов вокруг ручного управления глобальной переменной, может у меня фобия такая с С++ осталась, не знаю. В любом случае не вижу смысла писать дополнительный код, если рантайм это сам хорошо умеет, преимуществ с него ведь ноль, верно?

ЗЫ. Рестарты покрутил, оценил и остановился на динамической переменной, как наиболее органичном на мой взгляд решении проблемы.

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

может у меня фобия такая с С++ осталась

забудь об этом кошамрном сне. Связывание динамическиx переменных в CL в нормальных реализацииях хорошо оптимизировано.

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

В данном случае это костыль, чтобы компилятор не матерился.

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

остановился на динамической переменной, как наиболее органичном на мой взгляд решении проблемы.

Ну со стеком возиться не надо, а в остальном, та же глобальная переменная. Кто угодно и как угодно бесконтрольно может ее использовать и в хвост и в гриву.

anonymous
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.