В 1936 году ученый Алан Тьюринг в своей публикации “On Computable Numbers, With An Application to Entscheidungsproblem” описывает использование универсальной вычислительной машины которая смогла бы поставить точку в вопросе проблемы разрешимости в математике. По итогу он приходит к выводу что такая машина ничего бы не смогла решить корректно, если бы результат ее работы инвертировали и зациклили бы на саму себя. Получается что *идеальный* антивирус невозможно создать, *идеальный* плиткоукладчик тоже, программу которая подсказывает идеальные фразы для твоего краша и т.д. Парадокс-с!
Однако данную универсальную вычислительную машину можно использовать для реализации любого алгоритма, чем и воспользовалась разведка Британии, взяв Тьюринга на работу и разрешив создать “Bombe” машину для дешифровки немецких сообщений во время второй мировой войны.
Далее приводится ООП моделирование одноленточного вычислителя на языке Dart, с опорой на оригинальный документ.
Машина Тьюринга состоит из пленки, разбитой на секции, в каждой секции находится символ, символы можно считывать или записывать. Пример класса пленки:
class MapInfiniteTape implements InfiniteTape {
final _map = Map<int, String>();
String read({required int at}) {
return _map[at] ?? "";
}
void write({required String symbol, required int at}) {
_map[at] = symbol;
}
}
Также существует “сканирующий квадрат”, он может перемещаться по пленке, считывать или записывать информацию, на современном языке – магнитная головка. Пример класса магнитной головки:
class TapeHead {
int _index = 0;
InfiniteTape _infiniteTape;
TapeHead(this._infiniteTape) {}
String next() {
_index += 1;
move(to: _index);
final output = read();
return output;
}
String previous() {
_index -= 1;
move(to: _index);
final output = read();
return output;
}
void move({required int to}) {
this._index = to;
}
String read() {
return _infiniteTape.read(at: this._index);
}
void write(String symbol) {
_infiniteTape.write(symbol: symbol, at: this._index);
}
int index() {
return _index;
}
}
Машина содержит “m-конфигурации” по которым может решать что делать дальше. На современном языке – состояния и обработчики состояний. Пример обработчика состояний:
class FiniteStateControl {
FiniteStateControlDelegate? delegate = null;
void handle({required String symbol}) {
if (symbol == OPCODE_PRINT) {
final argument = delegate?.nextSymbol();
print(argument);
}
else if (symbol == OPCODE_GENERATE_RANDOM_NUMBER_FROM_ZERO_TO_AND_WRITE_AFTER) {
final to = int.tryParse(delegate!.nextSymbol())!;
final value = new Random().nextInt(to);
delegate!.nextSymbol();
delegate!.write(value.toString());
}
else if (symbol == OPCODE_INPUT_TO_NEXT) {
final input = stdin.readLineSync()!;
delegate?.nextSymbol();
delegate?.write(input);
}
else if (symbol == OPCODE_COPY_FROM_TO) {
final currentIndex = delegate!.index();
и т.д.
После этого нужно создать “конфигурации”, на современном языке это коды операций (опкоды), их обработчики. Пример опкодов:
const OPCODE_STOP = "stop";
const OPCODE_PRINT = "print";
const OPCODE_INCREMENT_NEXT = "increment next";
const OPCODE_DECREMENT_NEXT = "decrement next";
const OPCODE_IF_PREVIOUS_NOT_EQUAL = "if previous not equal";
const OPCODE_MOVE_TO_INDEX = "move to index";
const OPCODE_COPY_FROM_TO = "copy from index to index";
const OPCODE_INPUT_TO_NEXT = "input to next";
const OPCODE_GENERATE_RANDOM_NUMBER_FROM_ZERO_TO_AND_WRITE_AFTER = "generate random number from zero to next and write after";
Не забудьте создать опкод и обработчик останова, иначе не сможете доказать либо не доказать (sic!) проблему разрешения.
Теперь, используя паттерн “медиатор”, соединяем все классы в классе Машине Тьюринга, создаем экземпляр класса, записываем через магнитофон на пленку программы, загружаем кассету и можно пользоваться!
Лично для меня остался интересен вопрос, что было первично – создание универсального вычислителя или доказательство “Entscheidungsproblem” в результате которого, как побочный продукт, появился вычислитель.
Кассеты
Развлечения ради я записал несколько кассет-программ для своего варианты машины.
Hello World
print
hello world
stop
Считаем до 16-ти
increment next
0
if previous not equal
16
copy from index to index
1
8
print
?
move to index
0
else
copy from index to index
1
16
print
?
print
Finished!
stop
Самой интересной задачей было написание Quine программы, которая печатает свой исходный код, для одноленточной машины. Первые 8 часов мне казалось что эта задача не решаема с таким малым количеством опкодов, однако всего через 16 часов оказалось что я был не прав.
Реализация и примеры кассет, источники ниже.
Ссылки
https://gitlab.com/demensdeum/turing-machine
Источники
https://www.astro.puc.cl/~rparra/tools/PAPERS/turing_1936.pdf
https://kpolyakov.spb.ru/prog/turing.htm
https://www.youtube.com/watch?v=dNRDvLACg5Q
https://www.youtube.com/watch?v=jP3ceURvIYc
https://www.youtube.com/watch?v=9QCJj5QzETI
https://www.youtube.com/watch?v=HeQX2HjkcNo&t=0s