├── .gitignore ├── CITATION.bib ├── README.md ├── motivation ├── dynamic │ ├── inf_sub_pydantic.py │ ├── inf_sub_pytypes.py │ ├── inf_sub_typeguard.py │ └── inf_sub_typical.py └── static │ ├── .pyre_configuration │ ├── inf_sub_static.py │ └── pyright_unsound.py └── typing_machines ├── __init__.py ├── abstract_machines ├── __init__.py └── turing_machine.py ├── app.py ├── compilers ├── __init__.py ├── compiler_g.py └── compiler_r.py ├── examples ├── __init__.py └── machines.py └── experiment ├── __init__.py └── stack_size_experiment.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Python template 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | env/ 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .coverage 43 | .coverage.* 44 | .cache 45 | nosetests.xml 46 | coverage.xml 47 | *,cover 48 | .hypothesis/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | 58 | # Flask stuff: 59 | instance/ 60 | .webassets-cache 61 | 62 | # Scrapy stuff: 63 | .scrapy 64 | 65 | # Sphinx documentation 66 | docs/_build/ 67 | 68 | # PyBuilder 69 | target/ 70 | 71 | # IPython Notebook 72 | .ipynb_checkpoints 73 | 74 | # pyenv 75 | .python-version 76 | 77 | # celery beat schedule file 78 | celerybeat-schedule 79 | 80 | # dotenv 81 | .env 82 | 83 | # virtualenv 84 | venv/ 85 | ENV/ 86 | 87 | # Spyder project settings 88 | .spyderproject 89 | 90 | # Rope project settings 91 | .ropeproject 92 | ### VirtualEnv template 93 | # Virtualenv 94 | # http://iamzed.com/2009/05/07/a-primer-on-virtualenv/ 95 | [Bb]in 96 | [Ii]nclude 97 | [Ll]ib 98 | [Ll]ib64 99 | [Ll]ocal 100 | [Ss]cripts 101 | pyvenv.cfg 102 | .venv 103 | pip-selfcheck.json 104 | 105 | ### JetBrains template 106 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 107 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 108 | 109 | # User-specific stuff 110 | .idea/**/workspace.xml 111 | .idea/**/tasks.xml 112 | .idea/**/usage.statistics.xml 113 | .idea/**/dictionaries 114 | .idea/**/shelf 115 | 116 | # AWS User-specific 117 | .idea/**/aws.xml 118 | 119 | # Generated files 120 | .idea/**/contentModel.xml 121 | 122 | # Sensitive or high-churn files 123 | .idea/**/dataSources/ 124 | .idea/**/dataSources.ids 125 | .idea/**/dataSources.local.xml 126 | .idea/**/sqlDataSources.xml 127 | .idea/**/dynamic.xml 128 | .idea/**/uiDesigner.xml 129 | .idea/**/dbnavigator.xml 130 | 131 | # Gradle 132 | .idea/**/gradle.xml 133 | .idea/**/libraries 134 | 135 | # Gradle and Maven with auto-import 136 | # When using Gradle or Maven with auto-import, you should exclude module files, 137 | # since they will be recreated, and may cause churn. Uncomment if using 138 | # auto-import. 139 | # .idea/artifacts 140 | # .idea/compiler.xml 141 | # .idea/jarRepositories.xml 142 | # .idea/modules.xml 143 | # .idea/*.iml 144 | # .idea/modules 145 | # *.iml 146 | # *.ipr 147 | 148 | # CMake 149 | cmake-build-*/ 150 | 151 | # Mongo Explorer plugin 152 | .idea/**/mongoSettings.xml 153 | 154 | # File-based project format 155 | *.iws 156 | 157 | # IntelliJ 158 | out/ 159 | 160 | # mpeltonen/sbt-idea plugin 161 | .idea_modules/ 162 | 163 | # JIRA plugin 164 | atlassian-ide-plugin.xml 165 | 166 | # Cursive Clojure plugin 167 | .idea/replstate.xml 168 | 169 | # SonarLint plugin 170 | .idea/sonarlint/ 171 | 172 | # Crashlytics plugin (for Android Studio and IntelliJ) 173 | com_crashlytics_export_strings.xml 174 | crashlytics.properties 175 | crashlytics-build.properties 176 | fabric.properties 177 | 178 | # Editor-based Rest Client 179 | .idea/httpRequests 180 | 181 | # Android studio 3.1+ serialized cache file 182 | .idea/caches/build_file_checksums.ser 183 | 184 | # idea folder, uncomment if you don't need it 185 | .idea 186 | 187 | typeshed-main 188 | .mypy_cache 189 | .pyre 190 | -------------------------------------------------------------------------------- /CITATION.bib: -------------------------------------------------------------------------------- 1 | @misc{https://doi.org/10.48550/arxiv.2208.14755, 2 | doi = {10.48550/ARXIV.2208.14755}, 3 | url = {https://arxiv.org/abs/2208.14755}, 4 | author = {Roth, Ori}, 5 | keywords = {Programming Languages (cs.PL), FOS: Computer and information sciences, FOS: Computer and information sciences}, 6 | title = {Python Type Hints are Turing Complete}, 7 | publisher = {arXiv}, 8 | year = {2022}, 9 | copyright = {Creative Commons Attribution 4.0 International} 10 | } 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Python Type Hints are Turing Complete 2 | 3 | [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.7004898.svg)](https://doi.org/10.5281/zenodo.7004898) 4 | 5 | We present a reduction from Turing machines to Python type hints. The Turing machine accepts its input if and only if 6 | the Python program is correctly typed. 7 | 8 | ## How did it happen? 9 | 10 | [Python enhancement proposal (PEP) 484](https://peps.python.org/pep-0484/) 11 | added optional type hints to the Python programming language. One of the features of the proposed type system is 12 | [nominal subtyping with variance](https://peps.python.org/pep-0484/#covariance-and-contravariance). Radu Grigore showed 13 | that [this particular form of subtyping is Turing complete](https://arxiv.org/abs/1605.05274) 14 | by describing a reduction from Turing machines to class tables. This project applies Radu's construction with Python 15 | type hints. 16 | 17 | ## How can I try it? 18 | 19 | Here is one possible script (also found in `typing_machines/app.py`): 20 | 21 | ```python 22 | from typing_machines.app import * # import application 23 | with open("example.py", "w") as python_file: # write palindromes machine and input "abbabba" 24 | python_file.write(encode(Algorithm.Grigore, palindromes, "abbabba")) 25 | sleep(1) # wait for write operation 26 | with Popen(["mypy", "example.py"]) as p: # run mypy 27 | retcode = p.wait(timeout=10) 28 | assert retcode == 0 # compiles successfully, "abbabba" is a palindrome 29 | with open("example.py", "w") as python_file: # write palindromes machine and input "abbbaba" 30 | python_file.write(encode(Algorithm.Grigore, palindromes, "abbbaba")) 31 | sleep(1) # wait for write operation 32 | with Popen(["mypy", "example.py"]) as p: # run mypy 33 | retcode = p.wait(timeout=10) 34 | assert retcode != 0 # does not compile, "abbbaba" is not a palindrome 35 | ``` 36 | 37 | The `palindromes` Turing machine is defined in `typing_machines/examples/machines.py`. You can add new machines in this 38 | file. 39 | 40 | ## Wait, so `mypy` can get into an infinite loop? 41 | 42 | Kind of. As with many other compilers, the subtyping algorithm implemented in `mypy` is recursive, so, recursion + 43 | infinite loop = *stack overflow*. In fact, even this simple program makes `mypy` throw a segmentation fault: 44 | 45 | ```python 46 | from typing import TypeVar, Generic 47 | T = TypeVar("T", contravariant=True) 48 | class N(Generic[T]): ... 49 | class C(N[N["C"]]): ... 50 | _: N[C] = C() 51 | ``` 52 | 53 | This is *not* a `mypy` bug. The problem of verifying Python type hints is undecidable, so `mypy` getting stuck for 54 | certain programs is unavoidable. 55 | 56 | ## What's new? 57 | 58 | We introduce an alternative construction that is supposed to compile much faster for large inputs. You can try the new 59 | construction by using `Algorithm.Roth` instead of 60 | `Algorithm.Grigore` in the script above. 61 | 62 | ## Is my type checker in danger? 63 | 64 | We simulate Turing machines and infinite loops at the type level using contravariant type parameters. Thus, your type 65 | checker can get into an infinite loop using our methods only if it supports variance as described in PEP 484. 66 | 67 | | Type Checker | Discipline | Supports Variance? | 68 | |-------------------|------------|--------------------------| 69 | | Mypy 0.991 | static |
| 70 | | Pyre 0.9.17 | static |
| 71 | | Pyright 1.1.279 | static |
| 72 | | Pytype 2022.11.10 | static |
| 73 | | Pyanalyze 0.8.0 | static |
| 74 | | Pydantic 1.10.2 | dynamic |
| 75 | | Pytypes 1.0b10 | dynamic |
| 76 | | Typeguard 2.13.3 | dynamic |
| 77 | | Typical 2.8.0 | dynamic |
| 78 | 79 | The programs used to obtain these results are found in `motivation/static` and 80 | `motivation/dynamic`. 81 | 82 | Note that Pyright is *unsound*, which means that it reports errors for 83 | correctly-typed programs, so its variance support is only partial. 84 | For example, the code in `motivation/static/pyright_unsound.py` is correctly 85 | typed, Mypy and Pyre report no error when checking the file, but Pyright does 86 | report an error. 87 | -------------------------------------------------------------------------------- /motivation/dynamic/inf_sub_pydantic.py: -------------------------------------------------------------------------------- 1 | # Python version: 3.9.5 2 | 3 | # Pydantic version: 1.10.2 4 | # Site: https://pydantic-docs.helpmanual.io/ 5 | # Verdict: no error 6 | # Reason: from looking at the source, variance is probably not supported 7 | 8 | from typing import TypeVar, Generic 9 | from pydantic import BaseModel 10 | from pydantic.generics import GenericModel 11 | Z = TypeVar("Z", contravariant=True) 12 | class N(GenericModel, Generic[Z]): ... 13 | class C(N[N["C"]]): ... 14 | class A(BaseModel): x: N[C] 15 | A(x=C()) 16 | -------------------------------------------------------------------------------- /motivation/dynamic/inf_sub_pytypes.py: -------------------------------------------------------------------------------- 1 | # Python version: 3.9.5 2 | 3 | # Pytypes version: 1.0b10 4 | # Site: https://pypi.org/project/pytypes/ 5 | # Verdict: run time error 6 | # Reason: refers to isinstance which does not support generic types (only when using resolve_fw_decl?) 7 | 8 | from typing import TypeVar, Generic 9 | from pytypes import is_subtype, resolve_fw_decl 10 | Z = TypeVar("Z", contravariant=True) 11 | class N(Generic[Z]): ... 12 | class C(N[N["C"]]): ... 13 | assert not is_subtype(resolve_fw_decl(C), resolve_fw_decl(N[C])) 14 | -------------------------------------------------------------------------------- /motivation/dynamic/inf_sub_typeguard.py: -------------------------------------------------------------------------------- 1 | # Python version: 3.9.5 2 | 3 | # Typeguard version: 2.13.3 4 | # Site: https://typeguard.readthedocs.io 5 | # Verdict: no error 6 | # Reason: from looking at the source, variance is probably not supported 7 | 8 | from typing import TypeVar, Generic, Any 9 | from typeguard import typechecked 10 | Z = TypeVar("Z", contravariant=True) 11 | class N(Generic[Z]): ... 12 | X = TypeVar("X") 13 | class C(Generic[X], N[N["C[C[X]]"]]): ... 14 | _: N[C[Any]] = C[Any]() 15 | @typechecked 16 | def f(_: N[C[Any]]): ... 17 | f(C[Any]()) 18 | -------------------------------------------------------------------------------- /motivation/dynamic/inf_sub_typical.py: -------------------------------------------------------------------------------- 1 | # Python version: 3.9.5 2 | 3 | # Typical version: 2.8.0 4 | # Site: https://python-typical.org/ 5 | # Verdict: run time error 6 | # Reason: refers to isinstance which does not support generic types 7 | 8 | from typing import TypeVar, Generic, Any 9 | import typic 10 | Z = TypeVar("Z", contravariant=True) 11 | class N(Generic[Z]): ... 12 | X = TypeVar("X") 13 | class C(Generic[X], N[N["C[C[X]]"]]): ... 14 | _: N[C[Any]] = C[Any]() 15 | @typic.al 16 | def f(x: N[C[Any]]): ... 17 | f(C()) 18 | -------------------------------------------------------------------------------- /motivation/static/.pyre_configuration: -------------------------------------------------------------------------------- 1 | { 2 | "site_package_search_strategy": "pep561", 3 | "source_directories": [ 4 | "." 5 | ], 6 | "typeshed": "../../typeshed-main" 7 | } 8 | -------------------------------------------------------------------------------- /motivation/static/inf_sub_static.py: -------------------------------------------------------------------------------- 1 | # Python version: 3.9.5 2 | 3 | # Mypy version: 0.991 4 | # Site: http://mypy-lang.org/ 5 | # Verdict: segmentation fault 6 | 7 | # Pyre version: 0.9.17 8 | # Note: download typeshed-main (https://github.com/python/typeshed) and place it in motivation/ 9 | # Site: https://pyre-check.org/ 10 | # Verdict: internal error 11 | 12 | # Pyright version: 1.1.279 13 | # Site: https://github.com/microsoft/pyright 14 | # Verdict: type error 15 | # Reason: ??? 16 | 17 | # Pytype version: 2022.11.10 18 | # Site: https://google.github.io/pytype/ 19 | # Verdict: variance not supported 20 | 21 | # Pyanalyze version: 0.8.0 22 | # Site: https://pyanalyze.readthedocs.io 23 | # Verdict: no error 24 | # Reason: from looking at the source, variance is probably not supported 25 | 26 | from typing import TypeVar, Generic, Any 27 | Z = TypeVar("Z", contravariant=True) 28 | class N(Generic[Z]): ... 29 | X = TypeVar("X") 30 | class C(Generic[X], N[N["C[C[X]]"]]): ... 31 | _: N[C[Any]] = C[Any]() # infinite subtyping 32 | -------------------------------------------------------------------------------- /motivation/static/pyright_unsound.py: -------------------------------------------------------------------------------- 1 | # This file contains a subtyping machine that recognizes palindromes and its input "abbabba", a palindrome. 2 | # Mypy and Pyre compile this file with no errors, as expected. 3 | # Pyright reports a type error, meaning that it is unsound. 4 | 5 | from typing import TypeVar, Generic 6 | T = TypeVar("T", contravariant=True) 7 | class Z: ... 8 | class N(Generic[T]): ... 9 | class ML(Generic[T]): ... 10 | class MR(Generic[T]): ... 11 | class L_a(Generic[T]): ... 12 | class L_c(Generic[T]): ... 13 | class L_b(Generic[T]): ... 14 | class L___TAPE_END__(Generic[T]): ... 15 | class QLR_q0(Generic[T]): ... 16 | class QLR_q1(Generic[T]): ... 17 | class QLR_q2(Generic[T]): ... 18 | class QLR_q8(Generic[T]): ... 19 | class QLR_q3(Generic[T]): ... 20 | class QLR_q4(Generic[T]): ... 21 | class QLR_q5(Generic[T]): ... 22 | class QLR_q6(Generic[T]): ... 23 | class QLR_q7(Generic[T]): ... 24 | class QRL_q0(Generic[T]): ... 25 | class QRL_q1(Generic[T]): ... 26 | class QRL_q2(Generic[T]): ... 27 | class QRL_q8(Generic[T]): ... 28 | class QRL_q3(Generic[T]): ... 29 | class QRL_q4(Generic[T]): ... 30 | class QRL_q5(Generic[T]): ... 31 | class QRL_q6(Generic[T]): ... 32 | class QRL_q7(Generic[T]): ... 33 | class E(Generic[T], QLR_q0["N[QRW_q0[E[E[T]]]]"], QLR_q1["N[QRW_q1[E[E[T]]]]"], QLR_q2["N[QRW_q2[E[E[T]]]]"], QLR_q8["N[QRW_q8[E[E[T]]]]"], QLR_q3["N[QRW_q3[E[E[T]]]]"], QLR_q4["N[QRW_q4[E[E[T]]]]"], QLR_q5["N[QRW_q5[E[E[T]]]]"], QLR_q6["N[QRW_q6[E[E[T]]]]"], QLR_q7["N[QRW_q7[E[E[T]]]]"], QRL_q0["N[QLW_q0[E[E[T]]]]"], QRL_q1["N[QLW_q1[E[E[T]]]]"], QRL_q2["N[QLW_q2[E[E[T]]]]"], QRL_q8["N[QLW_q8[E[E[T]]]]"], QRL_q3["N[QLW_q3[E[E[T]]]]"], QRL_q4["N[QLW_q4[E[E[T]]]]"], QRL_q5["N[QLW_q5[E[E[T]]]]"], QRL_q6["N[QLW_q6[E[E[T]]]]"], QRL_q7["N[QLW_q7[E[E[T]]]]"]): ... 34 | class QLW_q0(Generic[T], ML["N[QL_q0[T]]"], MR["N[QLW_q0[MR[N[T]]]]"], L_a["N[QLW_q0[L_a[N[T]]]]"], L_c["N[QLW_q0[L_c[N[T]]]]"], L_b["N[QLW_q0[L_b[N[T]]]]"], L___TAPE_END__["N[QLW_q0[L___TAPE_END__[N[T]]]]"], E["QLR_q0[N[T]]"]): ... 35 | class QRW_q0(Generic[T], MR["N[QR_q0[T]]"], ML["N[QRW_q0[ML[N[T]]]]"], L_a["N[QRW_q0[L_a[N[T]]]]"], L_c["N[QRW_q0[L_c[N[T]]]]"], L_b["N[QRW_q0[L_b[N[T]]]]"], L___TAPE_END__["N[QRW_q0[L___TAPE_END__[N[T]]]]"], E["QRL_q0[N[T]]"]): ... 36 | class QLW_q1(Generic[T], ML["N[QL_q1[T]]"], MR["N[QLW_q1[MR[N[T]]]]"], L_a["N[QLW_q1[L_a[N[T]]]]"], L_c["N[QLW_q1[L_c[N[T]]]]"], L_b["N[QLW_q1[L_b[N[T]]]]"], L___TAPE_END__["N[QLW_q1[L___TAPE_END__[N[T]]]]"], E["QLR_q1[N[T]]"]): ... 37 | class QRW_q1(Generic[T], MR["N[QR_q1[T]]"], ML["N[QRW_q1[ML[N[T]]]]"], L_a["N[QRW_q1[L_a[N[T]]]]"], L_c["N[QRW_q1[L_c[N[T]]]]"], L_b["N[QRW_q1[L_b[N[T]]]]"], L___TAPE_END__["N[QRW_q1[L___TAPE_END__[N[T]]]]"], E["QRL_q1[N[T]]"]): ... 38 | class QLW_q2(Generic[T], ML["N[QL_q2[T]]"], MR["N[QLW_q2[MR[N[T]]]]"], L_a["N[QLW_q2[L_a[N[T]]]]"], L_c["N[QLW_q2[L_c[N[T]]]]"], L_b["N[QLW_q2[L_b[N[T]]]]"], L___TAPE_END__["N[QLW_q2[L___TAPE_END__[N[T]]]]"], E["QLR_q2[N[T]]"]): ... 39 | class QRW_q2(Generic[T], MR["N[QR_q2[T]]"], ML["N[QRW_q2[ML[N[T]]]]"], L_a["N[QRW_q2[L_a[N[T]]]]"], L_c["N[QRW_q2[L_c[N[T]]]]"], L_b["N[QRW_q2[L_b[N[T]]]]"], L___TAPE_END__["N[QRW_q2[L___TAPE_END__[N[T]]]]"], E["QRL_q2[N[T]]"]): ... 40 | class QLW_q8(Generic[T], ML["N[QL_q8[T]]"], MR["N[QLW_q8[MR[N[T]]]]"], L_a["N[QLW_q8[L_a[N[T]]]]"], L_c["N[QLW_q8[L_c[N[T]]]]"], L_b["N[QLW_q8[L_b[N[T]]]]"], L___TAPE_END__["N[QLW_q8[L___TAPE_END__[N[T]]]]"], E["E[Z]"]): ... 41 | class QRW_q8(Generic[T], MR["N[QR_q8[T]]"], ML["N[QRW_q8[ML[N[T]]]]"], L_a["N[QRW_q8[L_a[N[T]]]]"], L_c["N[QRW_q8[L_c[N[T]]]]"], L_b["N[QRW_q8[L_b[N[T]]]]"], L___TAPE_END__["N[QRW_q8[L___TAPE_END__[N[T]]]]"], E["E[Z]"]): ... 42 | class QLW_q3(Generic[T], ML["N[QL_q3[T]]"], MR["N[QLW_q3[MR[N[T]]]]"], L_a["N[QLW_q3[L_a[N[T]]]]"], L_c["N[QLW_q3[L_c[N[T]]]]"], L_b["N[QLW_q3[L_b[N[T]]]]"], L___TAPE_END__["N[QLW_q3[L___TAPE_END__[N[T]]]]"], E["QLR_q3[N[T]]"]): ... 43 | class QRW_q3(Generic[T], MR["N[QR_q3[T]]"], ML["N[QRW_q3[ML[N[T]]]]"], L_a["N[QRW_q3[L_a[N[T]]]]"], L_c["N[QRW_q3[L_c[N[T]]]]"], L_b["N[QRW_q3[L_b[N[T]]]]"], L___TAPE_END__["N[QRW_q3[L___TAPE_END__[N[T]]]]"], E["QRL_q3[N[T]]"]): ... 44 | class QLW_q4(Generic[T], ML["N[QL_q4[T]]"], MR["N[QLW_q4[MR[N[T]]]]"], L_a["N[QLW_q4[L_a[N[T]]]]"], L_c["N[QLW_q4[L_c[N[T]]]]"], L_b["N[QLW_q4[L_b[N[T]]]]"], L___TAPE_END__["N[QLW_q4[L___TAPE_END__[N[T]]]]"], E["QLR_q4[N[T]]"]): ... 45 | class QRW_q4(Generic[T], MR["N[QR_q4[T]]"], ML["N[QRW_q4[ML[N[T]]]]"], L_a["N[QRW_q4[L_a[N[T]]]]"], L_c["N[QRW_q4[L_c[N[T]]]]"], L_b["N[QRW_q4[L_b[N[T]]]]"], L___TAPE_END__["N[QRW_q4[L___TAPE_END__[N[T]]]]"], E["QRL_q4[N[T]]"]): ... 46 | class QLW_q5(Generic[T], ML["N[QL_q5[T]]"], MR["N[QLW_q5[MR[N[T]]]]"], L_a["N[QLW_q5[L_a[N[T]]]]"], L_c["N[QLW_q5[L_c[N[T]]]]"], L_b["N[QLW_q5[L_b[N[T]]]]"], L___TAPE_END__["N[QLW_q5[L___TAPE_END__[N[T]]]]"], E["QLR_q5[N[T]]"]): ... 47 | class QRW_q5(Generic[T], MR["N[QR_q5[T]]"], ML["N[QRW_q5[ML[N[T]]]]"], L_a["N[QRW_q5[L_a[N[T]]]]"], L_c["N[QRW_q5[L_c[N[T]]]]"], L_b["N[QRW_q5[L_b[N[T]]]]"], L___TAPE_END__["N[QRW_q5[L___TAPE_END__[N[T]]]]"], E["QRL_q5[N[T]]"]): ... 48 | class QLW_q6(Generic[T], ML["N[QL_q6[T]]"], MR["N[QLW_q6[MR[N[T]]]]"], L_a["N[QLW_q6[L_a[N[T]]]]"], L_c["N[QLW_q6[L_c[N[T]]]]"], L_b["N[QLW_q6[L_b[N[T]]]]"], L___TAPE_END__["N[QLW_q6[L___TAPE_END__[N[T]]]]"], E["QLR_q6[N[T]]"]): ... 49 | class QRW_q6(Generic[T], MR["N[QR_q6[T]]"], ML["N[QRW_q6[ML[N[T]]]]"], L_a["N[QRW_q6[L_a[N[T]]]]"], L_c["N[QRW_q6[L_c[N[T]]]]"], L_b["N[QRW_q6[L_b[N[T]]]]"], L___TAPE_END__["N[QRW_q6[L___TAPE_END__[N[T]]]]"], E["QRL_q6[N[T]]"]): ... 50 | class QLW_q7(Generic[T], ML["N[QL_q7[T]]"], MR["N[QLW_q7[MR[N[T]]]]"], L_a["N[QLW_q7[L_a[N[T]]]]"], L_c["N[QLW_q7[L_c[N[T]]]]"], L_b["N[QLW_q7[L_b[N[T]]]]"], L___TAPE_END__["N[QLW_q7[L___TAPE_END__[N[T]]]]"], E["QLR_q7[N[T]]"]): ... 51 | class QRW_q7(Generic[T], MR["N[QR_q7[T]]"], ML["N[QRW_q7[ML[N[T]]]]"], L_a["N[QRW_q7[L_a[N[T]]]]"], L_c["N[QRW_q7[L_c[N[T]]]]"], L_b["N[QRW_q7[L_b[N[T]]]]"], L___TAPE_END__["N[QRW_q7[L___TAPE_END__[N[T]]]]"], E["QRL_q7[N[T]]"]): ... 52 | class QL_q0(Generic[T], L_a["N[QLW_q1[L_c[N[MR[N[T]]]]]]"], L_b["N[QLW_q5[L_c[N[MR[N[T]]]]]]"], L___TAPE_END__["N[QLW_q8[L___TAPE_END__[N[L_c[N[MR[N[T]]]]]]]]"], L_c["N[QLW_q8[L_c[N[MR[N[T]]]]]]"]): ... 53 | class QR_q0(Generic[T], L_a["N[QRW_q1[MR[N[L_c[N[T]]]]]]"], L_b["N[QRW_q5[MR[N[L_c[N[T]]]]]]"], L___TAPE_END__["N[QRW_q8[L___TAPE_END__[N[MR[N[L_c[N[T]]]]]]]]"], L_c["N[QRW_q8[MR[N[L_c[N[T]]]]]]"]): ... 54 | class QL_q1(Generic[T], L_a["N[QLW_q2[L_a[N[MR[N[T]]]]]]"], L_b["N[QLW_q2[L_b[N[MR[N[T]]]]]]"], L___TAPE_END__["N[QLW_q8[L___TAPE_END__[N[L_c[N[MR[N[T]]]]]]]]"], L_c["N[QLW_q8[L_c[N[MR[N[T]]]]]]"]): ... 55 | class QR_q1(Generic[T], L_a["N[QRW_q2[MR[N[L_a[N[T]]]]]]"], L_b["N[QRW_q2[MR[N[L_b[N[T]]]]]]"], L___TAPE_END__["N[QRW_q8[L___TAPE_END__[N[MR[N[L_c[N[T]]]]]]]]"], L_c["N[QRW_q8[MR[N[L_c[N[T]]]]]]"]): ... 56 | class QL_q2(Generic[T], L_a["N[QLW_q2[L_a[N[MR[N[T]]]]]]"], L_b["N[QLW_q2[L_b[N[MR[N[T]]]]]]"], L___TAPE_END__["N[QLW_q3[L___TAPE_END__[N[ML[N[L_c[N[T]]]]]]]]"], L_c["N[QLW_q3[ML[N[L_c[N[T]]]]]]"]): ... 57 | class QR_q2(Generic[T], L_a["N[QRW_q2[MR[N[L_a[N[T]]]]]]"], L_b["N[QRW_q2[MR[N[L_b[N[T]]]]]]"], L___TAPE_END__["N[QRW_q3[L___TAPE_END__[N[L_c[N[ML[N[T]]]]]]]]"], L_c["N[QRW_q3[L_c[N[ML[N[T]]]]]]"]): ... 58 | class QL_q8(Generic[T]): ... 59 | class QR_q8(Generic[T]): ... 60 | class QL_q3(Generic[T], L_a["N[QLW_q4[ML[N[L_c[N[T]]]]]]"]): ... 61 | class QR_q3(Generic[T], L_a["N[QRW_q4[L_c[N[ML[N[T]]]]]]"]): ... 62 | class QL_q4(Generic[T], L_a["N[QLW_q4[ML[N[L_a[N[T]]]]]]"], L_b["N[QLW_q4[ML[N[L_b[N[T]]]]]]"], L___TAPE_END__["N[QLW_q0[L___TAPE_END__[N[L_c[N[MR[N[T]]]]]]]]"], L_c["N[QLW_q0[L_c[N[MR[N[T]]]]]]"]): ... 63 | class QR_q4(Generic[T], L_a["N[QRW_q4[L_a[N[ML[N[T]]]]]]"], L_b["N[QRW_q4[L_b[N[ML[N[T]]]]]]"], L___TAPE_END__["N[QRW_q0[L___TAPE_END__[N[MR[N[L_c[N[T]]]]]]]]"], L_c["N[QRW_q0[MR[N[L_c[N[T]]]]]]"]): ... 64 | class QL_q5(Generic[T], L_a["N[QLW_q6[L_a[N[MR[N[T]]]]]]"], L_b["N[QLW_q6[L_b[N[MR[N[T]]]]]]"], L___TAPE_END__["N[QLW_q8[L___TAPE_END__[N[L_c[N[MR[N[T]]]]]]]]"], L_c["N[QLW_q8[L_c[N[MR[N[T]]]]]]"]): ... 65 | class QR_q5(Generic[T], L_a["N[QRW_q6[MR[N[L_a[N[T]]]]]]"], L_b["N[QRW_q6[MR[N[L_b[N[T]]]]]]"], L___TAPE_END__["N[QRW_q8[L___TAPE_END__[N[MR[N[L_c[N[T]]]]]]]]"], L_c["N[QRW_q8[MR[N[L_c[N[T]]]]]]"]): ... 66 | class QL_q6(Generic[T], L_a["N[QLW_q6[L_a[N[MR[N[T]]]]]]"], L_b["N[QLW_q6[L_b[N[MR[N[T]]]]]]"], L___TAPE_END__["N[QLW_q7[L___TAPE_END__[N[ML[N[L_c[N[T]]]]]]]]"], L_c["N[QLW_q7[ML[N[L_c[N[T]]]]]]"]): ... 67 | class QR_q6(Generic[T], L_a["N[QRW_q6[MR[N[L_a[N[T]]]]]]"], L_b["N[QRW_q6[MR[N[L_b[N[T]]]]]]"], L___TAPE_END__["N[QRW_q7[L___TAPE_END__[N[L_c[N[ML[N[T]]]]]]]]"], L_c["N[QRW_q7[L_c[N[ML[N[T]]]]]]"]): ... 68 | class QL_q7(Generic[T], L_b["N[QLW_q4[ML[N[L_c[N[T]]]]]]"]): ... 69 | class QR_q7(Generic[T], L_b["N[QRW_q4[L_c[N[ML[N[T]]]]]]"]): ... 70 | _: E[E[Z]] = QRW_q0[L___TAPE_END__[N[L_a[N[L_b[N[L_b[N[L_a[N[L_b[N[L_b[N[L_a[N[MR[N[L___TAPE_END__[N[E[E[Z]]]]]]]]]]]]]]]]]]]]]]]() 71 | -------------------------------------------------------------------------------- /typing_machines/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OriRoth/python-typing-machines/cedb8529a3b0907abdf49c30d0f72ec6e2fdd90b/typing_machines/__init__.py -------------------------------------------------------------------------------- /typing_machines/abstract_machines/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OriRoth/python-typing-machines/cedb8529a3b0907abdf49c30d0f72ec6e2fdd90b/typing_machines/abstract_machines/__init__.py -------------------------------------------------------------------------------- /typing_machines/abstract_machines/turing_machine.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from enum import Enum 3 | from functools import cached_property 4 | from typing import List 5 | 6 | 7 | class Direction(Enum): 8 | """ 9 | Machine head move direction. 10 | """ 11 | LEFT = 1 12 | RIGHT = 2 13 | 14 | 15 | @dataclass 16 | class Transition: 17 | """ 18 | Turing machine transition. 19 | When the machine is in state `source_state` and 20 | and the current tape cell contains the letter `read_letter`, 21 | the machine moves to state `target_state`, writes letter 22 | `write_letter` in the current cell, and moves the machine 23 | head according to `move_direction`. 24 | """ 25 | source_state: str 26 | read_letter: str 27 | target_state: str 28 | write_letter: str 29 | move_direction: Direction 30 | 31 | 32 | @dataclass 33 | class TuringMachine: 34 | """ 35 | Turing machine specifications. 36 | Accepts the input word immediately when reaching its single accepting state. 37 | """ 38 | BLANK = "@" 39 | initial_state: str 40 | termination_state: str 41 | transitions: List[Transition] 42 | 43 | @cached_property 44 | def alphabet(self) -> List[str]: 45 | """ 46 | Returns the machine alphabet, i.e., the set of machine letters. 47 | """ 48 | alphabet = [] 49 | for transition in self.transitions: 50 | if transition.read_letter not in alphabet and transition.read_letter != TuringMachine.BLANK: 51 | alphabet.append(transition.read_letter) 52 | if transition.write_letter not in alphabet and transition.write_letter != TuringMachine.BLANK: 53 | alphabet.append(transition.write_letter) 54 | return alphabet 55 | 56 | @cached_property 57 | def states(self) -> List[str]: 58 | """ 59 | Returns the set of machine states. 60 | """ 61 | states = [self.initial_state] 62 | for transition in self.transitions: 63 | if transition.source_state not in states: 64 | states.append(transition.source_state) 65 | if transition.target_state not in states: 66 | states.append(transition.target_state) 67 | if self.termination_state not in states: 68 | states.append(self.termination_state) 69 | return states 70 | -------------------------------------------------------------------------------- /typing_machines/app.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | from os import remove 3 | from subprocess import Popen 4 | from time import sleep 5 | from typing import Union, List 6 | 7 | from typing_machines.abstract_machines.turing_machine import TuringMachine 8 | from typing_machines.compilers.compiler_g import compile_g, compile_query_g 9 | from typing_machines.compilers.compiler_r import compile_r, compile_query_r 10 | from typing_machines.examples.machines import palindromes 11 | 12 | 13 | class Algorithm(Enum): 14 | """ 15 | Supported encoding algorithms by author name. 16 | """ 17 | Grigore = 1 18 | Roth = 2 19 | 20 | 21 | def encode_machine(algorithm: Algorithm, machine: TuringMachine) -> str: 22 | """ 23 | Encode a Turing machine as a Python class table with given algorithm. 24 | """ 25 | if algorithm == Algorithm.Grigore: 26 | return compile_g(machine) 27 | elif algorithm == Algorithm.Roth: 28 | return compile_r(machine) 29 | else: 30 | raise Exception(f"unrecognized algorithm {algorithm}") 31 | 32 | 33 | def encode_query(algorithm: Algorithm, machine: TuringMachine, input_word: Union[str, List[str]]) -> str: 34 | """ 35 | Encode an input word as a Python subtyping query with given algorithm. 36 | """ 37 | if algorithm == Algorithm.Grigore: 38 | return compile_query_g(input_word, machine) 39 | elif algorithm == Algorithm.Roth: 40 | return compile_query_r(input_word, machine) 41 | else: 42 | raise Exception(f"unrecognized algorithm {algorithm}") 43 | 44 | 45 | def encode(algorithm: Algorithm, machine: TuringMachine, input_word: Union[str, List[str]]) -> str: 46 | """ 47 | Encode a Turing machine and its input using Python typing hints with given algorithm. 48 | """ 49 | return encode_machine(algorithm, machine) + "\n" + encode_query(algorithm, machine, input_word) 50 | 51 | 52 | if __name__ == '__main__': 53 | print("Is 'abbabba' a palindrome?") 54 | with open("example.py", "w") as python_file: 55 | python_file.write(encode(Algorithm.Grigore, palindromes, "abbabba")) 56 | sleep(1) 57 | with Popen(["mypy", "example.py"]) as p: 58 | retcode = p.wait(timeout=10) 59 | assert retcode == 0 # abbabba is a palindrome 60 | print("Is 'abbbaba' a palindrome?") 61 | with open("example.py", "w") as python_file: 62 | python_file.write(encode(Algorithm.Grigore, palindromes, "abbbaba")) 63 | sleep(1) 64 | with Popen(["mypy", "example.py"]) as p: 65 | retcode = p.wait(timeout=10) 66 | assert retcode != 0 # abbbaba is not a palindrome 67 | remove("example.py") 68 | -------------------------------------------------------------------------------- /typing_machines/compilers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OriRoth/python-typing-machines/cedb8529a3b0907abdf49c30d0f72ec6e2fdd90b/typing_machines/compilers/__init__.py -------------------------------------------------------------------------------- /typing_machines/compilers/compiler_g.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from typing_machines.abstract_machines.turing_machine import TuringMachine, Direction 4 | 5 | 6 | def _render_type(*types: str, stringify_argument=True) -> str: 7 | assert len(types) > 0 8 | if len(types) == 1: 9 | return types[0] 10 | quotation: str = "\"" if stringify_argument else "" 11 | return f"{types[0]}[{quotation}{_render_type(*types[1:], stringify_argument=False)}{quotation}]" 12 | 13 | 14 | TAPE_END: str = "__TAPE_END__" 15 | 16 | 17 | def compile_g(turing_machine: TuringMachine) -> str: 18 | """ 19 | Compiles a Turing machine into Python type hints that simulate it. 20 | Implementation of Grigore's original construction, see here for more details: 21 | https://arxiv.org/abs/1605.05274 22 | """ 23 | typing_machine: str = "" 24 | # render imports and type variable 25 | typing_machine += "from typing import TypeVar, Generic\n" 26 | typing_machine += "T = TypeVar(\"T\", contravariant=True)\n" 27 | # render Z / N / ML / MR 28 | typing_machine += "class Z: ...\n" 29 | typing_machine += "class N(Generic[T]): ...\n" 30 | typing_machine += "class ML(Generic[T]): ...\n" 31 | typing_machine += "class MR(Generic[T]): ...\n" 32 | # render L_s 33 | typing_machine += "\n".join(f"class L_{letter}(Generic[T]): ..." for letter in turing_machine.alphabet) + "\n" 34 | typing_machine += f"class L_{TAPE_END}(Generic[T]): ...\n" 35 | # render q_lr / q_rl 36 | typing_machine += "\n".join(f"class QLR_{state}(Generic[T]): ..." for state in turing_machine.states) + "\n" 37 | typing_machine += "\n".join(f"class QRL_{state}(Generic[T]): ..." for state in turing_machine.states) + "\n" 38 | # render E 39 | supers_e: List[str] = [] 40 | supers_e += [_render_type(f"QLR_{state}", "N", f"QRW_{state}", "E", "E", "T") for state in turing_machine.states] 41 | supers_e += [_render_type(f"QRL_{state}", "N", f"QLW_{state}", "E", "E", "T") for state in turing_machine.states] 42 | super_clause: str = "" if len(supers_e) == 0 else ", " + ", ".join(supers_e) 43 | typing_machine += f"class E(Generic[T]{super_clause}): ...\n" 44 | # render q_lw / q_rw 45 | for state in turing_machine.states: 46 | supers_lw: List[str] = [] 47 | supers_rw: List[str] = [] 48 | supers_lw.append(_render_type("ML", "N", f"QL_{state}", "T")) 49 | supers_rw.append(_render_type("MR", "N", f"QR_{state}", "T")) 50 | supers_lw.append(_render_type("MR", "N", f"QLW_{state}", "MR", "N", "T")) 51 | supers_rw.append(_render_type("ML", "N", f"QRW_{state}", "ML", "N", "T")) 52 | supers_lw += [_render_type(f"L_{letter}", "N", f"QLW_{state}", f"L_{letter}", "N", "T") 53 | for letter in turing_machine.alphabet + [TAPE_END]] 54 | supers_rw += [_render_type(f"L_{letter}", "N", f"QRW_{state}", f"L_{letter}", "N", "T") 55 | for letter in turing_machine.alphabet + [TAPE_END]] 56 | if state != turing_machine.termination_state: 57 | supers_lw.append(_render_type("E", f"QLR_{state}", "N", "T")) 58 | supers_rw.append(_render_type("E", f"QRL_{state}", "N", "T")) 59 | else: 60 | supers_lw.append(_render_type("E", "E", "Z")) 61 | supers_rw.append(_render_type("E", "E", "Z")) 62 | super_clause: str = "" if len(supers_lw) == 0 else ", " + ", ".join(supers_lw) 63 | typing_machine += f"class QLW_{state}(Generic[T]{super_clause}): ...\n" 64 | super_clause = "" if len(supers_rw) == 0 else ", " + ", ".join(supers_rw) 65 | typing_machine += f"class QRW_{state}(Generic[T]{super_clause}): ...\n" 66 | # render q_l / q_r 67 | for state in turing_machine.states: 68 | supers_l: List[str] = [] 69 | supers_r: List[str] = [] 70 | for transition in turing_machine.transitions: 71 | if transition.source_state == state: 72 | if transition.read_letter != TuringMachine.BLANK: 73 | if transition.move_direction == Direction.LEFT: 74 | supers_l.append(_render_type(f"L_{transition.read_letter}", "N", 75 | f"QLW_{transition.target_state}", "ML", "N", 76 | f"L_{transition.write_letter}", "N", "T")) 77 | supers_r.append(_render_type(f"L_{transition.read_letter}", "N", 78 | f"QRW_{transition.target_state}", f"L_{transition.write_letter}", 79 | "N", "ML", "N", "T")) 80 | else: 81 | supers_l.append(_render_type(f"L_{transition.read_letter}", "N", 82 | f"QLW_{transition.target_state}", f"L_{transition.write_letter}", 83 | "N", "MR", "N", "T")) 84 | supers_r.append(_render_type(f"L_{transition.read_letter}", "N", 85 | f"QRW_{transition.target_state}", "MR", "N", 86 | f"L_{transition.write_letter}", "N", "T")) 87 | else: 88 | if transition.move_direction == Direction.LEFT: 89 | supers_l.append(_render_type(f"L_{TAPE_END}", "N", f"QLW_{transition.target_state}", 90 | f"L_{TAPE_END}", "N", "ML", "N", 91 | f"L_{transition.write_letter}", "N", "T")) 92 | supers_r.append(_render_type(f"L_{TAPE_END}", "N", f"QRW_{transition.target_state}", 93 | f"L_{TAPE_END}", "N", f"L_{transition.write_letter}", 94 | "N", "ML", "N", "T")) 95 | else: 96 | supers_l.append(_render_type(f"L_{TAPE_END}", "N", f"QLW_{transition.target_state}", 97 | f"L_{TAPE_END}", "N", f"L_{transition.write_letter}", 98 | "N", "MR", "N", "T")) 99 | supers_r.append(_render_type(f"L_{TAPE_END}", "N", f"QRW_{transition.target_state}", 100 | f"L_{TAPE_END}", "N", "MR", "N", 101 | f"L_{transition.write_letter}", "N", "T")) 102 | super_clause: str = "" if len(supers_l) == 0 else ", " + ", ".join(supers_l) 103 | typing_machine += f"class QL_{state}(Generic[T]{super_clause}): ...\n" 104 | super_clause = "" if len(supers_r) == 0 else ", " + ", ".join(supers_r) 105 | typing_machine += f"class QR_{state}(Generic[T]{super_clause}): ...\n" 106 | return typing_machine.rstrip() 107 | 108 | 109 | def compile_query_g(input_word: Union[str, List[str]], 110 | turing_machine: TuringMachine): 111 | """ 112 | Compiles an input word into a variable assignments which invokes a 113 | subtyping query. 114 | """ 115 | if isinstance(input_word, str): 116 | return compile_query_g(list(input_word), turing_machine) 117 | input_word = input_word[::-1] 118 | tape: List[str] = [] 119 | tape.append(f"QRW_{turing_machine.initial_state}") 120 | tape.append(f"L_{TAPE_END}") 121 | tape.append("N") 122 | for letter in input_word: 123 | tape.append(f"L_{letter}") 124 | tape.append("N") 125 | tape.append("MR") 126 | tape.append("N") 127 | tape.append(f"L_{TAPE_END}") 128 | tape.append("N") 129 | tape.append("E") 130 | tape.append("E") 131 | tape.append("Z") 132 | tape_type: str = "[".join(tape) + "]" * (len(tape) - 1) 133 | return f"_: E[E[Z]] = {tape_type}()" 134 | -------------------------------------------------------------------------------- /typing_machines/compilers/compiler_r.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | 3 | from typing_machines.abstract_machines.turing_machine import TuringMachine, Direction 4 | 5 | 6 | def _render_type(*types: str, stringify_argument=True) -> str: 7 | assert len(types) > 0 8 | if len(types) == 1: 9 | return types[0] 10 | quotation: str = "\"" if stringify_argument else "" 11 | return f"{types[0]}[{quotation}{_render_type(*types[1:], stringify_argument=False)}{quotation}]" 12 | 13 | 14 | TAPE_END: str = "__TAPE_END__" 15 | 16 | 17 | def compile_r(turing_machine: TuringMachine) -> str: 18 | """ 19 | Compiles a Turing machine into Python type hints that simulate it 20 | in real time. 21 | """ 22 | typing_machine: str = "" 23 | # render imports and type variable 24 | typing_machine += "from typing import TypeVar, Generic, Any\n" 25 | typing_machine += "T = TypeVar(\"T\", contravariant=True)\n" 26 | # render Z 27 | typing_machine += "class Z: ...\n" 28 | # render L_s 29 | typing_machine += "\n".join(f"class L_{letter}(Generic[T]): ..." for letter in turing_machine.alphabet) + "\n" 30 | typing_machine += f"class L_{TAPE_END}(Generic[T]): ...\n" 31 | # render q_l / q_r 32 | for state in turing_machine.states: 33 | if state == turing_machine.termination_state: 34 | continue 35 | supers_l = [] 36 | supers_r = [] 37 | for transition in turing_machine.transitions: 38 | if transition.source_state == state: 39 | if transition.read_letter != TuringMachine.BLANK: 40 | if transition.move_direction == Direction.LEFT: 41 | supers_l.append(_render_type(f"L_{transition.read_letter}", "N", 42 | f"QL_{transition.target_state}", 43 | f"L_{transition.write_letter}", "N", "T")) 44 | supers_r.append(_render_type(f"L_{transition.read_letter}", 45 | f"QWRL_{transition.target_state}", 46 | "N", f"L_{transition.write_letter}", "N", "T")) 47 | else: 48 | supers_l.append(_render_type(f"L_{transition.read_letter}", 49 | f"QWLR_{transition.target_state}", 50 | "N", f"L_{transition.write_letter}", "N", "T")) 51 | supers_r.append(_render_type(f"L_{transition.read_letter}", "N", 52 | f"QR_{transition.target_state}", 53 | f"L_{transition.write_letter}", "N", "T")) 54 | else: 55 | if transition.move_direction == Direction.LEFT: 56 | supers_l.append(_render_type(f"L_{TAPE_END}", f"QLSL_{transition.target_state}", 57 | "N", f"L_{transition.write_letter}", "N", "T")) 58 | supers_r.append(_render_type(f"L_{TAPE_END}", f"QRSL_{transition.target_state}", 59 | "N", f"L_{transition.write_letter}", "N", "T")) 60 | else: 61 | supers_l.append(_render_type(f"L_{TAPE_END}", f"QLSR_{transition.target_state}", 62 | "N", f"L_{transition.write_letter}", "N", "T")) 63 | supers_r.append(_render_type(f"L_{TAPE_END}", f"QRSR_{transition.target_state}", 64 | "N", f"L_{transition.write_letter}", "N", "T")) 65 | super_clause: str = "" if len(supers_l) == 0 else ", " + ", ".join(supers_l) 66 | typing_machine += f"class QL_{state}(Generic[T]{super_clause}): ...\n" 67 | super_clause = "" if len(supers_r) == 0 else ", " + ", ".join(supers_r) 68 | typing_machine += f"class QR_{state}(Generic[T]{super_clause}): ...\n" 69 | supers_hl = [] 70 | supers_hr = [] 71 | for letter in turing_machine.alphabet + [TAPE_END]: 72 | supers_hl.append(_render_type(f"L_{letter}", "Any")) 73 | supers_hr.append(_render_type(f"L_{letter}", "Any")) 74 | super_clause: str = "" if len(supers_hl) == 0 else ", " + ", ".join(supers_hl) 75 | typing_machine += f"class QL_{turing_machine.termination_state}(Generic[T]{super_clause}): ...\n" 76 | super_clause = "" if len(supers_hr) == 0 else ", " + ", ".join(supers_hr) 77 | typing_machine += f"class QR_{turing_machine.termination_state}(Generic[T]{super_clause}): ...\n" 78 | # render q_wl / q_wr 79 | for state in turing_machine.states: 80 | supers_wl = [] 81 | supers_wr = [] 82 | for letter in turing_machine.alphabet: 83 | supers_wl.append(_render_type(f"L_{letter}", "N", f"QL_{state}", f"L_{letter}", "N", "T")) 84 | supers_wr.append(_render_type(f"L_{letter}", "N", f"QR_{state}", f"L_{letter}", "N", "T")) 85 | super_clause: str = "" if len(supers_wl) == 0 else ", " + ", ".join(supers_wl) 86 | typing_machine += f"class QWL_{state}(Generic[T]{super_clause}): ...\n" 87 | super_clause = "" if len(supers_wr) == 0 else ", " + ", ".join(supers_wr) 88 | typing_machine += f"class QWR_{state}(Generic[T]{super_clause}): ...\n" 89 | # render q_rl / q_lr / q_wlr / q_wrl / q_lsl / q_rsr / q_lsr / q_rsl 90 | for state in turing_machine.states: 91 | typing_machine += f"class QRL_{state}(Generic[T]): ...\n" 92 | typing_machine += f"class QLR_{state}(Generic[T]): ...\n" 93 | typing_machine += f"class QWLR_{state}(Generic[T]): ...\n" 94 | typing_machine += f"class QWRL_{state}(Generic[T]): ...\n" 95 | typing_machine += f"class QLSL_{state}(Generic[T]): ...\n" 96 | typing_machine += f"class QRSR_{state}(Generic[T]): ...\n" 97 | typing_machine += f"class QLSR_{state}(Generic[T]): ...\n" 98 | typing_machine += f"class QRSL_{state}(Generic[T]): ...\n" 99 | # render N 100 | supers_n = [] 101 | for state in turing_machine.states: 102 | supers_n.append(_render_type(f"QWLR_{state}", "N", f"QWR_{state}", "T")) 103 | supers_n.append(_render_type(f"QWRL_{state}", "N", f"QWL_{state}", "T")) 104 | supers_n.append(_render_type(f"QLSL_{state}", f"QRL_{state}", "N", f"L_{TAPE_END}", "N", "T")) 105 | supers_n.append(_render_type(f"QRSR_{state}", f"QLR_{state}", "N", f"L_{TAPE_END}", "N", "T")) 106 | supers_n.append(_render_type(f"QLSR_{state}", "N", f"QWR_{state}", f"L_{TAPE_END}", "N", "T")) 107 | supers_n.append(_render_type(f"QRSL_{state}", "N", f"QWL_{state}", f"L_{TAPE_END}", "N", "T")) 108 | supers_n.append(_render_type(f"QLR_{state}", "N", f"QR_{state}", "T")) 109 | supers_n.append(_render_type(f"QRL_{state}", "N", f"QL_{state}", "T")) 110 | super_clause: str = "" if len(supers_n) == 0 else ", " + ", ".join(supers_n) 111 | typing_machine += f"class N(Generic[T]{super_clause}): ..." 112 | return typing_machine 113 | 114 | 115 | def compile_query_r(input_word: Union[str, List[str]], 116 | turing_machine: TuringMachine): 117 | """ 118 | Compiles an input word into a variable assignments which invokes a 119 | subtyping query. 120 | """ 121 | if isinstance(input_word, str): 122 | return compile_query_r(list(input_word), turing_machine) 123 | tape: List[str] = [] 124 | for letter in input_word: 125 | tape.append(f"L_{letter}") 126 | tape.append("N") 127 | tape.append(f"L_{TAPE_END}") 128 | tape.append("N") 129 | tape.append("Z") 130 | value: str = _render_type(f"QR_{turing_machine.initial_state}", f"L_{TAPE_END}", "N", "Z") 131 | return f"_: {_render_type(*tape)} = {value}()" 132 | -------------------------------------------------------------------------------- /typing_machines/examples/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OriRoth/python-typing-machines/cedb8529a3b0907abdf49c30d0f72ec6e2fdd90b/typing_machines/examples/__init__.py -------------------------------------------------------------------------------- /typing_machines/examples/machines.py: -------------------------------------------------------------------------------- 1 | """ 2 | Turing machine examples. 3 | """ 4 | 5 | from typing_machines.abstract_machines.turing_machine import TuringMachine, Transition, Direction 6 | 7 | anbn: TuringMachine = TuringMachine("q0", "q4", [ 8 | Transition("q0", "a", "q1", "c", Direction.RIGHT), 9 | Transition("q1", "a", "q1", "a", Direction.RIGHT), 10 | Transition("q1", "b", "q1", "b", Direction.RIGHT), 11 | Transition("q1", TuringMachine.BLANK, "q2", "c", Direction.LEFT), 12 | Transition("q1", "c", "q2", "c", Direction.LEFT), 13 | Transition("q2", "b", "q3", "c", Direction.LEFT), 14 | Transition("q3", "a", "q3", "a", Direction.LEFT), 15 | Transition("q3", "b", "q3", "b", Direction.LEFT), 16 | Transition("q3", TuringMachine.BLANK, "q0", "c", Direction.RIGHT), 17 | Transition("q3", "c", "q0", "c", Direction.RIGHT), 18 | Transition("q0", TuringMachine.BLANK, "q4", "c", Direction.RIGHT), 19 | Transition("q0", "c", "q4", "c", Direction.RIGHT), 20 | ]) 21 | """ Recognizes { a^n b^n | n >=0 } """ 22 | 23 | palindromes: TuringMachine = TuringMachine("q0", "q8", [ 24 | Transition("q0", "a", "q1", "c", Direction.RIGHT), 25 | Transition("q1", "a", "q2", "a", Direction.RIGHT), 26 | Transition("q1", "b", "q2", "b", Direction.RIGHT), 27 | Transition("q1", TuringMachine.BLANK, "q8", "c", Direction.RIGHT), 28 | Transition("q1", "c", "q8", "c", Direction.RIGHT), 29 | Transition("q2", "a", "q2", "a", Direction.RIGHT), 30 | Transition("q2", "b", "q2", "b", Direction.RIGHT), 31 | Transition("q2", TuringMachine.BLANK, "q3", "c", Direction.LEFT), 32 | Transition("q2", "c", "q3", "c", Direction.LEFT), 33 | Transition("q3", "a", "q4", "c", Direction.LEFT), 34 | Transition("q4", "a", "q4", "a", Direction.LEFT), 35 | Transition("q4", "b", "q4", "b", Direction.LEFT), 36 | Transition("q4", TuringMachine.BLANK, "q0", "c", Direction.RIGHT), 37 | Transition("q4", "c", "q0", "c", Direction.RIGHT), 38 | Transition("q0", "b", "q5", "c", Direction.RIGHT), 39 | Transition("q5", "a", "q6", "a", Direction.RIGHT), 40 | Transition("q5", "b", "q6", "b", Direction.RIGHT), 41 | Transition("q5", TuringMachine.BLANK, "q8", "c", Direction.RIGHT), 42 | Transition("q5", "c", "q8", "c", Direction.RIGHT), 43 | Transition("q6", "a", "q6", "a", Direction.RIGHT), 44 | Transition("q6", "b", "q6", "b", Direction.RIGHT), 45 | Transition("q6", TuringMachine.BLANK, "q7", "c", Direction.LEFT), 46 | Transition("q6", "c", "q7", "c", Direction.LEFT), 47 | Transition("q7", "b", "q4", "c", Direction.LEFT), 48 | Transition("q0", TuringMachine.BLANK, "q8", "c", Direction.RIGHT), 49 | Transition("q0", "c", "q8", "c", Direction.RIGHT), 50 | ]) 51 | """ Recognizes { w | w is a palindrome over {a,b} } """ 52 | -------------------------------------------------------------------------------- /typing_machines/experiment/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OriRoth/python-typing-machines/cedb8529a3b0907abdf49c30d0f72ec6e2fdd90b/typing_machines/experiment/__init__.py -------------------------------------------------------------------------------- /typing_machines/experiment/stack_size_experiment.py: -------------------------------------------------------------------------------- 1 | from os import remove 2 | from random import Random 3 | from resource import RLIMIT_STACK, setrlimit 4 | from subprocess import Popen, DEVNULL 5 | from time import sleep 6 | from typing import Callable, List, Tuple, Iterable 7 | 8 | import matplotlib.pyplot as plt 9 | 10 | from typing_machines.app import encode, Algorithm 11 | from typing_machines.examples.machines import palindromes 12 | 13 | 14 | def binary_search(le: Callable[[int], bool]) -> int: 15 | """ 16 | Finds a natural number according to the given "lower equals" predicate. 17 | Returns -1 if no number is found. 18 | """ 19 | l: int = -1 20 | u: int = 1 21 | while le(u): 22 | l = u 23 | u *= 2 24 | u -= 1 25 | while l < u: 26 | m: int = (l + u) // 2 + (l + u) % 2 27 | if le(m): 28 | l = m 29 | else: 30 | u = m - 1 31 | return l 32 | 33 | 34 | def get_random_palindrome(n: int) -> str: 35 | """ 36 | Returns a random palindrome over {a, b} of length n. 37 | Always returns the same word for a given n. 38 | """ 39 | random: Random = Random(n) 40 | palindrome: str = "" 41 | while n > 0: 42 | l: str = "a" if random.getrandbits(1) else "b" 43 | palindrome = l + palindrome + l 44 | n -= 1 45 | return palindrome 46 | 47 | 48 | def get_stack_size(algorithm: Algorithm, input_word: str) -> int: 49 | """ 50 | Get the call stack size mypy requires to compile the palindromes typing machine with the given 51 | algorithm and input palindrome. 52 | """ 53 | 54 | def compiles(n: int) -> bool: 55 | with open("test.py", "w") as python_file: 56 | python_file.write(encode(algorithm, palindromes, input_word)) 57 | sleep(1) 58 | stack_size: int = (n + 5) * 1000000 59 | with Popen(["mypy", "test.py"], preexec_fn=lambda: setrlimit(RLIMIT_STACK, (stack_size, stack_size)), 60 | stdout=DEVNULL, stderr=DEVNULL) as p: 61 | retcode = p.wait(timeout=10) 62 | remove("test.py") 63 | return retcode != 0 64 | 65 | depth: int = binary_search(compiles) 66 | depth = 5 if depth == -1 else depth + 5 67 | return depth 68 | 69 | 70 | def run_experiment(algorithm: Algorithm, input_lengths: Iterable[int]) -> List[Tuple[int, int]]: 71 | """ 72 | Find mypy stack sizes for given algorithm and input lengths. 73 | """ 74 | results: List[Tuple[int, int]] = [] 75 | for n in input_lengths: 76 | s: int = get_stack_size(algorithm, get_random_palindrome(n)) 77 | results.append((n * 2, s)) 78 | print(f"mypy requires {s}M stack size with algorithm {algorithm.name} and palindrome of length {n * 2}") 79 | return results 80 | 81 | 82 | if __name__ == '__main__': 83 | grigore_results: List[Tuple[int, int]] = run_experiment(Algorithm.Grigore, range(5, 9)) 84 | print("Grigore's results:") 85 | for n, s in grigore_results: 86 | print(f"Grigore\t{n}\t{s}") 87 | roth_results: List[Tuple[int, int]] = run_experiment(Algorithm.Roth, range(5, 46, 5)) 88 | print("Roth's results:") 89 | for n, s in roth_results: 90 | print(f"Roth\t{n}\t{s}") 91 | plt.plot([n for n, _ in grigore_results], [s for _, s in grigore_results], color="blue", label="Grigore") 92 | plt.scatter([n for n, _ in grigore_results], [s for _, s in grigore_results], color="blue", marker="o") 93 | plt.plot([n for n, _ in roth_results], [s for _, s in roth_results], color="green", label="Roth") 94 | plt.scatter([n for n, _ in roth_results], [s for _, s in roth_results], color="green", marker="x") 95 | plt.legend(loc="upper right") 96 | plt.show() 97 | --------------------------------------------------------------------------------