Неподвижная точка
Jun. 14th, 2013 02:25 amУ меня впервые выплыла серьёзная задачка, в решении которой я применил понятие из сабжа – этот факт стоит зафиксировать. Раньше всякие там красивые понятия типа y-combinator или теорема Банаха были просто такими интересными зверьками. Ну, дожили вот )
Неподвижная точка функции это например вот что. Если взять калькулятор, набрать на нём какое-то число и много раз подряд нажимать на кнопку cos, в какой-то момент число меняться перестанет (либо начнёт колебаться на 1 в последнем разряде из-за округления).
Вот это число и будет неподвижной точкой той конкретной реализации взятия косинуса в вашем калькуляторе.
В реальной жизни мне это понадобилось в неожиданном месте – для валидации данных jQuery.my-форм на сервере с помощью описания самой формы.
Дело в том, что описание формы jQuery.my не содержит валидатора конечных данных – там есть только
- функции-связыватели полей ввода и данных (“свяжи значение из поля ввода "Год” с ячейкой памяти data.year”) в обе стороны
- валидаторы полей ввода (не данных)
- указатели зависимостей (мы явно говорим, после изменения каких полей какие ещё поля надо заново проверять и пересчитывать)
- ограничители на максимальную глубину связывания зависимостей (для избежания бесконечных циклов когда А зависит от Б, и Б – от А)
То-есть когда форма уже нарисована на экране, всё понятно. А вот когда она на сервере и никакой html-странички, в которой форма нарисована, нет – что делать не очень ясно было в общем случае.
Поля ввода совершенно не обязательно содержат данные в том же виде, в каком они хранятся в памяти. Ну то-есть дату нам удобнее вводить как 10-5-2012, а вот хранить её надо как 2012-05-10. Также возможно объединение полей – когда например отдельно вводятся год и выбирается месяц-день (3 поля ввода), а хранится всё в одной ячейке date в формате как в примере выше.
Получается, что у нас есть три функции отображения данных в памяти на поля ввода (назовём их gettern – нижний индекс это “номер” поля), три функции отображения обратно (settern) и три валидатора полей ввода (okn). Валидаторы или передают полученное на вход дальше, или останавливают вычисления с ошибкой.
Задача звучит так: как в общем случае, имея только эти функции и валидаторы-ограничители, проверить корректность данных в памяти. То-есть, как имея функцию отображения и результат, определить, мог ли быть получен такой результат из этой функции.
Она, задачка эта, неожиданно – а я давно над ней думаю – решается в одно соображение.
В общем случае данные верны, если они являются неподвижной точкой для цепочки отображений
По-русски это так. Если проделать все вызовы в той же очерёдности, включая зависимости, по той же схеме, что их делает jQuery.my, данные после всех вычислений должны совпасть с исходными данными и ни одно вычисление не должно вернуть ошибку.
С точки зрения практической реализации здесь куча технических моментов и пограничных случаев, но они все преодолимы.
Есть проблема и покруче – нужно обеспечить идентичный scope для функций на клиенте и на сервере. Эта задача в лоб неразрешима в принципе в общем случае, так что тут надо исхитряться и её ослаблять-переформулировать.
Наилучшая переформулировка – как обеспечить создание таких форм, в которых валидаторы и байндеры работают в контролируемом scope (не лезут там в глобальные переменные, не пытаются провалидировать данные, сравнивая их с текущим временем и тп).
Эта задачка решаема – она административная, а не математическая.
Такое вот зауми псто, ога.
no subject
Date: 2013-06-13 11:23 pm (UTC)no subject
Date: 2013-06-14 10:59 am (UTC)Если между одной ячейкой (скажем, a="год" из множество годов A) и другой, скажем (b="полная дата" из множества дат B), установлены два соответствия: f: B x A -> B (пересчитывает полную дату, используя то, что там уже было плюс значение года), и g: A x B -> A, то, по сути, тебе хочется доказать, что если год успешно провадилирован, то после соответствующего изменения полной даты, и попытки обратно вычислить год, он не изменится. Т.е. ok(a) => g(f(a))=a.
Поди попробуй *автоматически* докажи это для произвольных функций, вроде "g(x) = число, составленное из подряд идущих десятичных цифр начиная с позиции 1000 из корня кубического из x, вплоть до ближайшей последовательности 1234, где x любое натуральное число".
Это даже не возможно автоматически протестировать, т.к. "x любое натуральное число". Причём за заумными примерами далеко ходить не надо, достаточно работать не с числами из ограниченного диапазона, а со строками, скажем, имя-фамилия, или кусочки адреса. Задолбаешься.
Если же ты будешь поддерживать сложные связанные вычисления (не-копирование) только очень ограниченного вида, вроде кусочек даты копируется в сборную дату, или строка копируется в подстроку в позициях таких-то, то это становится выполнимо.
no subject
Date: 2013-06-14 11:03 am (UTC)no subject
Date: 2013-06-14 11:56 am (UTC)Ты пытаешься автоматически доказать вычислимость и, вообще говоря, остановку произвольно взятых функций (ну то-есть программ). Ни то, ни другое невозможно в общем случае.
Проблему остановки (например, прямое отображение меняет порядок букв в строке на обратный, а обратное просто копирует строку) я решаю ограничением возможной глубины рекурсии, это обозначено в условиях, так и формы работают.
Проблема вычислимости в моём случае редуцирована до "вычислимость за заранее определённое время" – это не совсем корректно, но это лучше чем ничего.
Моя задача – убедиться в том, что когда пользователь нажимал Send у него ничего там красным не горело. Ты решаешь задачу доказательства правильности программы – это невозможно просто.
no subject
Date: 2013-06-14 11:57 am (UTC)no subject
Date: 2013-06-18 07:42 am (UTC)1. В случае, если валидация ячейки прошла успешно, т.е. okn(form_valuen), то гарантируется, что все взаимосвязанные ячейки пересчитаются в некоторое корректное для них значение. Т.е. тебе не нужно полагаться на цепочку "необходимых, но, возможно, не достаточных" условий, а твои условия на каждое поле всегда "достаточные".
2. В случае okn(form_valuen), гарантируется, что gettern(settern(form_valuen)) = form_valuen.
3. В графе взаимосвязей там либо нет циклов, либо есть циклы, но такие, что ты приходишь к исходому элементу всегда ровно с тем же значением, которое там было.
4. Не позволять убирать фокус с элемента, если значение в нём неправильное. Т.е. в любой момент времени неправильным может быть только одно значение, которое пользователь сейчас вводит.
С этими условиями, поддерживать корректность формы - тривиально: при вводе проверяешь okn(form_valuen), и если оно верно, пересчитываешь все взаимосвязи простым обходом дерева в глубину (граф тривиальным образом превращается в дерево - можно помнить уже посещённые вершины и не идти по рёбрам, которые создадут цикл).
Как видишь, никакими неподвижными точками заморачиваться не пришлось.
Send disabled если not ok() для поля в фокусе и enabled в другом случае.
Что ты думаешь?
no subject
Date: 2013-06-18 08:09 am (UTC)2. Гарантируется не на атомарном цикле вычислений, а после всех вычислений – почуствуй разницу. Чтобы понятнее – см п.1
3. ок
4. это делать нельзя, просто нельзя. во-первых, не для всех элементов существует понятие "фокус". Во-вторых, см п.1. В-третьих, это скотство с точки зрения ux в большинстве случаев, потому что в простонародье такой сценарий называется "квест" )
no subject
Date: 2013-07-04 05:49 pm (UTC)Это не очень то жесткие ограничения оказались, напишу пост.