This commit is contained in:
292
lectures/README.md
Normal file
292
lectures/README.md
Normal file
@@ -0,0 +1,292 @@
|
||||
# Лекция 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;
|
||||
}
|
||||
```
|
||||
|
||||
19
lectures/lec1/Magic.java
Normal file
19
lectures/lec1/Magic.java
Normal file
@@ -0,0 +1,19 @@
|
||||
public class Magic {
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println(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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user