DRAKON.SU

Текущее время: Среда, 24 Апрель, 2024 00:22

Часовой пояс: UTC + 3 часа




Начать новую тему Ответить на тему  [ Сообщений: 2 ] 
Автор Сообщение
СообщениеДобавлено: Среда, 04 Декабрь, 2013 14:44 

Зарегистрирован: Понедельник, 25 Июнь, 2012 17:26
Сообщения: 396
Владислав Жаринов писал(а):
PSV100 писал(а):
...
Есть потенциал делать менее развесистыми и управляющие алгоритмы. ...
Алгоритм (рис. 1а) представлен в двух текстовых вариантах (рис. 1б и 1в), добавим третий:
... Транслятор а-ля LiveScript в таких случаях будет генерировать код необязательно через реальные исключения, на выхлопе могут быть те же классические "if"-ы, goto, jump и т.д., что нужно на целевой платформе.
...

- Ваши предложения не связаны с обсуждавшимся здесь: viewtopic.php?p=58038#p58038 (включая исходную ветку)? т.е. Вы хотите AND-THEN и/или PROGRESS-IF?

В целом, в исходном сообщении речь шла, в т.ч., и о технике реализации нечто подобного а-ля "PROGRESS-IF". Т.е. ставиться задача организовать алгоритм некоего управляющего характера, когда важно, или крайне желательно, содержательные и контекстные действия оформлять рядом, наглядно. Попробую раскрыть суть чуть подробнее.

Поскольку изначально примеры были на псевдокоде в стиле языка CoffeeScript/LiveScript, то и продолжу в том же духе. Подобные языки создаются для формирования "когнитивного" исходного кода, в частности, с императивной строгой (по умолчанию) семантикой, которые позволяют транслировать код как в целевой байт-/бинарный код, так и в исходники "низкого" уровня - JavaScript/C/C++/Pascal и т.д. Задаваемая формальная модель вычислений, где обработка ошибок м.б. реализована в т.ч. и через механизм исключений, позволяет на выходе в этом случае иметь как необходимые jump-переходы, так и, например, "нижний" код с задействованием флагов с анализом через if/switch, или goto. И ключевой момент - как организовать удобную обработку ошибок или анализ результатов ф-ций, легко, понятно, с минимум уровней вложенности кода и т.п.

В последнее время всё больше и больше становится модным принцип обработки ошибок в стиле Erlang-а, который себя оправдал на многолетней практике. Основная суть: в случае непредвиденной или нежелательной ситуации необходимо генерировать ошибку (исключение), прерывать работу процесса, раскручивать стэк, выполнять уничтожение объектов и освобождение ресурсов. При этом, как правило, в системе выстраивается иерархия процессов, где наверху м.б. процессы-супервизоры, которые контролируют своих подчиненных. Они получают сигналы о завершении работы своих наблюдаемых или связанных труженников, в т.ч. и об аварийных ситуациях. В результате надзиратели могут принять решение, чего же делать при крахе контролируемого. В основном, типовой сценарий - пусть процесс упадёт, зафиксируем этот факт в логе, запустим заново, разберёмся потом, а сейчас работать надо, без остановки. И такова архитектура позволяет в т.ч. контролировать и удаленные, распределенные, процессы. Основная мотивация для оправдания уничтожения процессов - в случае ошибок (исключений) крайне проблематично, в общем случае, особенно при наличии раскрутки стэка, восстановить или сформировать корректное состояние процесса, да и к тому же необходимо проектировать и реализовывать дальнейшую работу процесса после ошибок. Что в итоге только усложняет разработку и ещё больше способствует появлению глюков. Однако, это не значит, что на каждый чих нужно выбрасывать исключение. Немалая часть возникающих проблем вполне ожидаема и естественна, и их необходимо учитывать. К тому же желательно, чтобы библиотечный обобщённый код поменьше вмешивался в логику работы прикладного кода приложения. Поэтому часть проектируемой информации о проблемах содержится в результатах функций, где на всю катушку используются алгебраические типы (на счёт обработок ошибок в Erlang-е: документация, статья, здесь пару слов).
И для обработки результатов функций применяется "сопоставление с образцом" (pattern matching). Причём в Erlang-е нет "присваивания", знак "равно" означает именно сопоставление (где может быть и связывание переменных со значением). Это отличный приём для вооружения. Если сопоставление неуспешно, то автоматически генерируется исключение вида "bad match error...", и в объекте исключения (для дальнейшего анализа) можно зафиксировать место возникновения ошибки (отражающее условие сопоставления) и значение, с которым сопоставляли (т.е. то, что было во время вылета ошибки справа от "равно"):
Код:
x = 1   
y = 2
~x = y  # здесь возникнет "bad match..."

В коде выше добавленная к "х" закорлючка "~" означает не связывание с переменной, а сопоставление с уже хранящимся значением в "х" (кстати, в самом Erlang-е нет явных подобных указаний, и если раньше где-то переменная уже введена в строй, то автоматически выполняется сопоставление со значением, что иногда может приводить к неожиданностям, когда кто-то не заметил/забыл, что переменная с таким именем уже использовалась где-то выше по коду). Подобная техника позволяет расслабиться и не писать лишних типовых if/case/switch/raise/throw и т.д. для обработки результатов и выброса исключений, по таким мотивам (с заглавной буквы - конструкторы данных или "enum" и т.п.):
Код:
load_file = (name) ->
    OK f = file::open(name, [Read, Binary])
    OK bin = file::read(f, 1024*1024)
    binary_to_data(bin)

Функции типа "file::open" обычно возвращают кортеж вида "OK(data) | Error(message)", и требуя "ОK" в нужных местах мы не пройдём вперёд, вылетим по исключению, и уже где-то наверху решат, чего делать.

Однако не всегда допустимы вылеты из процесса и его перезапуск. В каких-то случаях рестарт приведёт к излишнему переоткрытию файла, переконнекту по сети и т.п. В самом Erlang-е имеются механизмы для перехвата исключений по типовой схеме а-ля "try-catch/except-finally" (и некоторые др.). В исходном сообщении форума был затронут язык Go. Гугловцы передрали вычислительную модель Erlang-а (его модель акторов), в т.ч. и принципы обработки ошибок. Но для перехвата исключений схему "try-except-finally" (в отличие от С++ в Go нет деструкторов, поэтому им нужен и "finally") гугловоды критикуют. Подобные инструкции усложняют код, вносят новые уровни вложенности, размазывают логику на части (часть алгоритмов улетает в секции "except"), в случаях, когда необходимо по месту обработать ошибку и повторить попытки, возникают вложенные "try...try" и т.д. и т.п. В общем, рациональное зерно есть. Они придумали Defer, Panic, and Recover (плюс здесь). Вызов "defer" определяет "отложенные" вычисления, которые будут исполнены после завершения функции (причём именно функции, в отличие от деструкторов, которые должны отрабатывать сразу при выходе из области видимости и возможно и раньше, если объект больше не используется). В случае нескольких "defer" вычисления определяются согласно LIFO. "Recover" останавливает "панику", использоваться может только внутри "defer", прекращает раскрутку стэка, в результате есть возможность определить значение ф-ции в случаях неуспеха. Гугловцы рекомендуют возвращать ошибки как результаты ф-ций и "паниковать" только по делу. Но в Go нет сопоставления с образцом, анализ ошибок требует возни с if/switch, в общем, не всё так приятно, как хотелось бы. В результате сами же go-разработчики в базовых библиотеках интенсивно "паникуют", правда на "нижнем уровне" и стараются не пропускать панику за пределы интерфейса модуля, чего всем и советуют. И такая ситуация вызывает критику со стороны тех, кто привык работать в системах, где якобы нет неоднозначности как обрабатывать ошибки - через возврат кодов/объектов или через исключения - если приняты исключения, то хочешь или нет, но ты тоже вынужден будешь их интенсивно использовать, работая с кучей общераспространенных библиотек.
В общем, в Go есть нюансы с "паникой", а вот сами "defer"-вычисления, как таковы, очень даже ничего себе... Удобно реализовывать "finally":
Код:
load_file = (name) ->
    OK f = file::open(name, [Read, Binary])
    defer file::close(f)                      # указываем, что в конце ф-ции закрыть файл,
    ...                                       # вне зависимости от исключений
    OK bin = file::read(f, 1024*1024)
    binary_to_data(bin)

И на счёт самих исключений... Основной недостаток типовой модели "try-except/catch" и "паник" в том, что при выбросе исключения в блоке "except/catch" потенциально существует возможность проанализировать ошибку и принять меры, но, в общем случае, вернуться в исходное место и повторить попытку не получится, имея в виду без дополнительных внешних телодвижений поверх try-except. Плюс пока включится "except/catch" произойдёт раскрутка стэка с соответствующим разрушением контекстного состояния. В связи со сказанным, модель исключений в лисп-классике гораздо эффективнее и гибче: Conditions and Restarts (перевод). Такая модель позволяет организовать обработку исключений без раскрутки стека (при необходимости), т.е. при возникновении ошибки на месте принять меры и повторить вычисления с учётом корректив, при этом принятие решения как обработать ошибку можно вынести на верхний уровень, т.е. в функции выше уровнем, которые вызывают данную процедуру, где возникла проблема. Пример:
Код:
# определим типы данных, через которые выразим возможные исключительные ситуации
# и ответные реакции на них:

BadFile(msg: str) = Retry(fname: str)  | UseDefault     # при ошибке открытия файла: открыть другой указанный файл или использовать файл "по умолчанию"
InvalidRead       = SetValue(val: str) | SetZero        # при ошибке чтения данных: использовать указанную строку или ноль


# определим ф-цию чтения данных, которая принимает имя файла и возвращает прочитанные данные,
# указав также то, что ф-ция может сигнализировать 2 сигнала об исключениях:

load_file:  (str)  -> SomeData(a) with BadFile|InvalidRead
load_file = (name) ->
    # пытаемся открыть файл, в случае неуспеха посылаем сигнал BadFile с сообщением
    # о проблеме и, если в ответ указали реакцию, делаем ещё одну попытку открытия необходимого файла:
    try
        OK(f) else BadFile(msg) = file::open(name, [Read, Binary])
    handle
        Retry(newname) =>  OK(f) = file::open(newname, [Read, Binary])
        UseDefault     =>  OK(f) = file::open(service::get_def_name, [Read, Binary])

    # "отложено" закрываем файл
    defer file::close(f)                     
    ...
    # пытаемся прочитать данные, при неуспехе сигнализируем "InvalidRead" и применяем то,
    # чего указали в ответ:
    try
        OK(bin) else InvalidRead = file::read(f, 1024*1024)
    handle
        SetValue(v) =>  bin = v
        SetZero     =>  bin = 0

    # используем прочитанные данные...
    binary_to_data(bin)


# применяем ф-цию чтения данных:
some_func = () ->
    ...
    # в секции "with" указываем возможные реакции на сигналы, посылаемые из "load_file":
    try
        bindata = load_file(file_name)
    with
        BadFile _   => Retry(alt_name)
        InvalidRead => SetValue("*invalid*")
    ...
    # продолжаем работу...
    use_data(bindata)

Секции "try-with-handle" похожи на типовые "try-except/catch", но есть семантические отличия. В случае исключения внутри "try..." мы сразу не вылетаем наверх, первоначально не выполняется раскрутка стэка, если где-то определён подходящий блок "with" (как и try-except и им подобные в типовых языках, возможны несколько блоков по уровням), то посылается соответствующий сигнал (условно, т.е. выполняются вычисления в блоке with), и исключение "трансформируется" в указанный ответ. Затем выполняется то, что определено в блоке "handle", вне зависимости от того были или нет задействованы "with". Если в "handle" не обработано исключение, то вылетаем по ошибке как обычно (операторы "try-with-handle" напоминают операторы для "pattern match" вида "case-of", т.е. в них возможны любые действия).

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

Запись вида " OK(f) else BadFile(msg) = file::open(name, [Read, Binary]) " есть сокращенная форма сопоставления:
Код:
case file::open(name, [Read, Binary]) of
     Ok(f)      => _                     # т.е. ничего не делаем, просто связываем переменную "f" со значением
     Error(msg) => fail BadFile(msg)     # "выбрасываем" исключение, т.е. сигнализируем об ошибке

В блоке "with" мы можем выбросить новое исключение, соответственно уничтожив тех, кто старался выжить, или же прекратить их работу, например, через ф-цию "abort", где в качестве параметров необходимо задать результат вычислений:
Код:
some_func = () ->
    ...
    bindata = try load_file(file_name) with
                    BadFile _   => if has_alt_name then Retry(alt_name)
                                                   else fail "Bad file ..."   # решили упасть...
                    InvalidRead => case some_flag of
                                        AsZero    => SetZero
                                        AsStr     => SetValue("*invalid*")
                                        NeedAbort => abort(0xFFFF)            # прерываем работу ф-ции load_file и указываем
    ...                                                                       # результат для bindata как 0xFFFF

Таким образом:

- желательно, чтобы стандартный (или базовой, широко применяемый) библиотечный код поменьше мешал логике работы конечного прикладного кода в системах. В этом случае лучше передавать ошибки как результат ф-ции. Для таких ф-ций как "file::open" проблемы с открытием файла вполне ожидаемы, и лучше в прикладном коде конкретного приложения решать падать или нет;

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

- в иных случаях лучше "прибить" процесс, чтобы он дальше не работал глючным (да и сложно спроектировать всеохватывающею стратегию работы, с увеличением сложности больше рисков с ошибками), надзиратели переинициализируют вычисления, если необходимо;

- весьма полезны "отложенные" defer-вычисления, особенно когда желательно наглядно отразить действия компактно, рядом, в контексте. Они не заменяют, а дополняют деструкторы и технику RAII (и согласно примерчикам выше такая операция как закрытие файла должна быть оформлена как типовой деструктор для универсального применения, но при необходимости его автоматический вызов можно переопределить, типа "defer free file").

Ну и "сопоставление с образцом" - неплохой помощник. Вот такой вот вариант реализации PROGRESS-IF...


И ещё раз обращаю внимание, что для подобного кода блок-схемы - слабые помощники, особенно когда вместо явных циклов - рекурсии, передаваемые по месту "лямбды", применение "аспектного" программирования, как, например:
Код:
# где-то в каком-то модуле устанавливаем "перехваты":

#[turn before load_file]
check_before = (file_name) ->
    log("before: " + file_name)

#[turn after load_file]
check_after = (file_name) ->
    log("after: " + file_name)

#[turn when BadFile]                # ф-ция сработает, когда возникнет сигнал исключения BadFile(msg: str),
check_bad_file = (err_msg) ->       # параметр msg будет передан ф-ции
    log("bad open: " + err_msg)


# в другом модуле используется load_file:

some_func = () ->
    ...
    try
        bindata = load_file(file_name)
    with
        BadFile _   => Retry(alt_name)
        InvalidRead => SetValue("*invalid*")
    ...

do ->
    some_func()

# в результате при исполнении some_func появятся сообщения в логе:
#   "before ..."
#   "bad open ..."   если во время выполнения load_file был сигнал BadFile
#   "after ..."


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

(модератор) выделено из viewtopic.php?p=83835#p83835 по указанию администрации раздела


Последний раз редактировалось Евгений Темиргалеев Четверг, 26 Декабрь, 2013 20:03, всего редактировалось 1 раз.
пометка о переносе


Вернуться к началу
 Профиль  
 
СообщениеДобавлено: Среда, 04 Декабрь, 2013 14:53 

Зарегистрирован: Понедельник, 25 Июнь, 2012 17:26
Сообщения: 396
Владислав Жаринов писал(а):
...
может, как-то алгоритмизовать полустрогие логоперации, как они даны Мейером: viewtopic.php?p=69127#p69127 (в т.ч. OR-ELSE)?..

На счёт ленивости, здесь на проблему необходимо взглянуть шире, например, вот сюда, Будущее программирования:
Цитата:
...
Среды времени исполнения

Языки с нестрогой семантикой будут доминировать в новом мире благодаря увеличению повторно используемого кода и модульности, которая характерна для «нестрогости по умолчанию». Как я уже говорил в предыдущем посте, опциональная ленивость не справляется. Как и в случае с другими упомянутыми проблемами, проблемы со строгостью по умолчанию неочевидны для большинства программистов, включая тех, кто считает себя знакомым с областью функционального программирования. Проблемы, которые привносит строгость, становятся хорошо заметны только после существенного углубления в функциональный стиль разработки, в частности, после ознакомления с идеей библиотек комбинаторов и абстракций, необходимых для уничтожения дупликации кода [DRY] в этих библиотеках. Эти проблемы стали источником затруднений в библиотеке Scalaz, которая поддерживает функциональный стиль в Scala, строгом по умолчанию языке, также эти проблемы заметны в нашей рабочей кодовой базе на Scala.

Единственная существенная проблема с ленивостью — необходимость понимать, как используются память и стэк. Эта проблема пользуется популярностью среди людей, которые предпочитают находить своему нежеланию учить Haskell и ФП рациональные объяснения, но исследования и более умные стратегии вычисления в состоянии с ней справиться. Опять же, нет законов природы, утверждающих, что мы должны уступить и искать утешения в строгом порядке вычислений.

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

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

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

(и у автора в блоге есть связанные заметки на сей счёт: Optional laziness doesn't quite cut it, Perfect strictness analysis (Part 1), Perfect strictness analysis (Part 2), на счёт редукции вычислений неплохо расписано, например, у Холомьёва).

В общем, я тоже думаю, что грань между "функциональным" и "императивным" постепенно будет стираться, и компиляторы всё больше и больше будут понимать зависимость между вычислениями. И в идеале, в языке программирования не хотелось бы иметь схожих сущностей с тонкими слабо заметными нюансами, таких как одновременное использование "and, or, and then, or else". Пусть лучше будет один вариант как "and then, or else", т.е. задающий необязательность вычисления второго аргумента и с последовательностью слева направо, а умные компиляторы сами перекроят код (и в большинстве случаев не только вокруг "and, or"), с гарантией правильной семантики. В некоторых языках, например, ruby (если я не ошибаюсь) есть смешивание операторов "and, or, &&, ||". Неподготовленный человек, читающий сторонний код, с лёгкостью может предположить, что "and <=> &&" и "or <=> ||", т.е. просто синтаксический сахар, одно и то же, но это не так (у них разные приоритеты), что может привести к неправильному пониманию. Нужно поменьше неоднозначности.


Вернуться к началу
 Профиль  
 
Показать сообщения за:  Поле сортировки  
Начать новую тему Ответить на тему  [ Сообщений: 2 ] 

Часовой пояс: UTC + 3 часа


Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 18


Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете добавлять вложения

Найти:
cron
Вся информация, размещаемая участниками на конференции (тексты сообщений, вложения и пр.) © 2008-2024, участники конференции «DRAKON.SU», если специально не оговорено иное.
Администрация не несет ответственности за мнения, стиль и достоверность высказываний участников, равно как и за безопасность материалов, предоставляемых участниками во вложениях.
Powered by phpBB® Forum Software © phpBB Group
Русская поддержка phpBB