Полная по тьюрингу машина

Содержание

Тьюринг-полнота

Содержание

Введение [ править ]

Определение:
Вычислительное устройство является Тьюринг-эквивалентным (англ. Turing-equivalent), если оно может эмулировать машину Тьюринга.
Определение:
Задача называется Тьюринг-полной (англ. Turing-complete), если её можно решить, используя только машину Тьюринга или любую систему, являющуюся Тьюринг-эквивалентной.

Зачастую Тьюринг-эквивалентные языки программирования называют Тьюринг-полными.

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

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

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

Критерии Тьюринг-полноты [ править ]

Если на языке программирования можно реализовать машину Тьюринга, то такой язык Тьюринг-полон, и наоборот. Возможность реализации машины Тьюринга на конкретном языке программирования можно грубо описать как перечень требований, которым этот язык должен для этого удовлетворять:

Тьюринг-полнота и неполнота некоторых языков программирования [ править ]

Доказать Тьюринг-полноту языка программирования можно, предложив способ реализации машины Тьюринга на этом языке. Кроме того, можно предложить на нём интерпретатор Тьюринг-полного языка.

Assembly language [ править ]

Язык Ассемблера достаточно примитивен относительно языков программирования высокого уровня: он рассчитан на архитектуру с конечной памятью и работает с конечным набором регистров. Однако, не был бы он полным по Тьюрингу, не были бы Тьюринг-полны и любые высокоуровневые языки программирования.

Всё необходимое для машины Тьюринга на asm можно сделать примерно так:

И далее использовать инструкцию [math]\mathrm[/math] или ей подобную, чтобы выполнять определённую последовательность команд при определённом текущем значении, таким образом обеспечив ветвление.

Pascal [ править ]

C [ править ]

В языке C нет высокоуровневого понятия переменной (в смысле Паскаля), есть объекты (object), хранящиеся в памяти как последовательно расположенные байты,имеющие адрес (байты в свою очередь состоят из неадресуемых битов). Целые типы ограничены (конечное множество значений), указатель отождествляется с адресом, постулируется возможность хранить адрес в целочисленной переменной (int или long — зависит от реализации), откуда следует ограниченность множества значений указателей, а стало быть, и ограниченность адресного пространства C-машины. То есть язык C, как и язык ассемблера, ориентирован на архитектуру с конечной памятью. Файл не является типом данных языка C, в отличие от Паскаля. Это вещь из окружения, для работы с которой есть операции над потоками в виде набора библиотечных функций. Тип fpos_t, принятый в стандарте C для позиционирования файлов, постулируется как «отличный от массива тип данных (object type)». Следовательно, множество значений этого типа конечно, а значит, максимальная длина файла в языке C ограничена сверху.

SQL [ править ]

HTML [ править ]

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

Некоторые другие ЯП [ править ]

Название языка Год изобретения Парадигма Уровень Зависимость от архитектуры процессора Полнота по Тьюрингу
C 1972 Процедурный Низкий зав. от ISO Да
C++ 1983 Мультипарадигменный Высокий/Низкий Нет Да
Язык Ассемблера 1950 Полнофункциональный Низкий Да Да
SQL 1989 Декларативный Высокий Нет Нет
Haskell 1990 Функциональный Высокий Нет Да
HTML 1986 Декларативный Высокий Нет Нет
CSS 1996 Декларативный Высокий Нет Нет
Java 1995 Объектно-ориентированный Высокий Нет Да
JavaScript 1995 Объектно-ориентированный Высокий Нет Да
Python 1991 Объектно-ориентированный Высокий Нет Да
XML 1998 Декларативный Высокий Нет Нет
Brainfuck 1993 Эзотерический Низкий Да Да
Whitespace 2003 Эзотерический Низкий Да Да

Интересные случаи полноты по Тьюрингу [ править ]

Шаблоны C++ [ править ]

Java Generics [ править ]

URISC [ править ]

URISC (от англ. Ultimate RISC) — предельный случай процессора типа RISC (буквально: компьютер с предельно сокращённым набором инструкций), который умеет выполнять одну-единственную инструкцию. Обычно это «вычесть и пропустить следующую инструкцию, если вычитаемое было больше уменьшаемого» (англ. «reverse-subtract and skip if borrow»). Аналогичная концепция, основанная именно на «вычесть и перейти, если результат не положительный» (англ. «subtract and branch unless positive»), называется SUBLEQ.

URISC также известен в современной литературе как OISC (англ. One Instruction Set Computer) и является полным по Тьюрингу.

mov [ править ]

Источник

Наследие Тьюринга: машина, тест и полнота

anonymous

anonymous

content 4cb2f283d8c5d391611c5977b8d802bf

Что такое машина Тьюринга

Для того, чтобы представить простейшую машину Тьюринга, взглянем на её художественную реализацию:

content cc88cc94b5646f37a3f2f60356278c52

Это бесконечная лента, не имеющая ни начала, ни конца, поделённая на ячейки. Для работы с ней мы используем некое управляющее устройство (автомат), для визуализации выбрана каретка. В каждый момент времени она имеет состояние qj и считывает содержимое ячейки ai. О том, что происходит в остальной части ленты, каретка не знает, соответственно оперировать она может только текущими данными. Всего возможно три типа действий, зависящий от этой композиции:

Что-то похожее реализовано в электронных таблицах: там тоже условно неограниченное поле, вы можете изменить значение ячейки, изменить действие или перейти на другую ячейку.

Создадим таблицу для реализации алгоритма Тьюринга:

content 6ac4957f88530b1046b93a2d162db8e8

Символами _Л, _П, _Н обозначим направление движения автомата – соответственно сдвиг «влево», «вправо» или неподвижное положение.

Пусть наша лента выглядит так:

content 4a3f26d3e780a8d5ac1530f5bf8b90eb

Начальное положение – крайняя правая ячейка, остановка – в пустой клетке. Догадались как она будет выглядеть после завершения алгоритма?

content abdf19c5afa651542b8320c3a4bdae21

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

Зачем это программисту

Машина Тьюринга позволяет размять мозги и взглянуть на решение задачи иначе. В конечном счёте, с той же целью следует познакомиться с:

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

Полнота по Тьюрингу

Ещё один важный вопрос, связанный с именем известного математика. На форумах и в статьях вы неоднократно могли видеть выражение «полный\не полный язык программирования по Тьюрингу». Ответ на вопрос «что это означает?» возвращает нас к описанной выше теории. Как уже было сказано, машина Тьюринга позволяет выполнить любое преобразование, соответственно, вы можете реализовать на ней абсолютно любой алгоритм или функцию. То же самое относится и к языкам. Если с его помощью вы можете реализовать любой заданный алгоритм – он тьюринг-полный. Если в дело вступают ограничения синтаксиса или любые физические – не полный.

Тест по Тьюрингу

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

Такой тест на долгие годы предопределил развитие ИИ – программы вроде Элизы или PARRY строились именно на копировании человеческого поведения машиной. Уже позднее, когда стало понятно, что путь тупиковый, вектор развития был сдвинут в сторону изучения механизмов интеллекта. Однако до сих пор тема «способна ли мыслить машина» лежит в основе многих тестов, романов и кинофильмов.

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

Если вы не учились профессии программиста в вузе или не ходили в специальную школу, то, возможно «Машина Тьюринга» для вас просто дешифратор из курса истории или фильма «Игра в имитацию». В действительности всё немного сложнее, любому уважающему себя программисту необходимо знать и понимать, что это такое.

content 4cb2f283d8c5d391611c5977b8d802bf

Что такое машина Тьюринга

Для того, чтобы представить простейшую машину Тьюринга, взглянем на её художественную реализацию:

content cc88cc94b5646f37a3f2f60356278c52

Это бесконечная лента, не имеющая ни начала, ни конца, поделённая на ячейки. Для работы с ней мы используем некое управляющее устройство (автомат), для визуализации выбрана каретка. В каждый момент времени она имеет состояние qj и считывает содержимое ячейки ai. О том, что происходит в остальной части ленты, каретка не знает, соответственно оперировать она может только текущими данными. Всего возможно три типа действий, зависящий от этой композиции:

Что-то похожее реализовано в электронных таблицах: там тоже условно неограниченное поле, вы можете изменить значение ячейки, изменить действие или перейти на другую ячейку.

Создадим таблицу для реализации алгоритма Тьюринга:

content 6ac4957f88530b1046b93a2d162db8e8

Символами _Л, _П, _Н обозначим направление движения автомата – соответственно сдвиг «влево», «вправо» или неподвижное положение.

Пусть наша лента выглядит так:

content 4a3f26d3e780a8d5ac1530f5bf8b90eb

Начальное положение – крайняя правая ячейка, остановка – в пустой клетке. Догадались как она будет выглядеть после завершения алгоритма?

content abdf19c5afa651542b8320c3a4bdae21

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

Зачем это программисту

Машина Тьюринга позволяет размять мозги и взглянуть на решение задачи иначе. В конечном счёте, с той же целью следует познакомиться с:

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

Полнота по Тьюрингу

Ещё один важный вопрос, связанный с именем известного математика. На форумах и в статьях вы неоднократно могли видеть выражение «полный\не полный язык программирования по Тьюрингу». Ответ на вопрос «что это означает?» возвращает нас к описанной выше теории. Как уже было сказано, машина Тьюринга позволяет выполнить любое преобразование, соответственно, вы можете реализовать на ней абсолютно любой алгоритм или функцию. То же самое относится и к языкам. Если с его помощью вы можете реализовать любой заданный алгоритм – он тьюринг-полный. Если в дело вступают ограничения синтаксиса или любые физические – не полный.

Тест по Тьюрингу

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

Такой тест на долгие годы предопределил развитие ИИ – программы вроде Элизы или PARRY строились именно на копировании человеческого поведения машиной. Уже позднее, когда стало понятно, что путь тупиковый, вектор развития был сдвинут в сторону изучения механизмов интеллекта. Однако до сих пор тема «способна ли мыслить машина» лежит в основе многих тестов, романов и кинофильмов.

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

Источник

Тьюринг-полнота Generic типов Java

Периодически на хабре можно встретить статьи о том, какие невероятные вещи можно сделать на шаблонах C++: конечные автоматы, лямбда-исчисление, машина Тьюринга и многое другое.

Параметризованные типы в Java традиционно считаются лишь пародией на шаблоны C++ (несмотря на то, что их даже сравнивать как-то некорректно), и причины этого несложно понять. Тем не менее не всё так плохо, и компилятор Java можно заставить производить во время проверки типов любые вычисления, лишь бы хватило оперативной памяти. Конкретный способ это сделать был описан в ноябре 2016-го года в этой прекрасной публикации. Его я и хотел бы объяснить.

Для затравки приведу следующий код. Корректен ли он? Предлагаю скомпилировать и проверить, угадали ли вы результат.

Компилятор выбросит java.lang.StackOverflowError независимо от размера стэка.

Разберёмся, почему компилятор ведёт себя именно так (я бы не назвал это багом), как понимание данных причин может быть полезно и причём тут машина Тьюринга.

1. О ковариантности и контравариантности

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

Сосредоточимся на контравариантных типах.

2. Формализация подмножества контравариантных типов

Итак, определим, какие именно типы в Java нас интересуют. Введём понятие индуктивно:

Теперь можно наконец ввести отношение является подтипом «d5c9e2a8f96ff05c990645bbff479f6c«.

В этом месте появляется существенное ограничение. Чтобы отношение d5c9e2a8f96ff05c990645bbff479f6cсовпадало со стандартным способом определения подтипов в Java, необходимо, чтобы в пункте 2 значение n было нечётным.

Полного формального доказательства я приводить не буду, оно было бы неуместно длинным. Правильнее будет рассмотреть ситуацию на примерах.

n=3:
interface S extends C1 >> <>
Докажем, что верно 343f3e232202205a84b15bf337a3a5e0:

Given a generic type declaration d85d9f67272e771dd5f690b12c220579$» data-tex=»inline»/> (n > 0), the direct supertypes of the parameterized type 637eb1322b833db6001e44f29a5364f7$» data-tex=»inline»/> where at least one of the b658686f89fd6333b25aba0dac60881ais a wildcard type argument, are the direct supertypes of the parameterized type 749bbe86e6bc0994418a9c3c502a9aea$» data-tex=»inline»/> which is the result of applying capture conversion to 637eb1322b833db6001e44f29a5364f7$» data-tex=»inline»/> (§5.1.10)

n=2:
interface S extends C1 > <>
Предположим, что 034dcb872bb3554fd2a3a55af2359105:

Случаи n>2 рассматриваются аналогичным образом.

Теперь можно сформулировать следующую теорему, которая позже будет доказана:

Теорема 1
Не существует алгоритма, который для любых двух заданных типов b63390770f5587edf3152c5a3993cf84и 14a691fced38af8858e06f2a55621b76смог бы определить, является ли верным высказывание 505cacd525f5c16bc415766d9c948773.

Другими словами, в общем случае в Java невозможно определить, является ли один тип подтипом другого.

3. Что будем понимать под машиной Тьюринга

Машина Тьюринга c3609355f164a03db73ff187d34111ce— это пятёрка f70cf238f4a46be51e079e85326fb78d, где eb904b678aba503fe98f5759398e5ee0— это конечное множество состояний, f08b752f2e657af1ee4e256ace96c11a— это начальное состояние, c7a8e7b2ff73fa88d378b409fd94cacc— это конечное состояние, 885292cd7b9ccfacd4aa4d5252708efa— это конечный алфавит и 0ce0fd111d66edf992e9ffe8ebba636c— это функция перехода. 66ab320b4761b438083a05fcbc67949f— это дополнительный символ, не содержащийся в 885292cd7b9ccfacd4aa4d5252708efa.

Конфигурация машины Тьюринга — это четвёрка 915c6395ad512f3ceceaa730c7c10b77, в которой 49faf6bfc2d659525462e3cf643d6176— это текущее состояние, f347da50ba6ec553307ff809d0d60d7e— это левая часть ленты, 4b967555db48614b69c1aac08c07230e— это текущий символ и 59ef0ea016e18b4b94e03dbdf7e6d0c8— это правая часть ленты.

Шаг выполнения машины c3609355f164a03db73ff187d34111ceотображает множество конфигураций само в себя следующим образом:

Так же учитываются граничные случаи (символ 69c5575e9b1b42051069fb6122976644здесь означает пустую строку). Они показывают, что по достижении конца строки (слева или справа) к ней автоматически дописывается символ 66ab320b4761b438083a05fcbc67949f:

Запуск машины на входе 4a33fa9f62b40b5d0bff7f255dd11de9— это последовательность шагов выполнения, начинающаяся с конфигурации 4b66c2ea34ae133403e03c53275544fe. Если c3609355f164a03db73ff187d34111ceдостигает bb1ae1730156d60c122849c034c48469, то мы говорим, что машина c3609355f164a03db73ff187d34111ceзавершается (halts) на входе 4a33fa9f62b40b5d0bff7f255dd11de9. Если же функция перехода не позволяет сделать следующий шаг выполнения, будем считать, что машина c3609355f164a03db73ff187d34111ceломается на входе 4a33fa9f62b40b5d0bff7f255dd11de9.

4. Subtyping Machines

Начнём связывать воедино имеющиеся у нас понятия. Цель — сопоставить шаги выполнения машины Тьюринга с процессом проверки типов в Java. Положим, что у нас есть такой запрос:

b247067c93ee1b177a1cd2de640f5b68

Это утверждение, истинность или ложность которого предлагается доказать. Для удобства введём две альтернативные формы записи:

b769fbb30570b8d646ed86745e149b3f

cdd5f502cae6fc1fe9e8258a36cb78cf— это специальная конфигурация, называемая завершающей (halting).
Отметим, что имея правило наследования fc53c26e589170ad4da13b2e18081851и подставляя тип 194c2de704b63f16c9585cbce920dba4, мы получаем следующее правило выполнения:

1c3730df55eeec841f986e9254fe605a

Так же в силу контравариантности используемых типов справедливо такое правило:

5196ce5d7da94805def0b31096250352

Используя введённые правила, можно понять, что происходит при проверке типов в примере из начала статьи (они полностью соответствуют алгоритму проверки типов Java на выделенном нами подмножестве контравариантных типов). В нём задано отношение наследования 1f3e3047fdb23049078e21b8358a7c80и запрос e0b0fd83361a777de45bf7aa4a340a31.
Цепочка правил выполнения будет следующей:

025a31f2abb05e14e52259028d3e4420

Как видно, проверка типов зацикливается, этим и обусловлено переполнение стэка во время компиляции. В общем виде описанный процесс можно выразить таким образом:

Выражение b953342ce1a5439e1be8e3392453d44cистинно тогда и только тогда, когда существует завершающийся процесс выполнения 55d995042fcbf24313b08436272d1054.

Пример посложнее

Рассмотрим следующую таблицу классов:

d78ada6b8bd13667d72301972c068d15

9a90195e2c21cd7313bbf183a949fb18

fda5f846a19414f31b3abe21920d431c

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

5. Построение машины Тьюринга

Имея машину Тьюринга c3609355f164a03db73ff187d34111ceи входную ленту 4a33fa9f62b40b5d0bff7f255dd11de9, построим типы f94a01496c4cb2d41aa02716572ac992и 3013d17e0af4b2af14bfb0b9938e9794таким образом, что 04a1b176c449b40b804d843cef049b04тогда и только тогда, когда c3609355f164a03db73ff187d34111ceостанавливается на входе 4a33fa9f62b40b5d0bff7f255dd11de9.
Для каждого состояния 29ec2ecdf2111b8967e35577d82c302cвведём 6 классов: 222a4bc0cde6abea8205539550a3c283, 889373a1f10591cdaf548a428920557b, e4486f46a947c5f2e116c9040ad1adcd, ec3f2076f5528e2b02860727bde8ee83, 5166484d4dd2444a8e84cf5a07bdba5dи ce7abb6e1f0e4c85002d491c1f894e54; для каждого символа 9cbb2be6178ac8d1899051810f7dd9a2построим класс aaad65f086abced1d20e15acaaeb8654. Символ 66ab320b4761b438083a05fcbc67949fиз определения машины Тьюринга для удобства будем записывать как b109675ad45b436063092010dfa683cc, ему будет соответствовать класс 6d37d97d22cb03f3146eb5ad39cb5bf6. Так же дополнительно введём 4 класса: 1e80c3b3087c0a57b68ad11261a9ec2b, d4f55a37fb733b176d0ef014591e1b35, f533f9a2558e1c7c03ed3a15dcc386d2и 023e53e65c0e8cb9b6bc407b8b1fdea3.

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

222a4bc0cde6abea8205539550a3c283и 889373a1f10591cdaf548a428920557b— это по сути типы dc75d148d341a76bd6758819901647c3и d327870b6ef7a8fe82022618b41169faиз последнего примера. Большую часть времени они блуждают (wander) вдоль ленты. Смысл типов 5166484d4dd2444a8e84cf5a07bdba5dи ce7abb6e1f0e4c85002d491c1f894e54тоже сохраняется: они нужны для разворота на конце ленты. Единственное исключение — момент, когда ca605b714fbe3f5392488edb54221fe0( 6d6a4f78fbacd6edecc018ce8ad3e364это a5a4e0afaec84939dbfda220172b2be0или b81a7c1e9676b36cc02ddeea5d5f6e51) встречается с соответствующим ему 3cc905ef18003757cb1514747b372ff3: он при этом «превращается» в 1cfd9ddbf8774b166b841ad1e09250f4. 69f401eeeb8af9100b6ba696d495dbee— класс для завершающего состояния, обрабатываемый особым образом.
Полное описание выглядит так:

1515d89500a466be80887d3fbb677337

e4486f46a947c5f2e116c9040ad1adcdи ec3f2076f5528e2b02860727bde8ee83указывают машине, что пришло время для очередного шага выполнения. Соответственно, для каждого правила 5f30611c730410df255041e5a8113abaв машине Тьюринга c3609355f164a03db73ff187d34111ceмы строим специальное отношение наследования. При этом не забываем об особых правилах работы с классом 6d37d97d22cb03f3146eb5ad39cb5bf6:

3ce37341cb1b9c9386a412d4e79a709d

b34ab4d4a3c3c463fb7dce077830d1de

Данные отношения выглядят довольно-таки хитро, тем не менее в них можно проследить ряд закономерностей. Механику работы каждого я разбирать не буду, рассмотрим лишь часть.
830030fa964685aeb4f94bd192e74a02:

897d1ffa36c443e8e83b672a1b146201

63abff059d4011ee3e0f28d0e23d7bc8:

a126def2e4f60b63545d57e328b46fa0

510f86b2116d0921d88cc668c9533f18:

8b8a25ff5b76e3441bb00741499e21ee

Остальные 9 случаев расписываются аналогичным образом.

6. Fluent interface

Вернёмся в реальный мир. Мы же про Java говорим, а значит наши рассуждения должны в итоге привести к написанию какого-либо кода, желательно полезного.
В мире ООП есть такое понятие, как текучий интерфейс. Вы, возможно, никогда не встречали этого названия, но наверняка видели его реализацию в коде. По сути это просто каскадный вызов методов, использование которого нацелено на повышение читаемости, ну или ещё чего нибудь. Выглядит вот так:

Для этого напишем следующий класс:

Стоит понимать, что тут я описываю только сигнатуру, а не реализации соответствующих методов. О реализации будет позже.

полностью повторяет левую часть требуемого запроса. То есть, следующая строка кода на самом деле запустит требуемую машину Тьюринга во время работы алгоритма проверки типов:

7. Безопасный Builder

Итак, я хочу иметь следующий код, который мог бы валидироваться компилятором:

Требования будут самыми простыми — чтобы оба поля были инициализированы, причём именно в таком порядке и не больше одного раза (я прекрасно понимаю, что такую простую задачу можно решить более подходящими методами; пример с грамматикой посложнее будет позже).

3263d5fee0143d95723de37e769cdb1e
ca1797bf83ae757fa04ec6d3b1737f3e
d02ffa3bc82b00d10b6b720c22201d2d
561ef9a27c0944de79d6673d66e6420a

Довольно-таки прямолинейно. Машина остановится только на входе 65ab8b0d97b17ed4ccf8dc958efea064.

Классы User и UserBuilder реализованы следующим образом (осталось только заполнить многоточия):

Осталось только обеспечить требуемое предположение;

Целиком пример можно найти на гитхабе.

Чтобы не быть голословным, далее я привожу пример машины Тьюринга посложнее. Такая машина, грубо говоря, проверяет парность скобок, т.е. выражение ()(()()) является валидным, а выражение ()())(() — невалидным. Только открывающая и закрывающая скобки заменены символами A и B ( x — дополнительный символ, означающий, что скобка на этом месте уже проверена).
Функция перехода будет следующей:

6983522cca611ca5263e7fbf6e411705

235ad5132e3e87a823df0d7314651e87
069a7691a8e0ada7329ad1469f6c5fd2
a673202c76fbf5fa0f1c6acd241c58cf
0cd15e9e8602e7516ca5546e204c76cb

ef2e1e5cbfb56f9da5a9dc8b822d9395
33dd24b300de031ad2ce8d3a321d8c80
30da37794bde93f0f48b58b6732c76fa

7d7867d4c77a0d3b48abc3ac99554aa6
9f9fc149f0337ae5af479b655cbb1147

Поиграться с данной машиной, или погенерировать свои, можно всё там же на гитхабе.
Да, генератор кода по описанию машины Тьюринга присутствует.

Для данной грамматики следующий код спокойно скомпилируется:

а вот этот уже нет:

8. Заключение

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

Но можно — не значит нужно.
Во-первых, слишком много дополнительно объявленных классов, да и сигнатуры методов класса Builder выглядят очень сложно.
Во-вторых, компилятору понадобится гораздо больше памяти, чтобы не падать на сложных выражениях. В первую очередь — гораздо более глубокий стэк.
В-третьих, сложная диагностика ошибок. Типичный вывод компилятора выглядит как-то так (для краткости стёр названия пакетов):

Уровень информативности нулевой. Если хочется найти ошибку, то остаётся только два варианта:

Тогда зачем это всё? Ответов, опять же, несколько:

Разработчики Kotlin, к примеру, знали, что в Java легко составить типы, проверка которых приведёт к экспоненциально долгому времени компиляции, и ввели т.н. Non-Expansive Inheritance Restriction. Поясню на минимальном примере:

Цель проста — избежать «слишком сложных» типов, тем самым гарантируя высокую скорость компиляции. Из-за данного ограничения описанный способ запуска машин Тьюринга на Kotlin реализовать невозможно. В таком случае есть шанс, что проверка типов в Kotlin гарантированно завершается за конечное время.

С другой стороны, если все интерфейсы для машины Тьюринга объявить в Java-файлах, то компилятор Kotlin сделает ровно то же, что и компилятор Java — запустит соответствующую машину. Так что если учесть, что Kotlin используется не в изоляции от Java, то можно заключить, что и его система типов полна по Тьюрингу.

Источник

Оцените статью
AvtoRazbor.top - все самое важное о вашем авто