title | filename | chapternum |
---|---|---|
Restricted computational models |
lec_08a_restricted_models |
10 |
- See that Turing completeness is not always a good thing.
- Another example of an always-halting formalism: context-free grammars and simply typed $\lambda$ calculus.
- The pumping lemma for non context-free functions.
- Examples of computable and uncomputable semantic properties of regular expressions and context-free grammars.
"Happy families are all alike; every unhappy family is unhappy in its own way", Leo Tolstoy (opening of the book "Anna Karenina").
We have seen that many models of computation are Turing equivalent, including Turing machines, NAND-TM/NAND-RAM programs, standard programming languages such as C/Python/Javascript, as well as other models such as the
The uncomputability of halting and other semantic specification problems for Turing equivalent models motivates restricted computational models that are (a) powerful enough to capture a set of functions useful for certain applications but (b) weak enough that we can still solve semantic specification problems on them. In this chapter we discuss several such examples.
::: { .bigidea #restrictedmodel} We can use restricted computational models to bypass limitations such as uncomputability of the Halting problem and Rice's Theorem. Such models can compute only a restricted subclass of functions, but allow to answer at least some semantic questions on programs. :::
{#restrictedmodelsoverviewfig}
We have seen that seemingly simple computational models or systems can turn out to be Turing complete. The following webpage lists several examples of formalisms that "accidentally" turned out to Turing complete, including supposedly limited languages such as the C preprocessor, CSS, (certain variants of) SQL, sendmail configuration, as well as games such as Minecraft, Super Mario, and the card game "Magic: The Gathering". Turing completeness is not always a good thing, as it means that such formalisms can give rise to arbitrarily complex behavior. For example, the postscript format (a precursor of PDF) is a Turing-complete programming language meant to describe documents for printing. The expressive power of postscript can allow for short descriptions of very complex images, but it also gave rise to some nasty surprises, such as the attacks described in this page ranging from using infinite loops as a denial of service attack, to accessing the printer's file system.
::: {.example title="The DAO Hack" #ethereum}
An interesting recent example of the pitfalls of Turing-completeness arose in the context of the cryptocurrency Ethereum.
The distinguishing feature of this currency is the ability to design "smart contracts" using an expressive (and in particular Turing-complete) programming language.
In our current "human operated" economy, Alice and Bob might sign a contract to agree that if condition X happens then they will jointly invest in Charlie's company.
Ethereum allows Alice and Bob to create a joint venture where Alice and Bob pool their funds together into an account that will be governed by some program
Specifically Ethereum uses the Turing-complete programming language solidity which has a syntax similar to JavaScript. The flagship of Ethereum was an experiment known as The "Decentralized Autonomous Organization" or The DAO. The idea was to create a smart contract that would create an autonomously run decentralized venture capital fund, without human managers, where shareholders could decide on investment opportunities. The DAO was at the time the biggest crowdfunding success in history. At its height the DAO was worth 150 million dollars, which was more than ten percent of the total Ethereum market. Investing in the DAO (or entering any other "smart contract") amounts to providing your funds to be run by a computer program. i.e., "code is law", or to use the words the DAO described itself: "The DAO is borne from immutable, unstoppable, and irrefutable computer code". Unfortunately, it turns out that (as we saw in chapcomputable{.ref}) understanding the behavior of computer programs is quite a hard thing to do. A hacker (or perhaps, some would say, a savvy investor) was able to fashion an input that caused the DAO code to enter into an infinite recursive loop in which it continuously transferred funds into the hacker's account, thereby cleaning out about 60 million dollars out of the DAO. While this transaction was "legal" in the sense that it complied with the code of the smart contract, it was obviously not what the humans who wrote this code had in mind. The Ethereum community struggled with the response to this attack. Some tried the "Robin Hood" approach of using the same loophole to drain the DAO funds into a secure account, but it only had limited success. Eventually, the Ethereum community decided that the code can be mutable, stoppable, and refutable. Specifically, the Ethereum maintainers and miners agreed on a "hard fork" (also known as a "bailout") to revert history to before the hacker's transaction occurred. Some community members strongly opposed this decision, and so an alternative currency called Ethereum Classic was created that preserved the original history. :::
If you have ever written a program, you've experienced a syntax error. You probably also had the experience of your program entering into an infinite loop. What is less likely is that the compiler or interpreter entered an infinite loop while trying to figure out if your program has a syntax error.
When a person designs a programming language, they need to determine its syntax. That is, the designer decides which strings corresponds to valid programs, and which ones do not (i.e., which strings contain a syntax error). To ensure that a compiler or interpreter always halts when checking for syntax errors, language designers typically do not use a general Turing-complete mechanism to express their syntax. Rather they use a restricted computational model. One of the most popular choices for such models is context free grammars.
To explain context free grammars, let us begin with a canonical example.
Consider the function
-
A digit is one of the symbols
$0,1,2,3,4,5,6,7,8,9$ . -
A number is a sequence of digits. (For simplicity we drop the condition that the sequence does not have a leading zero, though it is not hard to encode it in a context-free grammar as well.)
-
An operation is one of
$+,-,\times,\div$ -
An expression has either the form "number", the form "sub-expression1 operation sub-expression2", or the form "(sub-expression1)", where "sub-expression1" and "sub-expression2" are themselves expressions. (Note that this is a recursive definition.)
A context free grammar (CFG) is a formal way of specifying such conditions.
A CFG consists of a set of rules that tell us how to generate strings from smaller components.
In the above example, one of the rules is "if
::: {.definition title="Context Free Grammar" #defcfg}
Let
-
$V$ , known as the variables, is a set disjoint from$\Sigma$ . -
$s\in V$ is known as the initial variable. -
$R$ is a set of rules. Each rule is a pair$(v,z)$ with$v\in V$ and$z\in (\Sigma \cup V)^*$ . We often write the rule$(v,z)$ as$v \Rightarrow z$ and say that the string$z$ can be derived from the variable$v$ . :::
::: {.example title="Context free grammar for arithmetic expressions" #cfgarithmeticex} The example above of well-formed arithmetic expressions can be captured formally by the following context free grammar:
-
The alphabet
$\Sigma$ is${ (,),+,-,\times,\div,0,1,2,3,4,5,6,7,8,9}$ -
The variables are
$V = { expression ;,; number ;,; digit ;,; operation }$ . -
The rules are the set
$R$ containing the following$19$ rules:-
The
$4$ rules$operation \Rightarrow +$ ,$operation \Rightarrow -$ ,$operation \Rightarrow \times$ , and$operation \Rightarrow \div$ . -
The
$10$ rules$digit \Rightarrow 0$ ,$\ldots$,$digit \Rightarrow 9$ . -
The rule
$number \Rightarrow digit$ . -
The rule
$number \Rightarrow digit; number$ . -
The rule
$expression \Rightarrow number$ . -
The rule
$expression \Rightarrow expression ; operation ; expression$ . -
The rule
$expression \Rightarrow (expression)$ .
-
-
The starting variable is
$expression$ :::
People use many different notations to write context free grammars.
One of the most common notations is the Backus–Naur form.
In this notation we write a rule of the form <v> := a
.
If we have several rules of the form <v> := a|b|c
.
(In words we say that
operation := +|-|*|/
digit := 0|1|2|3|4|5|6|7|8|9
number := digit|digit number
expression := number|expression operation expression|(expression)
Another example of a context free grammar is the "matching parentheses" grammar, which can be represented in Backus-Naur as follows:
match := ""|match match|(match)
A string over the alphabet (
,)
match
is the starting expression and ""
corresponds to the empty string) if and only if it consists of a matching set of parentheses.
In contrast, by regexpparn{.ref} there is no regular expression that matches a string
We can think of a context-free grammar over the alphabet
::: {.definition title="Deriving a string from a grammar" #CFGderive}
If
We say that
We say that $x\in \Sigma^$ is matched by $G=(V,R,s)$ if $x$ can be derived from the starting variable $s$ (i.e., if $s \Rightarrow_G^ x$).
We define the function computed by
A priori it might not be clear that the map
For every CFG
As usual we restrict attention to grammars over
::: {.proof #proofofcfghalt data-ref="CFGhalt"}
We only sketch the proof.
We start with the observation we can convert every CFG to an equivalent version of Chomsky normal form, where all rules either have the form
The idea behind such a transformation is to simply add new variables as needed, and so for example we can translate a rule such as
Using the Chomsky Normal form we get a natural recursive algorithm for computing whether
::: {.remark title="Parse trees" #parsetreesrem}
While we focus on the task of deciding whether a CFG matches a string, the algorithm to compute
Often the first step in a compiler or interpreter for a programming language is a parser that transforms the source into the parse tree (also known as the abstract syntax tree). There are also tools that can automatically convert a description of a context-free grammars into a parser algorithm that computes the parse tree of a given string. (Indeed, the above recursive algorithm can be used to achieve this, but there are much more efficient versions, especially for grammars that have particular forms, and programming language designers often try to ensure their languages have these more efficient grammars.) :::
Context free grammars can capture every regular expression:
Let
::: {.proof #proofofCFGreg data-ref="CFGreg"}
We prove the theorem by induction on the length of
In case 1, we can define the new grammar as follows: we add a new starting variable
We leave it to the reader as (a very good!) exercise to verify that in all three cases the grammars we produce capture the same function as the original expression. :::
It turns out that CFG's are strictly more powerful than regular expressions.
In particular, as we've seen, the "matching parentheses" function
::: {.solvedexercise title="Context free grammar for palindromes" #reversedstringcfg}
Let
::: {.solution data-ref="reversedstringcfg"}
A simple grammar computing
start := ; | 0 start 0 | 1 start 1
One can prove by induction that this grammar generates exactly the strings
A more interesting example is computing the strings of the form
::: {.solvedexercise title="Non-palindromes" #nonpalindrome}
Prove that there is a context free grammar that computes
::: {.solution data-ref="nonpalindrome"} Using Backus–Naur notation we can describe such a grammar as follows
palindrome := ; | 0 palindrome 0 | 1 palindrome 1
different := 0 palindrome 1 | 1 palindrome 0
start := different | 0 start | 1 start | start 0 | start 1
In words, this means that we can characterize a string
where palindrome
variable), then adding different
variable), and then we can add arbitrary number of start
variable).
:::
Even though context-free grammars are more powerful than regular expressions, there are some simple languages that are not captured by context free grammars. One tool to show this is the context-free grammar analog of the "pumping lemma" (pumping{.ref}):
Let
::: { .pause } The context-free pumping lemma is even more cumbersome to state than its regular analog, but you can remember it as saying the following: "If a long enough string is matched by a grammar, there must be a variable that is repeated in the derivation." :::
::: {.proof #proofofcfgpumping data-ref="cfgpumping"}
We only sketch the proof. The idea is that if the total number of symbols in the rules of the grammar is
Thus by the definition of the grammar, we can repeat the derivation to replace the substring
Using cfgpumping{.ref} one can show that even the simple function
::: {.solvedexercise title="Equality is not context-free" #equalisnotcfg}
Let
::: {.solution data-ref="equalisnotcfg"}
We use the context-free pumping lemma.
Suppose towards the sake of contradiction that there is a grammar
Consider the string
Firstly, unless
As in the case of regular expressions, the limitations of context free grammars do provide some advantages. For example, emptiness of context free grammars is decidable:
There is an algorithm that on input a context-free grammar
The proof is easier to see if we transform the grammar to Chomsky Normal Form as in CFGhalt{.ref}. Given a grammar
::: {.proof data-ref="cfgemptinessthem"}
We assume that the grammar
-
We start by marking all variables
$v$ that are involved in a rule of the form$v \Rightarrow \sigma$ as non-empty. -
We then continue to mark
$v$ as non-empty if it is involved in a rule of the form$v \Rightarrow uw$ where$u,w$ have been marked before.
We continue this way until we cannot mark any more variables.
We then declare that the grammar is empty if and only if
By analogy to regular expressions, one might have hoped to get an algorithm for deciding whether two given context free grammars are equivalent. Alas, no such luck. It turns out that the equivalence problem for context free grammars is uncomputable. This is a direct corollary of the following theorem:
For every set
fullnesscfgdef{.ref} immediately implies that equivalence for context-free grammars is uncomputable, since computing "fullness" of a grammar
::: {.proofidea data-ref="fullnesscfgdef"}
We prove the theorem by reducing from the Halting problem.
To do that we use the notion of configurations of NAND-TM programs, as defined in configtmdef{.ref}.
Recall that a configuration of a program
We define
The heart of the proof is to show that
::: {.proof data-ref="fullnesscfgdef"}
We only sketch the proof.
We will show that if we can compute
Recall that a configuration of Turing machine
-
A configuration can be encoded by a binary string
$\sigma \in {0,1}^*$ . -
The initial configuration of
$M$ on the input$0$ is some fixed string. -
A halting configuration will have the value a certain state (which can be easily "read off" from it) set to
$1$ . -
If
$\sigma$ is a configuration at some step$i$ of the computation, we denote by$NEXT_M(\sigma)$ as the configuration at the next step.$NEXT_M(\sigma)$ is a string that agrees with$\sigma$ on all but a constant number of coordinates (those encoding the position corresponding to the head position and the two adjacent ones). On those coordinates, the value of$NEXT_M(\sigma)$ can be computed by some finite function.
We will let the alphabet
We now define
We will show the following claim:
CLAIM:
The claim implies the theorem. Since
We now turn to the proof of the claim.
We will not show all the details, but the main point
-
$L$ is not of the right format, i.e. not of the form$\langle \text{binary-string} \rangle # \langle \text{binary-string} \rangle | \langle \text{binary-string} \rangle # \cdots$ . -
$L$ contains a substring of the form$| \sigma # \sigma' |$ such that$\sigma' \neq rev(NEXT_P(\sigma))$ -
$L$ contains a substring of the form$# \sigma | \sigma' #$ such that$\sigma' \neq NEXT_P(rev(\sigma))$
Since context-free functions are closed under the OR operation, the claim will follow if we show that we can verify conditions 1, 2 and 3 via a context-free grammar.
For condition 1 this is very simple: checking that
For conditions 2 and 3, this follows via very similar reasoning to that showing that the function
To summarize, we can often trade expressiveness of the model for amenability to analysis. If we consider computational models that are not Turing complete, then we are sometimes able to bypass Rice's Theorem and answer certain semantic questions about programs in such models. Here is a summary of some of what is known about semantic questions for the different models we have seen.
---
caption: 'Computability of semantic properties'
alignment: ''
table-width: ''
id: semantictable
---
_Model_, **Halting**, **Emptiness**, **Equivalence**
_Regular expressions_, Computable, Computable,Computable
_Context free grammars_, Computable, Computable, Uncomputable
_Turing-complete models_, Uncomputable, Uncomputable, Uncomputable
- The uncomputability of the Halting problem for general models motivates the definition of restricted computational models.
- In some restricted models we can answer semantic questions such as: does a given program terminate, or do two programs compute the same function?
- Regular expressions are a restricted model of computation that is often useful to capture tasks of string matching. We can test efficiently whether an expression matches a string, as well as answer questions such as Halting and Equivalence.
- Context free grammars is a stronger, yet still not Turing complete, model of computation. The halting problem for context free grammars is computable, but equivalence is not computable.
::: {.exercise title="Closure properties of context-free functions" #closurecfgex}
Suppose that
-
$H(x) = F(x) \vee G(x)$ . -
$H(x) = F(x) \wedge G(x)$ -
$H(x) = NAND(F(x),G(x))$ . -
$H(x) = F(x^R)$ where$x^R$ is the reverse of$x$ :$x^R = x_{n-1}x_{n-2} \cdots x_o$ for$n=|x|$ . -
$H(x) = \begin{cases}1 & x=uv \text{ s.t. } F(u)=G(v)=1 \ 0 & \text{otherwise} \end{cases}$
-
$H(x) = \begin{cases}1 & x=uu \text{ s.t. } F(u)=G(u)=1 \ 0 & \text{otherwise} \end{cases}$
-
$H(x) = \begin{cases}1 & x=uu^R \text{ s.t. } F(u)=G(u)=1 \ 0 & \text{otherwise} \end{cases}$ :::
::: {.exercise #noncontextfreeex}
Prove that the function
::: {.exercise title="Syntax for programming languages" #proglanguagecfgex} Consider the following syntax of a "programming language" whose source can be written using the ASCII character set:
-
Variables are obtained by a sequence of letters, numbers and underscores, but can't start with a number.
-
A statement has either the form
foo = bar;
wherefoo
andbar
are variables, or the formIF (foo) BEGIN ... END
where...
is list of one or more statements, potentially separated by newlines.
A program in our language is simply a sequence of statements (possibly separated by newlines or spaces).
-
Let
$VAR:{0,1}^* \rightarrow {0,1}$ be the function that given a string$x\in {0,1}^*$ , outputs$1$ if and only if$x$ corresponds to an ASCII encoding of a valid variable identifier. Prove that$VAR$ is regular. -
Let
$SYN:{0,1}^* \rightarrow {0,1}$ be the function that given a string$s \in {0,1}^*$ , outputs$1$ if and only if$s$ is an ASCII encoding of a valid program in our language. Prove that$SYN$ is context free. (You do not have to specify the full formal grammar for$SYN$ , but you need to show that such a grammar exists.) -
Prove that
$SYN$ is not regular. See footnote for hint^[Try to see if you can "embed" in some way a function that looks similar to$MATCHPAREN$ in$SYN$ , so you can use a similar proof. Of course for a function to be non-regular, it does not need to utilize literal parentheses symbols.] :::
As in the case of regular expressions, there are many resources available that cover context-free grammar in great detail. Chapter 2 of [@SipserBook] contains many examples of context-free grammars and their properties. There are also websites such as Grammophone where you can input grammars, and see what strings they generate, as well as some of the properties that they satisfy.
The adjective "context free" is used for CFG's because a rule of the form
The Chomsky Hierarchy is a hierarchy of grammars from the least restrictive (most powerful) Type 0 grammars, which correspond to recursively enumerable languages (see recursiveenumerableex{.ref}) to the most restrictive Type 3 grammars, which correspond to regular languages.
Context-free languages correspond to Type 2 grammars.
Type 1 grammars are context sensitive grammars.
These are more powerful than context-free grammars but still less powerful than Turing machines.
In particular functions/languages corresponding to context-sensitive grammars are always computable, and in fact can be computed by a linear bounded automatons which are non-deterministic algorithms that take