LINUX.ORG.RU

Про рестарты.

 


1

4

Вот я бесконечно далек от концепции рестартов (потому что не кодер). Но всё же я попытался впилить их в свой прожект (сначала это был декодер музыки в flac, а теперь оно ещё и немного понимает wav, а потом, может, будет понимать wavpack и ape).

Итак, ситуация. Вначале flac файла есть метаданные: обязательный блок streaminfo и прочие. Если какие-то блоки моя библиотека не понимает или они битые, то считыватель блока кидает condition 'flac-bad-metadata, а функция выше по стеку предоставляет рестарт: не консить такой блок к возвращаемому списку блоков и поправить позицию потока, из которого идет чтение. Вот код:

(defun open-flac (stream)
  ;; Checking if stream is flac stream
  (let ((bitreader (make-reader :stream stream)))
    (if (/= +flac-id+ #+x86_64 (read-bits 32 bitreader)
                      #-x86_64 (bitreader.be-bignum:read-bits 32 bitreader))
        (error 'flac-error :message "Stream is not flac stream"))

    (let (metadata-list)
     (do (last-block)
         (last-block)
       
       (setq last-block
             (restart-case
              (let ((metadata (metadata-reader bitreader)))
                (push metadata metadata-list)
                (metadata-last-block-p metadata))
              (skip-malformed-metadata (c)
                                       (let ((metadata (flac-metadata c)))
                                         (fix-stream-position bitreader metadata)
                                         (metadata-last-block-p metadata))))))
     (values
      (reverse metadata-list)
      bitreader))))

Так вот вопрос: нормальная ли практика предоставить конечному пользователю функцию open-flac, рестарт (или функцию-обертку) skip-malformed-metadata и condition flac-bad-metadata, чтобы он сам связал этот рестарт с этим условием (т.е. сделал handler-bind) и мог бы точно знать, есть ли у него битые/неизвестные блоки? Или лучше сделать функцию open-flac такой:

(defun open-flac (stream &key skip-malformed) «Если ошибка, то молча пропускаем» ...)? Или есть третий вариант?

Декодер работает и есть на жидхабе, если чё. Ещё он умеет дергать хидер из wav и расжимать g.711.


Нормальная. Намного лучше, чем делать &key skip-malformed. Потому как пользователь функции может желать вести лог битых блоков. Или после n битых блоков прекратить издеваться над потоком. Рестарт всё это позволит.

Я бы добавил рестарт retry, который позволил бы перечитать блок с другим metadata-reader.

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

блок с другим metadata-reader

Может быть. Я ещё запилил вариант считывания целого блока просто в «сырой» массив, не заполняя слотов специально для этого типа. Хотя это скорее для отладки, нежели чем для конечного пользователя.

Спасибо

onanij
() автор топика

Расскажи, пожалуйста, как ты устанавливаешь флаг last-block? Ведь metadata-last-block-p его видеть не должна... или я что-то не понимаю?

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

Считываю из файла. Вот так:

stream - это обертка над обычным потоком, если чё


(defun metadata-header-reader (stream)
  "Returns (values START-POSITION LAST-BLOCK-P TYPE LENGTH)"
  (values (reader-position stream)
          (if (= 0 (read-bit stream)) nil t)
          (read-bits 7 stream)
          (read-bits 24 stream)))



(defun metadata-reader (stream)
  (multiple-value-bind (start-position last-block-p type length)
      (metadata-header-reader stream)
    
    (let* ((mtype (get-metadata-type type))
           (data (make-instance mtype
                                :last-block-p last-block-p
                                :length length
                                :start-position start-position)))
      (metadata-body-reader stream data)
      data)))
onanij
() автор топика
Ответ на: комментарий от anonymous

Такие сложности, что весь декодер занял процентов 40 от объёма C кода

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

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

В C++ есть, например, try-catch. В блоке try может быть выброшено исключение, а catch - это обработчик. В CL же в любом месте по стеку вызова может быть обработчик и в любом может быть код, принимающий решение, какой обработчик будет вызван.

А ещё вместо исключений там условия (в оригинале conditions) - по сути обычные классы со своими слотами в которых можно хранить любую инфу, нужную для обработки ошибки.

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

Классический try/catch в CL тоже есть в виде макроса handler-case, если что.

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

Рестарты есть хоть в одном языке, кроме CL? Кстати, в языке со статической типизацией они будут выглядеть весьма убого, если вообще их там можно сделать.

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

Рестарты есть хоть в одном языке, кроме CL?

Похожая система была в PL/I.

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

Можно: https://mail.mozilla.org/pipermail/rust-dev/2012-October/002513.html

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

Ну это все-таки не то... Жалкая пародия. По сути, в языке со статической типизацией возможные рестарты(вместе с их параметрами) придется делать частью типа функции... Иначе не понятно, как использовать.

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

Ну это все-таки не то...

Это не то, но довольно близко. Причем сделано фактически библиотечными средствами, без изменения компилятора и рантайма.

Жалкая пародия.

Ы? Вызов функции выше по стеку, в зависимости от результата - продолжение или останов. Что не так?

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

Раскрой тему.

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

Раскрой тему

Видимо, он хочет проверять корректность вызова рестарта при компиляции. Т.е. функция декларирует, что может быть использована с такими-то рестартами, которые принимает параметры такого-то типа. А при использовании функции и перехвате ошибки, можно указать, какой рестарт в случае чего использовать и с какими параметрами. И типа если такой рестарт не поддерживается или типы его аргументов не совпадают, то ругаться. Я тоже над этой херней думал. Динамические языки для таких вещей лучше подходят.

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

Что-то ты такое невероятное вещаешь.

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

Какая функция?

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

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

Какая функция?

Любая.

Более того, condition сам по себе, рестарт сам по себе, биндинги устанавливаются в рантайме сами по себе.

Я знаю. Ты наверно не так меня понял. Ты задаешь рестарт, как вызывающая тебя функция узнает о нем? Ей нужно знать, чтобы в биндинге заюзать. И нужно не только имя, но и типы параметров. Речь о стат.типизации(сам я за динамику, если чо). Какие еще варианты?

Можно еще подождать, что скажет первый анонимус.

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

Любая.

Я имею в виду, где она стоит по стеку в механизме обработки ошибок и какую роль играет?

Ты задаешь рестарт, как вызывающая тебя функция узнает о нем?

Опять не понял, кто меня вызывает. Вообще, никакая функция рестарт не вызывает. В handler-bind, конечно, используется функция, а не рестарт, но это роли не играет. В этой функции обычно уже выбрана стратегия обработки ошибок.

Я имею в виду, что рестарты полностью динамичны. Даже в месте в коде, где принимается стратегия исправления ошибки, рестарты могут быть разными. Может быть такой код:

(handler-bind ((my-error #'(lambda (c) (if (find-restart 'r1) (invoke-restart 'r1) (invoke-restart 'r2))))) (my-code))

Хотя я только быдлокодер-любитель, но я как-то не понимаю, почему рестартов не может быть в языке со статической типизацией. Особенно с системой типов Common Lisp'а.

Например:

(define-condition my-error () ())
(define-condition my-error1 (my-error) ())
(define-condition my-error2 (my-error) ())


(handler-bind
 ((my-error #'(lambda (my-error c) (if (find-restart 'r1) (invoke-restart 'r1) (invoke-restart 'r2))))) ;; Словит и my-error1 и my-error2
  (my-code))

Это вроде называется sybtype polymorphism, и в common lisp, в принципе, много типов его используют, например

> (typep 2 'number)
t
> (typep 2 'integer)
t
> (typep 2 'fixnum)
t
> (typep 2 'float)
nil

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

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

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

Я имею в виду, что если впилить такую систему в другой язык со статической типизацией

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

Объясни дураку, что такое эти самые рестарты? Вот мы вызываем функцию, она работала-работала и внезапно подохла. Что дальше? Берём результат частичного выполнения функции и запускаем ф-ю заново, но скипнув тот кусок что уже был обработан?

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

Объясни дураку, что такое эти самые рестарты?

+1 нигде толком нормального описания не видел, что за вундервафля такая

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

У тебя рестарты «без аргументов». Для этого случая можно так и оставить. А если я хочу, например, рестарт с аргументом для значения, которое потом заюзаю? Мне тогда нужно или разрешить пихать в любой рестарт все, что угодно(а это хреново с точки зрения стат.типизации), или же как-то сообщать, что у нас возможны вот такие вот рестарты и втуда можно передать такие вот аргументы. Возможно я просто не имею достаточного опыта работы с CL и несу чушь, но если меня поправят, то буду рад.

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

Там дальше ссылка на pcl

а там дальше в этом pcl аффтар пишет что ничего нам про рестарты не расскажет и что идите просвещайтесь на эту тему более глубоко в другое место

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

Ну вот смотри пример из книги по твоей ссылке

(defun parse-log-entry (text)
  (if (well-formed-log-entry-p text)
    (make-instance 'log-entry ...)
    (restart-case (error 'malformed-log-entry-error :text text)
      (use-value (value) value)
      (reparse-entry (fixed-text) (parse-log-entry fixed-text)))))

Как ты себе представляешь задание use-value и reparse-entry в языке со стат.типизацией? Как ты себе представляешь их последующее использование в вышестоящих функциях?

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

Это, по-моему, нормально, так как тут как раз можно заранее сказать, что ты передашь рестарту.

#'(lambda (c) (invoke-restart 'my-restart (the my-error c)))

Опять же, в my-error могут входить и более узкие подклассы ошибок.

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

Как ты себе представляешь задание use-value и reparse-entry в языке со стат.типизацией?

В них тип знать не обязательно. Возможный пример на псевдо-C:

defrestart use_value(void *val) {return val;}

Псевдокаскель:

x a = a

Как ты себе представляешь их последующее использование в вышестоящих функциях?

Сделай ограничение, что тип value такой же, как и результата вычесления формы в restart-case. Обычно так и есть.

ИМХО, проблем нет

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

В них тип знать не обязательно. Возможный пример на псевдо-C

Даже тут у тебя задано, что параметр 1. А если человек два передаст? Или ни одного? И, опять же, кто будет его контролировать? У нас статическая типизация или нет?

Сделай ограничение, что тип value такой же, как и результата вычесления формы в restart-case. Обычно так и есть.

Так тип-то этот вышестоящая функция не знает. Вот поэтому и нужно, чтоб тип рестартов был частью типа функции... Иначе получится голая динамика без проверок(что не слишком органично смотрится на фоне стат. типизации).

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

Вот поэтому и нужно, чтоб тип рестартов был частью типа функции...

Чем-то это напоминает checked exceptions из Явы, которые общепризнанный фейл.

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

Так тип-то этот вышестоящая функция не знает.

Может я чего-то не знаю, но зачем ей его знать? Очевидно, ошибка может произойти внутри формы restart-case. Restart case предоставляет вариант исправления. Вне restart-case программе абсолютно побарабану, был вызван рестарт или нет.

Даже тут у тебя задано, что параметр 1

Мы вообще про количество аргументов или их тип? Как статическая/динамическая типизация влияют на количество аргументов, переданных куда либо?

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

Может я действительно не прав, даже не знаю

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

Из того, что я прочитал в википедии, получается, что это жутко негибко. Если где-то что-то выкидывает, ты должен везде выше прописать инфу об этом

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

Иначе получится голая динамика без проверок(что не слишком органично смотрится на фоне стат. типизации).

Тут я и не прав, видимо, потому что суть рестартов - динамика

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

Они действительно фейл, да. Но тут-то не о том речь. Здесь у обрабатывающего кода есть возможность «вызывать» рестарт, передавая ему определенные параметры. Как это можно не контролировать при стат. типизации?

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

Можешь переписать вот это

(defun parse-log-entry (text)
  (if (well-formed-log-entry-p text)
    (make-instance 'log-entry ...)
    (restart-case (error 'malformed-log-entry-error :text text)
      (use-value (value) value)
      (reparse-entry (fixed-text) (parse-log-entry fixed-text)))))[/lisp]
на Rust? Ну и использование тоже интересно...

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

А как там связывается функция, которая предоставляет рестарт и рестарт? Никак? А что будет, если я вызову рестарт при обработке, а этот рестарт не установлен?

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

Можешь переписать вот это

Компилятора Rust под рукой нет.

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

Как обычно - failure и unwinding.

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

Зачем тебе вообще эти рестарты? Ну объяснено же у Страуструпа, все по полочкам разложено, почему они не нужны. Подтверждено и авторитетами и ссылками на исследования надежных систем и пр. и пр. Что-то похожее на рестарты тебе понадобится в редких случаях. Тогда можно воспользоваться паттерном стратегия, передавая нужную стратегию явным параметром/«глобальной» переменной/thread-local переменной и т.д. и т.п. Никаких проблем и сложностей. А механизм исключений не перегружен всякими ненужными глокими куздрами.

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

Ну объяснено же у Страуструпа, все по полочкам разложено,
почему они не нужны. Подтверждено и авторитетами и ссылками на
исследования надежных систем и пр. и пр.

Хватит врать то.

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

D&E

Глава 16. Обработка исключений

16.6. Возобновление или завершение?

Например:

Затем, на заседании в Пало Альто в ноябре 1991 г. мы услышали блестящий доклад Джима Митчелла (Jiш Mitchell), работавшего в компании Sun, а перед этим - в Xerox PARC. Доклад содержал доводы в пользу семантики завершения, подкрепленные личным опытом и фактическими данными. На протяжешш 20 лет Митчелл пользовался обработкой исключений в разных языках и на первых порах отстаивал семантику возобновления в качестве одного из главных проектировщиков и разработчиков системы Cedar/Mesa от компании Xerox. Его вывод звучал так:

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

Свое утверждение Митчелл подкрепил рассказом о работе над несколькими операционными системами. Самым главным был пример системы Cedar/Mesa, которую написали программисты, любившие и умевшие пользоваться семантикой возобновления. Однако через десять лет в системе из полумиллиона строк остался лишь один случай использования возобновления - в запросе контекста. Поскольку и в данной ситуации оно фактически было не нужно, .механизм возобновления исключили полностью, после чего скорость работы этой части системы значительно возросла. Во всех тех случаях, когда применялась операция возобновления (а это более десяти лет эксплуатации), появлялись определенные проблемы и приходилось искать более подходящий механизм.

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

Один пример - это как-то тухло.

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