Лесли Лэмпорт — автор основополагающих работ в распределённых вычислениях, а ещё вы его можете знать по буквам La в слове LaTeX — «Lamport TeX». Это он впервые, ещё в 1979 году, ввёл понятие последовательной согласованности, а его статья «How to Make a Multiprocessor Computer That Correctly Executes Multiprocess Programs» получила премию Дейкстры (точней, в 2000 году премия называлась по-старому: «PODC Influential Paper Award»). Про него есть статья в Википедии, где можно добыть ещё несколько интересных ссылок. Если вы в восторге от решения задач на happens-before или проблемы византийских генералов (BFT), то должны понимать, что за всем этим стоит Лэмпорт.


Эта хабрастатья — перевод доклада Лесли на Heidelberg Laureate Forum в 2018 году. В докладе пойдёт речь о формальных методах, применяемых в разработке сложных и критичных систем вроде космического зонда Rosetta или движков Amazon Web Services. Просмотр этого доклада является обязательным для посещения сессии вопросов и ответов, которую проведет Лесли на конференции Hydra — эта хабрастатья может сэкономить вам час времени на просмотр видео. На этом вступление закончено, мы передаём слово автору.




Когда-то давно Тони Хоар написал: «В каждой большой программе живет маленькая программа, которая пытается выбраться наружу». Я бы это перефразировал так: «В каждой большой программе живет алгоритм, который пытается выбраться наружу». Не знаю, правда, согласится ли с такой интерпретацией Тони.


Рассмотрим в качестве примера алгоритм Евклида для нахождения наибольшего общего делителя двух положительных целых чисел $(M, N)$. В этом алгоритме мы присваиваем $x$ значение $M$, $N$ — значение $y$, и затем отнимаем наименьшее из этих значений от наибольшего, пока они не оказываются равны. Значение этих $x$ и $y$ и будет наибольшим общим делителем. В чем существенное отличие этого алгоритма и программы, которая его реализует? В такой программе будет много низкоуровневых вещей: у $M$ и $N$ будет определенный тип, BigInteger или что-то в таком роде; нужно будет определить поведение программы в случае, если $M$ и $N$ неположительные; и так далее и тому подобное. Четкой разницы между алгоритмами и программами нет, но на интуитивном уровне мы чувствуем отличие — алгоритмы более абстрактные, более высокоуровневые. И, как я уже сказал, внутри каждой программы живет алгоритм, который пытается выбраться наружу. Обычно это не те алгоритмы, про которые нам рассказывали в курсе алгоритмов. Как правило, это алгоритм, который полезен только для данной конкретной программы. Чаще всего он будет значительно сложнее тех, которые описаны в книгах. Такие алгоритмы зачастую называют спецификациями. И в большинстве случаев выбраться наружу этому алгоритму не удается, потому что программист не подозревает о его существовании. Дело в том, что этот алгоритм нельзя увидеть, если ваше мышление сосредоточено на коде, на типах, исключениях, циклах while и прочем, а не на математических свойствах чисел. Программу, написанную таким образом, сложно отлаживать, поэтому что это значит отлаживать алгоритм на уровне кода. Средства отладки предназначены для того, чтобы находить ошибки в коде, а не в алгоритме. Кроме того, такая программа будет неэффективной, потому что, опять-таки, вы будете оптимизировать алгоритм на уровне кода.


Как и почти в любой другой области науки и техники, решить эти проблемы можно, описав их математически. Для этого существует много способов, мы рассмотрим наиболее полезный из них. Он работает как с последовательными, так и с параллельными (распределенными) алгоритмами. Заключается этот метод в том, чтобы описать выполнение алгоритма как последовательность состояний, а каждое состояние — как присвоение свойств переменным. Например, алгоритм Евклида описывается как последовательность следующих состояний: вначале x присваивается значение M (число 12), а y — значение N (число 18). Затем мы отнимаем меньшее значение от большего ($x$ от $y$), что приводит нас к следующему состоянию, в котором мы отнимаем уже $y$ от $x$, и на этом выполнение алгоритма останавливается: $[x \leftarrow 12, \leftarrow 18], [x \leftarrow 12, \leftarrow 6], [x \leftarrow 6, \leftarrow 6]$.


Назовем последовательность состояний поведением, а пару последовательных состояний — шагом. Тогда любой алгоритм можно описать множеством поведений, которые представляют все возможные варианты выполнения алгоритма. Для каждого конкретного M и N есть только один вариант выполнения, поэтому для его описания достаточно множества из одного поведения. У более сложных алгоритмов, в особенности параллельных, множества поведений большие.


Множество поведений описывается, во-первых, начальным предикатом для состояний (предикат — это просто функция с булевым значением); и, во-вторых, предикатом следующего состояния для пар состояний. Некоторое поведение $s_1, s_2, s_3 ...$ входит в множество поведений только если начальный предикат верен для $s_1$, и предикаты следующего состояния верны для каждого шага $(s_i, s_{i+1})$. Попробуем описать таким образом алгоритм Евклида. Начальный предикат здесь такой: $(x = M)\land(y = N)$. А предикат следующего состояния для пар состояний здесь описывается следующей формулой:



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


Как видим, первая строка гласит, что в первом случае x больше y в первом состоянии. После логического И утверждается, что значение x во втором состоянии равно значению x в первом состоянии минус значение y в первом состоянии. После еще одного логического И утверждается, что значение y во втором состоянии равно значению y в первом состоянии. Все это значит, что в случае, когда x больше y, программа отнимет y от x, а y оставит неизменным. Последние три строки описывают случай, когда y больше x. Обратите внимание, что эта формула ложна, если x равен y. В этом случае следующего состояния нет, и поведение останавливается.


Итак, мы только что описали алгоритм Евклида двумя математическими формулами — и нам не пришлось связываться ни с каким языком программирования. Что может быть прекраснее этих двух формул? Заменить их одной формулой. Поведение $s_1, s_2, s_3 ...$ является выполнением алгоритма Евклида только в том случае, если:


  • $Init_E$ верно для $s_1$,
  • $Next_E$ верно для каждого шага $(s_i, s_{i+1})$.

Записать это как предикат для поведений (будем называеть его свойством) можно следующим образом. Первое условие можно выразить просто как $Init_E$. Это значит, что мы интерпретируем предикат состояния как верный для поведения только в том случае, если он верен для первого состояния. Второе условие записывается так: $\square Next_E$. Квадрат означает соответствие между предикатами пар состояний и предикатами поведений, то есть $Next_E$ верно для каждого шага в поведении. В итоге формула выглядит так: $Init_E\land\square Next_E$.


Итак, мы записали алгоритм Евклида математически. В сущности, это просто определения, или сокращения для $Init_E$ и $Next_E$. Полностью эта формула выглядела бы так:



Не правда ли, она прекрасна? К сожалению, для науки и техники красота не является определяющим критерием, но она говорит о том, что мы на правильном пути.


Свойство, которое мы записали, верно для некоторого поведения только в том случае, если выполняются два условия, которые мы только что описали. При $M = 12$ и $N = 18$ они верны для следующего поведения: $[x \leftarrow 12, \leftarrow 18], [x \leftarrow 12, \leftarrow 6], [x \leftarrow 6, \leftarrow 6]$. Но эти условия также выполняются для более коротких версий того же поведения: $[x \leftarrow 12, \leftarrow 18], [x \leftarrow 12, \leftarrow 6]$. А их мы не должны учитывать, поскольку это просто отдельные шаги уже учтенного нами поведения. Есть очевидный способ от них избавиться: просто не учитывать поведения, которые заканчиваются состоянием, для которого возможен хотя бы один следующий шаг. Но это не совсем правильный подход, нам нужно более общее решение. Кроме того, такое условие не всегда работает.


Обсуждение этой проблемы приводит нас к понятиям безопасности и активности. Свойство безопасности указывает, какие события допустимы. Например, алгоритму разрешается вернуть правильное значение. Свойство активности указывает, какие события должны рано или поздно произойти. Например, алгоритм рано или поздно должен вернуть некоторое значение. Для алгоритма Евклида свойство безопасности выглядит следующим образом: $Init_E \land \square Next_E$. К этому необходимо добавить свойство активности, чтобы исключить преждевременные остановки: $Init_E \land \square Next_E \land L$. В языках программирования в лучшем случае есть некоторое примитивное определение активности. Чаще всего активность даже не упоминается, просто подразумевается, что следующий шаг в программе обязательно должен произойти. И чтобы добавить это свойство, нужен довольно замысловатый код. Математически же активность выразить очень легко (как раз для этого нужен тот квадратик), но, к сожалению, у меня на это нет времени — нам придется ограничить наше обсуждение безопасностью.


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


$s = 17, \sqrt{2}, -2, \pi, 10, 1/2\\ t = 17, \sqrt{2}, -2, e, 10, 1/2$


Расстояние между этими двумя функциями — ?, поскольку первое различие между ними — на четвертом элементе. Соответственно, чем более продолжителен участок, на котором эти последовательности идентичны, тем ближе они друг к другу. Сама по себе эта функция не так уж и интересна, но она создает очень интересную топологию. В этой топологии свойства безопасности являются замкнутыми множествами, а свойства активности являются плотными множествами. В топологии одна из фундаментальных теорем гласит, что каждое множество является пересечением замкнутого множества и плотного множества. Если вспомнить, что свойства — это множества поведений, то из этой теоремы следует, что каждое свойство является конъюнкцией свойства безопасности и свойства активности. Это вывод, который будет интересен в том числе и программистам.


Частичная корректность означает, что программа может остановиться только в том случае, если выдаст правильный ответ. Частичная корректность алгоритма Евклида гласит, что если он завершил выполнение, то $x = GCD(M, N)$. А завершает выполнение наш алгоритм в случае, если $x = y$. Иначе говоря, $(x = y) \Rightarrow (x = GCD(M, N))$. Частичная правильность этого алгоритма означает, что эта формула верна для всех состояний поведения. Добавим в ее начало символ $\square$, который означает «для всех шагов». Как видим, в формуле нет переменных со штрихом, так что ее истинность зависит от первого состояния в каждом шаге. А если нечто верно для первого состояния каждого шага, то это утверждение верно для всех состояний. Частичная корректность алгоритма Евклида удовлетворяется любым поведением, допустимым для алгоритма. Как мы видели, поведение допустимо в том случае, если истинна только что приведенная формула. Когда мы говорим, что свойство удовлетворено, это просто значит, что это свойство следует из некоторой формулы. Не правда ли, это прекрасно? Вот она:


$Init_E\land\square Next_E \Rightarrow \square((x = y) \Rightarrow (x = GCD(M, N)))$


Перейдем к инвариантности. Квадрат со скобками после него называется свойство инвариантности:


$Init_E\land\square Next_E \Rightarrow \color{red}{\square((x = y) \Rightarrow (x = GCD(M, N)))}$


Значение, заключенное в скобки после квадрата, называется инвариант:


$Init_E\land\square Next_E \Rightarrow \square(\color{red}{(x = y) \Rightarrow (x = GCD(M, N))})$


Как доказать инвариантность? Чтобы доказать $Init_E\land\square Next_E \Rightarrow \square I_E$, нужно доказать, что для любого поведения $s_1, s_2, s_3 ...$ следствием $Init_E\land\square Next_E$ является истинность $I_E$ для любого состояния $s_j$. Мы можем доказать это методом индукции, для этого нам необходимо доказать следующее:


  1. из $Init_E\land\square Next_E$ следует, что $I_E$ верно для состояния $s_1$;
  2. из $Init_E\land\square Next_E$ следует, что если $I_E$ верно для состояния $s_j$, то оно также
    верно для состояния $s_{j+1}$.

Вначале нужно доказать, что $Init_E$ подразумевает $I_E$. Поскольку формула утверждает, что $Init_E$ верно для первого состояния, это значит, что $I_E$ верно для первого состояния. Далее, при $Next_E$, верном для любого шага, и $I_E$, верном для $s_j$, $I_E$ верно для $s_{j+1}$, потому что $\square Next_E$ значит, что $Next_E$ верно для любой пары состояний. Это записывается так: $Next_E \land I_E \Rightarrow I'_E$, где $I'_E$ — это $I_E$ для всех переменных со штрихом.


Инвариант, отвечающим двум условиям, которые мы только что доказали, называется индуктивным инвариантом. Частичная корректность не индуктивна. Чтобы доказать ее инвариантность, нужно найти индуктивный инвариант, который ее подразумевает. В нашем случае индуктивный инвариант будет такой: $GCD(x, y) = GCD(M, N)$.


Каждое следующее действие алгоритма зависит от его текущего состояния, а не от прошлых действий. Алгоритм удовлетворяет свойству безопасности, поскольку в нем сохраняется индуктивный инвариант. Алгоритм Евклида может вычислить наибольший общий знаменатель (т. е. он не останавливается, пока его не достигнет) благодаря тому, что в нем есть инвариант $GCD(x, y) = GCD(M, N)$. Чтобы понять алгоритм, необходимо знать его индуктивный инвариант. Если вы изучали верификацию программ, то вы знаете, что только что приведенное доказательство инварианта — это ни что иное, как метод доказательства частичной корректности последовательных программ Флойда-Хоара. Возможно, вы также слышали о методе Овики-Грис, который является распространением метода Флойда-Хоара на параллельные программы. В обоих случаях индуктивный инвариант пишется при помощи аннотации программы. И если это делать при помощи математики, а не языка программирования, это делается предельно просто. Именно это лежит в основе метода Овики-Грис. Математика делает сложные явления значительно доступнее для понимания, хотя сами явления, конечно же, от этого не станут проще.


Взглянем подробнее на формулы. Если в математике мы написали формулу с переменными $x$ и $y$, это не значит, что других переменных не существует. Можно добавить еще одно уравнение, в котором $y$ поставлено в отношение к $z$, это ничего не поменяет. Просто формула ничего не говорит ни о каких других переменных. Я уже говорил, что состояние — это присвоение значений переменным, сейчас к этому можно добавить: всем возможным переменным, начиная $x$ и $y$ и заканчивая населением Ирана. Должен признаться: когда я сказал, что формула $Init_E\land\square Next_E$ описывает алгоритм Евклида, я соврал. На самом деле она описывает вселенную, в которой значения $x$ и $y$ представляют выполнение алгоритма Евклида. Вторая часть формулы ($\square Next_E$) утверждает, что каждый шаг изменяет $x$ или $y$. Иначе говоря, она описывает вселенную, в которой население Ирана может измениться только в том случае, если изменилось значение $x$ или $y$. Из этого следует, что в Иране никто не может родиться после того, как завершено выполнение алгоритма Евклида. Очевидно, это не так. Исправить эту ошибку можно в том случае, если у нас допустимы шаги, для которых $x$ и $y$ остаются неизменными. Поэтому к нашей формуле нужно добавить еще одну часть: $Init_E \land \square (Next_E \lor (x' = x) \land (y' = y))$. Для краткости запишем это так: $Init_E \land \square [Next_E]_{<x,y>}$. Эта формула описывает вселенную, содержащую алгоритм Евклида. Те же изменения нужно внести в доказательство инварианта:


  • Доказываем: $Init_E \land \color{red}{\square [Next_E]_{<x,y>}} \Rightarrow \square I_E$
  • С помощью:
    1. $Init_E \Rightarrow I_E$
    2. $\color{red}{\square [Next_E]_{<x,y>}} \land I_E \Rightarrow I'_E$

Это изменение отвечает за безопасность алгоритма Евклида, поскольку теперь возможны поведения, в которых значения $x$ и $y$ не изменяются. Исключить такие поведения нужно с помощью свойства активности. Это сделать довольно просто, но сейчас я про это говорить не буду.


Поговорим о реализации. Предположим, у нас есть некоторая машина, которая реализует алгоритм Евклида подобно компьютеру. Она представляет числа как массивы 32-битных слов. Для простых операций сложения и вычитания ей нужно множество шагов, как компьютеру. Если пока не трогать активность, то такую машину мы также можем представить формулой $Init_{ME} \land \square [Next_{ME}]_{<...>}$. Что мы подразумеваем, когда говорим, что машина Евклида реализует алгоритм Евклида? Это значит, что следующая формула верна:



Не пугайтесь, мы сейчас рассмотрим ее по порядку. Она гласит, что наша машина удовлетворяет некоторому свойству ($\Rightarrow$). Этим свойством является формула Евклида $(Init_E \land \square [Next_E]_{<х,у>}$, многоточия — это выражения, которые содержат переменные машины Евклида, а $WITH$ — это подстановка. Иначе говоря, вторая строка — это формула Евклида, в которой $x$ и $y$ заменены на выражения в многоточиях. В математике нет общепринятого обозначения подстановки, поэтому мне пришлось придумать его самому. По сути, формула Евклида ($Init_E \land \square [Next_E]_{<х,у>}$) — это сокращение для формулы:



Красным выделена часть формулы, позволяющая $x$ и $y$ в $(Init_E \land \square [Next_E]_{\color{red}{<х,у>}}$ оставаться неизменными.


Описанное выражение утверждает не только, что машина реализует алгоритм Евклида, но и что она делает это с учетом указанных подстановок. Если просто взять пару программ и сказать, что переменные этих программ связаны с $x$ и $y$ — бессмысленно говорить, что всё это «реализует алгоритм Евклида». Обязательно указать, как именно алгоритм будет реализован, почему после всех подстановок формула станет истинной. Сейчас у меня нет времени, чтобы показать, что описанное выше определение является правильным, вам придется поверить мне на слово. Но вы, я думаю, уже оценили, насколько оно простое и элегантное. Математика действительно прекрасна — при помощи нее мы смогли определить, что значит, что один алгоритм реализует другой.


Чтобы доказать это, необходимо найти подходящий инвариант $I_{ME}$ машины Евклида. Для этого необходимо выполнить следующие условия:


  1. $Init_{ME} \Rightarrow (Init_E, WITH\thinspace x\leftarrow ..., y \leftarrow ...)$
  2. $I_{ME} \land [Next_{ME}]_{<...>} \Rightarrow ([Next_E]_{<х,у>}, WITH\thinspace x\leftarrow ..., y \leftarrow ...)$

Не будем сейчас в них вникать, просто обратите внимание на то, что это обычные математические формулы, хоть и не самые простые. Инвариант $I_ {ME}$ объясняет, почему машина Евклида реализует алгоритм Евклида. Реализация означает подстановку выражений на место переменных. Это вполне обычная математическая операция. Но в программе такую подстановку выполнить невозможно. Нельзя подставить a + b на место x в выражении присвоения x = …, такая запись не будет иметь смысла. Тогда как определить, что одна программа реализует другую? Если вы думаете только в рамках программирования — это невозможно. В лучшем случае вы сможете найти какое-нибудь мудреное определение, но гораздо более хороший способ — перевести программы в математические формулы и использовать определение, которое я привел выше. Перевести программу в математическую формулу значит дать программе семантику. Если машина Евклида является программой, а $Init_{ME} \land \square [Next_{ME}]_{<...>}$ — ее математическая запись, то $(Init_E \land \square [Next_E]_{<х,у>}, WITH\thinspace x\leftarrow ..., y \leftarrow ...)$ показывает нам, что это значит, что «программа реализует алгоритм Евклида». Языки программирования очень сложные, поэтому этот перевод программы на язык математики тоже сложный, так что на практике мы так не делаем. Просто языки программирования не предназначены для того, чтобы писать на них алгоритмы. Важность приведенного примера в том, что математика здесь показывает, что нужно сделать, чтобы реализовать алгоритм Евклида в программе: нужно определить, как x и y представлены в терминах состоянии программы и рассказывает, и что нужно дальше делать. Но, конечно, в первую очередь, для написания алгоритмов не стоит использовать язык программирования.


Кто-то обязательно скажет: математика работает на мелких выдуманных примерах, все знают, что в реальном мире программистам приходится иметь дело с огромными системами, и там от нее нету толку. На самом деле, это так только кажется. Единственное отличие алгоритма Евклида и большого алгоритма — это размер формулы. Вместо шести строк предикат следующего состояния может занимать несколько сотен или тысяч строк. Для таких больших формул мы используем иерархическую декомпозицию, так мы боремся со сложностью. В программировании есть множество мудреных методов для этого, но самый простой и мощный метод — математический, и этот способ называется определение. Чтобы выполнить иерархическую декомпозицию вот этой формулы:



мы даем имена частям этой формулы. Назовём вот эту часть $DecrementX$:



а вот эту часть — $DecrementY$:



Поэтому можно сказать, что:



Проще определений ничего не придумать.


Поговорим о реальных примерах математического описания алгоритмов. Rosetta — космический зонд, сделанный Европейским космическим агентством для исследования одной кометы. Некоторые ее инструменты управлялись операционной системой реального времени Virtuoso. Ее создатели затем сделали следующую версию этой системы и написали о ней книгу. Высокоуровневая структура там описана на TLA+, это язык для математической записи алгоритмов. Он работает по тем же принципам, что и примеры, которые я приводил в этом докладе. Процитирую отрывок из письма, которое мне прислал Эрик Верхольст (Eric Verhulst), он руководил разработкой системы. Я спросил его, принес ли им пользу TLA+, на что он ответил: «Во многом благодаря TLA+ нам удалось создать значительно более чистую архитектуру. Нам стали видны последствия многолетней промывки мозгов из-за программирования на C. Одним из результатов использования TLA+ стало уменьшение размера кода в десять раз по сравнению с Virtuoso». Вдумайтесь в эту цифру. Просто качественным программированием сокращения кода в десять раз не достичь. Для этого нужна более чистая архитектура, или, другими словами, более совершенный алгоритм. А для этого нужно математическое мышление. А к математическому мышлению приучает TLA+. Если вы мыслите в рамках языка программирования, такого результата вам не достичь.


Из космоса спустимся на облака. В Communications of the ACM несколько лет тому назад была ]опубликована статья о том, как веб-сервисы Amazon используют формальные методы. Amazon Web Services — это те, кто создают облачную инфраструктуру Amazon. Формальный метод, о котором идет речь — это всё тот же TLA+. Основные выводы статьи следующие. Во-первых, при помощи формальных методов удается найти баги в структуре системы, которые невозможно обнаружить никаким другим известным авторам способом. Подчеркну, это пишут программисты, которые в течение долгого времени занимаются распределенными системами. Во-вторых, по их мнению, формальные методы на удивление легко приживаются с общепринятыми методами разработки ПО и отлично окупаются. Нужно сказать, что авторы — люди, которые не витают в облаках, они привыкли оценивать технологии в долларовом эквиваленте. В-третьих, статья сообщает нам, что в Amazon формальные методы систематически применяются для проектирования сложных программ, включая публичные облачные сервисы. Мне недавно говорили, что около 10% программистов в этих группах используют TLA+.


Другой пример — Microsoft. Эпизодически там TLA+ применяется начиная с 2004 года. В конце 2015 года я написал короткую статью про TLA+, в которой было описано в т. ч. его применение в Amazon Web Services. Эту статью прочитал генеральный директор Microsoft Сатья Наделла, и на следующий день после рождества того года он отправил письмо высшему руководству компании, в котором рекомендовал им также познакомиться с этой статьёй. Приведу цитату: «С учетом сложности параллельных распределенных систем мы должны обеспечить корректность алгоритмов на уровне проектирования, в противном случае у нас возникнут проблемы в будущем». К счастью, в Microsoft по-прежнему пост генерального директора занимает бывший программист. Дальше он пишет: «Нам следует постараться вдохновить как можно больше программистов на использование этих методов». В 2016-17 гг. я провел три трёхдневных курса по TLA+, их прошли около 150 программистов, в основном работавших на Azure (облачная платформа Microsoft). Два менеджера из Azure сделали презентацию на семинаре в апреле 2018 года, в которой говорилось, что сложные системы требуют строгого математического мышления на каждом этапе их разработки. Они цитировали меня: «Продумывание задачи не гарантирует, что вы не совершите ошибок. Но если вы ее не продумываете, вы гарантированно сделаете ошибки». В презентации также говорилось о необходимости моделировать систему целиком, то есть всю вселенную, которая включает систему и ее среду. И здесь опять-таки на помощь приходит математика: код хорош для написания системы, но для ее среды необходимо нечто более простое и выразительное. В заключение было сказано, что нужно мыслить математически, и что многие программисты этого не делают.


Процитирую другого менеджера, который говорил о необходимости интегрировать TLA+ в культуру программирования. Этого он предлагал достичь, нанимая программистов с опытом работы в TLA+, интегрируя тренинги по TLA+ в курс адаптации новых программистов и в курс учебного лагеря Azure, и требуя, чтобы каждый анализ причин багов корректности в продакшне сопровождался спецификацией на TLA+. Последняя мера гарантирует, что человек понял первопричину бага. Наконец, автор также говорил о необходимости требовать спецификацию на TLA+ для гарантий, которые заявляет сервис. Сегодня Microsoft выпускает новую версию Cosmos DB, это глобальная база данных, от которой в значительной степени будет зависеть будущее всей компании. Насколько мне известно, этот проект пока что очень успешен. В его разработке использовался TLA+, и гарантии условий правильности, предоставляемые пользователям, сопровождаются в нем спецификациями на TLA+.


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


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


Напоминаю, что это перевод. Когда вы будете писать комментарии — помните, что автор их не прочитает. Если вам действительно хочется пообщаться с автором, то он будет на конференции Hydra 2019, которая пройдёт 11-12 июля 2019 года в Санкт-Петербурге. Билеты можно приобрести на официальном сайте.

Комментарии (31)


  1. Chuvi
    11.06.2019 12:50

    Примечание. Читать статью с андроидовского приложения в ночном режиме немного трудно.


    https://habrastorage.org/webt/4d/de/hq/4ddehqk_srwgnj-u6f1rzggz7ta.png


    https://habrastorage.org/webt/7x/di/ok/7xdioklcpvgeloo0thpbozn_kdy.png


    1. olegchir Автор
      11.06.2019 13:42

      С inline-формулами, наверное, ничего особо не сделаешь, это нужно как-то делать на уровне движка Хабра, так? Кастануть бы кого-нибудь вроде Boomburum


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


  1. andy_p
    11.06.2019 13:00

    Хм… В алгоритме Евклида вообще-то фигурирует деление с остатком, а не вычитание.
    Хотя с вычитанием тоже будет работать, но уж больно долго.


    1. gBear
      11.06.2019 13:50

      Хм… В алгоритме Евклида вообще-то фигурирует деление с остатком, а не вычитание.
      Ну да… именно поэтому, — «на наши деньги»- его оригинальное название значит что-то типа «пропорциональное вычитание». Если, конечно, мой склероз меня не обманывает. :-)


    1. Fedorchik
      12.06.2019 15:11

      С точки зрения вычислителя — одно и то же.


  1. Dima_Sharihin
    11.06.2019 15:28

    В чем принципиальное отличие TLA+ от Verilog или VHDL?


    1. aleks_raiden
      11.06.2019 17:46

      Хмм, это ведь языки для разработки аппаратных схем, а TLA+ — средство логической верификации/моделирования алгоритмов (которые потом да, могут воплощаться в конкретную реализацию в железе и моделироваться уже в Verilog/VHDL)


  1. Nookie-Grey
    11.06.2019 15:31

    Что-то меня всегда не улыбало смотреть на условия.
    Зачем проверять какое из чисел больше, когда можно найти разницу (меньшее число) и из суммы чисел вычесть разницу и поделить это на два (большее число)?
    x = abs(a - b);
    y = (a + b - x) / 2;


    1. mobi
      11.06.2019 16:01

      С точки зрения алгоритма, замена равнозначная. А с точки зрения практики применения — нет, т.к. в зависимости от реализации a+b может вызвать переполнение. Поэтому и начинаются упомянутые пляски поверх алгоритма:

      у M и N будет определенный тип, BigInteger или что-то в таком роде; нужно будет определить поведение программы в случае, если M и N неположительные; и так далее и тому подобное


    1. kaiu
      13.06.2019 19:20

      Даны два бесконечных числа:
      11110110101010101…
      11110010101110111…
      Какое из них больше легко найти, а время на поиск полной разницы составит бесконечность.


  1. firedragon
    11.06.2019 20:13

    Чтот не понятно, какие то пределы, вы бы попроще!


  1. qw1
    11.06.2019 20:56
    +1

    Похоже на туториал по рисованию совы.
    Сначала для примера рассмотрим алгоритма Евклида, а затем вы и сами легко поймёте, как это всё применяется на сложных распределённых и параллельных программах.


    1. dimm_ddr
      13.06.2019 13:49

      А так всегда — долго-долго мусолим концепцию на простом примере и переходим к следующей не задерживаясь на сложных практических случаях. Но у этого есть совершенно определенный смысл: понять концепцию гораздо проще если не приходится продираться через сложный алгоритм. С другой стороны, разбор чего-то сложного займет на порядок или два больше места — такой разбор просто не получится сделать в рамках доклада на конференции или статьи на хабре (или в аналогичном месте). Это можно сделать в учебнике или в серии статей конечно.


  1. arTk_ev
    11.06.2019 21:57
    +1

    ?-исчисления придуманы уже давно. Функциональные языки тоже. А машина состояний и логическое программирование — даже не тьюринг-полны и имеют предельно узкое применение.


  1. Gribs
    12.06.2019 02:43

    Примеры с рекуррентным алгоритмом это хорошо, но создается впечатление что TLA для них только и заточен. Вот например как на TLA выразить алгоритм, который в файл выводит Hello world? Как написать какой-нибудь fizzbuzz?


    1. soniq
      12.06.2019 06:53

      Наверное у них есть монады, что-то вроде Next = “fizz” + Next’


  1. vashu1
    12.06.2019 09:23
    +13

    Вы далеки от темы и вас пугают языки программирования? Предикаты и лямбда-функции решат вашу проблему.

    Они вас пугают куда больше? Не понимаю. Я 20 лет работаю профессором математики и меня они не пугают.


    1. Acuna
      13.06.2019 19:14

      Ну так языки надо учить, а новое учить тяжело) А математика уже знакома.


      1. vashu1
        14.06.2019 02:51

        Даже программисты часто считают что математика рулит. Что сейчас вот мы возьмем монады и забудем о всей низкоуровневой мелочи.

        На работе хаскель — каждую вторую программу приходится профилировать по памяти, для приемлемой скорости добавили битовые векторы, недавно был баг с переполнением переменной.

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


        1. Acuna
          14.06.2019 03:52

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


        1. worldmind
          14.06.2019 08:40

          На работе хаскель — каждую вторую программу приходится профилировать по памяти, для приемлемой скорости добавили битовые векторы, недавно был баг с переполнением переменной

          напишите статью об этом


          1. vashu1
            14.06.2019 12:33

            хех. как уволюсь — обязательно


  1. freecoder_xx
    12.06.2019 11:32
    +1

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

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


  1. tlbogdanov
    12.06.2019 12:50
    +1

    То, что я прочитал, похоже на глоток свежего воздуха. Спасибо!


  1. edo1h
    12.06.2019 14:05
    +3

    «Не используйте языки программирования, вот вам ещё один язык программирования»


    1. alsii
      12.06.2019 18:48
      +1

      Статья называется "Если вы не пишете программу...". Если вы пишете программу, вам нужна другая статья. Еще там сказано:


      В докладе пойдёт речь о формальных методах, применяемых в разработке сложных и критичных систем вроде космического зонда Rosetta или движков Amazon Web Services.

      Если разрабатываемая вами система не настолько сложна/критична вам опять нужна другая статья.


  1. uldashev
    12.06.2019 18:29
    +1

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


  1. third112
    12.06.2019 22:32

    Четкой разницы между алгоритмами и программами нет,

    ИМХО четкая разница в формуле Вирта:
    Алгоритмы + структуры данных = программы,
    Нпр.,
    у M и N будет определенный тип, BigInteger

    алгоритм Евклида м.б. реализован на языке, где нет типа BigInteger.
    А LaTeX — великая вещь! Респект.


  1. Kealon
    13.06.2019 08:14

    что-то мне напомнило анекдот про лису: «только бухгалтерии добавилось»


  1. perfect_genius
    13.06.2019 10:23

    «В каждой большой программе живет алгоритм, который пытается выбраться наружу»

    Скорее, каждая программа создана пытаться завершиться, но постоянно мешают циклы.


  1. Soarex16
    14.06.2019 02:56

    Было бы интересно узнать о производительности TLA+ и его сравнении с прологом. Или же в вопросах верификации производительность роли не имеет?