Files
paradigms-2026/lectures/README.md
me c4eaad130f
All checks were successful
Binary Search Test / test (push) Successful in 5s
update
2026-02-17 09:35:39 +03:00

293 lines
6.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Лекция 1. Программирование по контракту.
Мы видим следующий код:
```java
public class Magic {
int magic(int a, int n) {
int r = 1;
while (n != 0) {
if (n % 2 == 1) {
r *= a;
}
n /= 2;
a *= a;
}
return r;
}
}
```
Что это за код?
Как понять:
1. Пристально посмотреть.
2. Написать тесты.
* Это не поможет понять, работает ли код. Тесты могут только показать, что код **НЕ работает**.
3. Написать формальное доказательство.
Для последнего пункта нам понадобится *теорема о структурной декомпозиции*.
**Формулировка**: Любой код, который мы можем написать на каком-то языке, можно представить в виде замыкания следующих операций:
1. Ничего
2. Последовательное исполнение операций (последовательность действий)
3. Присваивание
4. Ветвление
5. Цикл `while`
С точки зрения теоремы, этих действий достаточно, чтобы написать любую содержательную программу.
Можно ли ввести такую конструкцию при помощи примитивов выше:
```java
try {
// some code here
} catch (...) {
// some code here
} finally {
// some code here
}
```
Давайте введем переменную `exitCode` и для каждого действия, в зависимости от того, успешно оно выполнилось или нет, будем обновлять `exitCode`.
```java
if (exitCode == 0) { // без ошибок
// делаем дальше
} else {
// ничего не делаем
}
```
Попытаемся этим воспользоваться.
Мы хотим наложить какие-то условия на нашу функцию, так что если эти условия на вход выполняются, то мы получим гарантированно правильное значение. Чтобы это доказать, нам помогут *тройки Хоара*.
Хоар придумал quick sort.
Тройка состоит из `P`, `C` и `Q`, где
* `P` - пред-условие
* `C` - код,
* `Q` - пост-условие
Если у нас есть код `C`, и мы выполняем какое-то пред-условие, то после исполнения кода, у нас будет выполнено какое-то пост-условие.
Для операции *ничего*:
```java
/*
Pred: Condition
Code: ;
Post: Condition
*/
```
То условие, которое было до того, как мы сделали *ничего*, останется.
Для *последовательности действий*:
```java
/*
Pred: P1
Code: ...
Post: Q1
Pred: P2
Code: ...
Post: Q2
*/
```
Таким образом из `Q1` должно следовать `P2`.
```java
/*
Pred: P1
Q1 -> P2
Post: Q2
*/
```
Для *присваивания*:
```java
/*
Pred: Q[x = expr]
Code: x = expr
Post: Q
*/
```
Например
```java
// Pred: (x = a + 2)[x = 6] -> a = 4
x = a + 2
// Post: x = 6
```
То есть только при `a == 4`, выполнится пост-условие.
Для операции `ветвление`:
```java
/*
// Pred: cond && P1 || ~cond && P2
if (cond) {
// Pred: P1
...
// Post: Q1
} else {
// Pred: P2
...
// Post: Q2
}
// Q1 -> Q && Q2 -> Q
// Post: Q
*/
```
Для цикла `while`:
```java
/*
// Pred: P = I
while (cond) {
// Pred: I
// Post: I (инвариант цикла)
}
// Post: Q = I
*/
```
Посмотрим на примере:
```java
// Pred: true
// Post: r = A' ^ N'
int magic(int a, int n) {
// A' = a, N' = n -- начальные значения
// Pred: A' == a && N' == n
int r = 1;
// Post: r == 1 && a ** n * r == A' ** N'
// Pred: a ** n * r == A' ** N'
// Inv: (a ** n) * r = A' ** N'
while (n != 0) {
// Pred: a ** n * r == A' ** N'
if (n % 2 == 1) {
// Pred: a ** n * r == A' ** N'
r = r * a;
// Post: a ** (n - 1) * r = A' ** N'
// Pred: a ** (n - 1) * r = A' ** N'
n = n - 1;
// Post: a ** n * r = A' ** N'
}
// Post: a ** n * r = A' ** N'
// Pred: a ** n * r = A' ** N'
n /= 2;
// Post: a ** (2 * n) * r = A' ** N'
// Pred: a ** (2 * n) * r = A' ** N'
a = a * a;
// Post: a ** n * r = A' ** N'
}
// Post: a ** n * r = A' ** N' && n == 0
// Pred: r = A' ** N'
return r;
// Post: r = A' ** N'
}
```
Мы формально доказали, что метод `magic()` возводит число `a` в степень `n`. Такая функция называется чистой, так как она не зависит от внешних переменных.
То, что мы написали, называется контракт. Участниками контракта являются *пользователь* и *разработчик*.
Мы, как разработчик, требуем пред-условие, и тогда можем гарантировать, что пост-условие будет выполняться.
`interface` в java -- частный случай контракта.
Это были случаи определения контракта для *чистых* функций. А как действовать в других случаях. Приведем пример
```java
int x;
// Pred:
// Post:
int add(int y) {
x = x + y;
return x;
}
```
Определим *модель* как некоторое состояние нашего класса.
```java
/*
Model: x (целое число)
*/
```
```java
int x;
// Pred: true
// Post: x = x' + y (x' -- старый x)
int add(int y) {
x = x + y;
return x;
}
```
Здесь контракт соблюдается.
А здесь:
```java
int x;
// Pred: true
// Post: x = x' + y (x' -- старый x)
int add(int y) {
x = x + y * 2;
return x / 2;
}
```
Контракт также соблюдается. То есть нам не важны детали реализации.
Можно даже сделать вот так:
```java
private int x = 10;
// Post: x = 0
void init() {
x = 1;
}
// Pred: true
// Post: x = x' + y
int add(int y) {
x = x + y * 2;
return (x - 1) / 2;
}
// Pred: true
// Post: R := x
int get() {
return (x - 1) / 2;
}
```