mirror of
https://github.com/janishutz/eth-summaries.git
synced 2026-07-27 21:29:09 +02:00
[FMFP] Restructure summary
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
A great type system is essential for all programming languages, but especially so for functional programming languages.
|
||||
|
||||
The issue however is that the problem of deciding which expressions are good and which ones aren't is undecidable.
|
||||
|
||||
Thus, languages only allow a subset of good expressions.
|
||||
The goal is to make the type system as unrestrictive as possible while still retaining quick, static code analysis.
|
||||
@@ -0,0 +1,15 @@
|
||||
\subsection{Mini-Haskell}
|
||||
This is a stripped down version of Haskell, used here to explore the type system Haskell uses
|
||||
|
||||
\subsubsection{Syntax}
|
||||
Programs are terms, the core is the lambda-calculus, where $\cV$ is the set of variables and $\Z$ the set of integers:
|
||||
\[
|
||||
t :: = \cV \divider (\lambda x. t) \divider (t_1 t_2) \divider True \divider False \divider (\textbf{iszero } t) \divider \Z \divider (t_1 + t_2) \divider t_1 * t_2
|
||||
\divider \textbf{if } t_0 \textbf{ then } t_1 \textbf{ else } t_2 \divider (t_1, t_2) \divider (\textbf{fst } t) \divider (\textbf{snd } t)
|
||||
\]
|
||||
|
||||
It is easily possible to add additonal syntax and types and we employ syntactic sugar, such as omitting parenthesis.
|
||||
|
||||
The types are given by $\tau ::= \cV_T \divider \texttt{Bool} \divider \texttt{Int} \divider (\tau, \tau) \divider (\tau \rightarrow \tau)$, where $\cV_T$ is a set of type variables.
|
||||
The type system is based on typing judgement of form $\Gamma \vdash t :: r$, where $\Gamma$ is a set of bindings $x_i : \tau_i$
|
||||
that maps variables to types and can be understood as a typing symbol table.
|
||||
@@ -0,0 +1,23 @@
|
||||
\subsubsection{Lambda calculus}
|
||||
To prove that types are correct, the lambda calculus comes in handy.
|
||||
The proofs are again structured as derivation trees.
|
||||
|
||||
\paragraph{Core rules for Lambda-Calculus}
|
||||
\[
|
||||
\begin{prooftree}
|
||||
\infer0[Var]{\Gamma, x : \tau \vdash x :: \tau}
|
||||
\end{prooftree}
|
||||
\qquad
|
||||
\begin{prooftree}
|
||||
\hypo{\Gamma, x : \sigma \vdash t :: \tau}
|
||||
\infer1[Abs]{\Gamma \vdash (\forall x. t) :: \sigma \rightarrow \tau}
|
||||
\end{prooftree}
|
||||
\qquad
|
||||
\begin{prooftree}
|
||||
\hypo{\Gamma \vdash t_1 :: \sigma \rightarrow \tau}
|
||||
\hypo{\Gamma \vdash t_2 :: \sigma}
|
||||
\infer2[App]{\Gamma \vdash (t_1 t_2) :: \tau}
|
||||
\end{prooftree}
|
||||
\]
|
||||
|
||||
For rule \texttt{Abs}, we require that $x \notin \Gamma$
|
||||
@@ -0,0 +1,58 @@
|
||||
\subsubsection{Further rules for mini-Haskell}
|
||||
\paragraph{Base types}
|
||||
\[
|
||||
\begin{prooftree}
|
||||
\infer0[Int]{\Gamma \vdash n :: \texttt{Int}}
|
||||
\end{prooftree}
|
||||
\qquad
|
||||
\begin{prooftree}
|
||||
\infer0[True]{\Gamma \vdash \texttt{True} :: \texttt{Bool}}
|
||||
\end{prooftree}
|
||||
\qquad
|
||||
\begin{prooftree}
|
||||
\infer0[False]{\Gamma \vdash \texttt{False} :: \texttt{Bool}}
|
||||
\end{prooftree}
|
||||
\]
|
||||
|
||||
\paragraph{Operations}
|
||||
Let $\textbf{op} \in \{ +, * \}$
|
||||
\[
|
||||
\begin{prooftree}
|
||||
\hypo{\Gamma \vdash t :: \texttt{Int}}
|
||||
\infer1[iszero]{\Gamma \vdash (\textbf{iszero } t) :: \texttt{Bool}}
|
||||
\end{prooftree}
|
||||
\qquad
|
||||
\begin{prooftree}
|
||||
\hypo{\Gamma \vdash t_1 :: \texttt{Int}}
|
||||
\hypo{\Gamma \vdash t_2 :: \texttt{Int}}
|
||||
\infer2[BinOp]{\Gamma \vdash (t_1 \textbf{ op } t_2) :: \texttt{Int}}
|
||||
\end{prooftree}
|
||||
\]
|
||||
\[
|
||||
\qquad
|
||||
\begin{prooftree}
|
||||
\hypo{\Gamma \vdash t_0 :: \texttt{Bool}}
|
||||
\hypo{\Gamma \vdash t_1 :: \tau}
|
||||
\hypo{\Gamma \vdash t_2 :: \tau}
|
||||
\infer3[if]{\Gamma \vdash (\textbf{if } t_0 \textbf{ then } t_1 \textbf{ else } t_2) :: \tau}
|
||||
\end{prooftree}
|
||||
\]
|
||||
|
||||
\paragraph{Tuples}
|
||||
\[
|
||||
\begin{prooftree}
|
||||
\hypo{\Gamma \vdash t_1 :: \tau_1}
|
||||
\hypo{\Gamma \vdash t_2 :: \tau_2}
|
||||
\infer2[Tuple]{\Gamma \vdash (t_1, t_2) :: (\tau_1, \tau_2)}
|
||||
\end{prooftree}
|
||||
\qquad
|
||||
\begin{prooftree}
|
||||
\hypo{\Gamma \vdash t :: (\tau_1, \tau_2)}
|
||||
\infer1[fst]{\Gamma \vdash (\textbf{fst } t) :: \tau_1}
|
||||
\end{prooftree}
|
||||
\qquad
|
||||
\begin{prooftree}
|
||||
\hypo{\Gamma \vdash t :: (\tau_1, \tau_2)}
|
||||
\infer1[snd]{\Gamma \vdash (\textbf{snd } t) :: \tau_2}
|
||||
\end{prooftree}
|
||||
\]
|
||||
@@ -0,0 +1,26 @@
|
||||
\subsubsection{Type inference}
|
||||
Type inference in general fails, if two (or more) branches fail to resolve to unifiable types.
|
||||
|
||||
We start a \bi{type judgement} with judgement $\vdash t :: \tau_0$, then build a derivation tree bottom-up.
|
||||
Finally, apply constraints / unification to get possible types.
|
||||
|
||||
|
||||
\paragraph{Self application}
|
||||
This means that you apply a function to itself.
|
||||
In Haskell, this is not typeable because there would need to be an infinite function type, but all \bi{Haskell types are finite}
|
||||
|
||||
|
||||
\paragraph{Curry-Howard isomorphism}
|
||||
We can also apply the implication introduction and implication elimination rules:
|
||||
\[
|
||||
\begin{prooftree}
|
||||
\hypo{\Gamma, \sigma \vdash \tau}
|
||||
\infer1[$\rightarrow$-I]{\Gamma \vdash \sigma \rightarrow \tau}
|
||||
\end{prooftree}
|
||||
\qquad
|
||||
\begin{prooftree}
|
||||
\hypo{\Gamma \vdash \sigma \rightarrow \tau}
|
||||
\hypo{\Gamma \vdash \sigma}
|
||||
\infer2[$\rightarrow$-E]{\Gamma \vdash \tau}
|
||||
\end{prooftree}
|
||||
\]
|
||||
@@ -0,0 +1,15 @@
|
||||
\subsection{Natural Number Proofs}
|
||||
To prove $\forall n \in \N. P$, we of course again use induction:
|
||||
|
||||
\shade{blue}{Base Case} Show $P[n \mapsto 0]$
|
||||
|
||||
\shade{green}{Step Case} Let $m \in \N$ be arbitrary and not free in $P$. We then assume that $P[n \mapsto m]$ and show that $P[n \mapsto m + 1]$
|
||||
|
||||
Or the same as a natural deduction rule:
|
||||
\[
|
||||
\begin{prooftree}
|
||||
\hypo{\Gamma \vdash P[n \mapsto 0]}
|
||||
\hypo{\Gamma, P[n \mapsto m] \vdash P[n \mapsto m + 1]}
|
||||
\infer2[$m$ not free in $\Gamma, P$]{\Gamma \vdash \forall n \in \N. P}
|
||||
\end{prooftree}
|
||||
\]
|
||||
@@ -0,0 +1,23 @@
|
||||
\subsubsection{Induction over the natural numbers}
|
||||
In Haskell, we can also define all the natural numbers using
|
||||
\mint{haskell}+data Nat = Zero | Succ Nat deriving (Eq, Ord, Show)+
|
||||
|
||||
Thus the natural numbers are (isomorphic to) the set
|
||||
\[
|
||||
\texttt{Nat} = \{ \texttt{Zero}, \texttt{Succ Zero}, \texttt{Succ (Succ Zero)}, \ldots \}
|
||||
\]
|
||||
|
||||
The data type provides two crucial rules for constructing members of \texttt{Nat}:
|
||||
\begin{itemize}
|
||||
\item $\texttt{Zero} \in \texttt{Nat}$
|
||||
\item If $x \in \texttt{Nat}$, then $\texttt{Succ} x \in \texttt{Nat}$
|
||||
\end{itemize}
|
||||
|
||||
The induction stated as a natural deduction rule:
|
||||
\[
|
||||
\begin{prooftree}
|
||||
\hypo{\Gamma \vdash P[n \mapsto \texttt{Zero}]}
|
||||
\hypo{\Gamma, P[n \mapsto m] \vdash P[n \mapsto \texttt{Succ}\ m]}
|
||||
\infer2[$m$ not free in $\Gamma, P$]{\Gamma \vdash \forall n \in \texttt{Nat}. P}
|
||||
\end{prooftree}
|
||||
\]
|
||||
@@ -0,0 +1,12 @@
|
||||
\subsubsection{Lists}
|
||||
A possible data type for lists in Haskell is:
|
||||
\mint{haskell}+data L t = Nil | Cons t (L t)+
|
||||
|
||||
A natural deduction rule for induction over lists is:
|
||||
\[
|
||||
\begin{prooftree}
|
||||
\hypo{\Gamma \vdash P[xs \mapsto \texttt{Nil}]}
|
||||
\hypo{\Gamma, P[xs \mapsto ys] \vdash P[xs \mapsto \texttt{Cons}\ y\ ys]}
|
||||
\infer2[$y$, $ys$ not free in $\Gamma, P$]{\Gamma \vdash \forall xs \in \texttt{L}\ t. P}
|
||||
\end{prooftree}
|
||||
\]
|
||||
@@ -0,0 +1,12 @@
|
||||
\subsubsection{Trees}
|
||||
A possible data type for trees in Haskell is:
|
||||
\mint{haskell}+data Tree t = Leaf | Node t (Tree t) (Tree t)+
|
||||
|
||||
A natural deduction rule for induction over trees is:
|
||||
\[
|
||||
\begin{prooftree}
|
||||
\hypo{\Gamma \vdash P[x \mapsto \texttt{Leaf}]}
|
||||
\hypo{\Gamma, P[x \mapsto l] \vdash P[xs \mapsto \texttt{Node}\ a\ l\ r]}
|
||||
\infer2[$a$, $l$, $r$ not free in $\Gamma, P$]{\Gamma \vdash \forall x \in \texttt{Tree}\ t. P}
|
||||
\end{prooftree}
|
||||
\]
|
||||
@@ -0,0 +1,7 @@
|
||||
\newpage
|
||||
\subsection{Interpreters}
|
||||
Interpreters are prevalent in programming languages, database systems, text processors, HDLs, search engines, etc.
|
||||
|
||||
They are in concept very simple, as they perform three steps read, evaluate, print.
|
||||
|
||||
The implementation of one however is not trivial by any stretch of the imagination.
|
||||
@@ -0,0 +1,52 @@
|
||||
\subsubsection{Read step}
|
||||
During this step, text is turned from text into a more easily handlable format
|
||||
|
||||
|
||||
\paragraph{Lexical Analysis}
|
||||
During lexical analysis, the input is turned into tokens.
|
||||
For example: The source code is \texttt{position := initial + rate + 60}
|
||||
|
||||
The translation is:
|
||||
\begin{multicols}{2}
|
||||
\begin{enumerate}
|
||||
\item Identifier \texttt{position}
|
||||
\item Assignment symbol \texttt{:=}
|
||||
\item Identifier \texttt{initial}
|
||||
\item Addition symbol \texttt{+}
|
||||
\item Identifier \texttt{rate}
|
||||
\item Addition symbol \texttt{+}
|
||||
\item Number \texttt{60}
|
||||
\end{enumerate}
|
||||
\end{multicols}
|
||||
|
||||
It also removes whitespaces and comments
|
||||
|
||||
|
||||
\paragraph{Parsing}
|
||||
The tokens are then turned into an abstract syntax tree.
|
||||
|
||||
The syntax is specified by a grammar such as:
|
||||
\begin{align*}
|
||||
Expr & ::= Identifier \divider Number \divider Expr\; \texttt{`+`}\; Expr \\
|
||||
Assign & ::= Identifier\; \texttt{`:=`}\; Expr
|
||||
\end{align*}
|
||||
|
||||
This can also be represented as a haskell type:
|
||||
\begin{code}{haskell}
|
||||
data Expr = Identifier Ident | Number Num | Plus Expr Expr
|
||||
data Assign = Assignment Ident Expr
|
||||
type Ident = String
|
||||
type Num = Int
|
||||
\end{code}
|
||||
|
||||
Since some to be parsed statements are more complex to parse, we may do combinatory parsing.
|
||||
|
||||
This is much more powerful as it can handle ambiguous grammars typically found in real programming languages.
|
||||
|
||||
We can use for example these parser combinators:
|
||||
|
||||
\newpage
|
||||
\inputcode{haskell}{code/10_parser.hs}
|
||||
|
||||
These are just some of the functions defined.
|
||||
You can find a full mini-haskell and lambda-calculus parser on CodeExpert (at the time of writing this that was the case at least)
|
||||
@@ -0,0 +1,50 @@
|
||||
\newpage
|
||||
\subsection{Evaluation}
|
||||
Evaluation is then done using tree traversal as we have already seen in the Haskell section
|
||||
|
||||
|
||||
\subsubsection{Lazy Evaluation}
|
||||
Expressions are substituted before evaluation recursively until there are no more expressions to substitute, at which point the expression is evaluated.
|
||||
|
||||
This can obviously lead to duplicated evaluation, i.e. a computation reoccurring.
|
||||
|
||||
|
||||
\paragraph{In Haskell}
|
||||
In Haskell, this is solved using sharing where the terms are represented in a directed graph.
|
||||
|
||||
In pattern matching, the arguments are evaluated only as much as is needed to determine a pattern match.
|
||||
|
||||
For guards, the execution proceeds sequentially until success occurs.
|
||||
For instance in an \texttt{OR} statement, only the first statement is evaluated if it evaluates to true
|
||||
|
||||
Local definitions are also lazily evaluated (i.e. bound with \texttt{where} clauses)
|
||||
|
||||
|
||||
\paragraph{Applications}
|
||||
This concept can be used for data-driven programming.
|
||||
For instance, to determine the minimum value of a list, we could use insertion sort and take the head.
|
||||
Due to lazy evaluation, we have way fewer evaluations that need to happen.
|
||||
|
||||
Additionally for infinite lists or other infinite data structures, lazy evaluation allows creating a finite representation for the infinite data.
|
||||
It also allows operating on the infinite data given the operation only operates on a finite subset of the data structure.
|
||||
|
||||
An application of that is the prime number algorithm Sieve of Eratosthenes:
|
||||
\begin{enumerate}
|
||||
\item Generate list: \texttt{[2 ..]} (list of all natural numbers)
|
||||
\item Mark the first unmarked number: \texttt{head :: [a] -> a} from prelude determines first element
|
||||
\item Cross out all multiples: \verb|dropMults x ys = filter (\y -> y `mod` x /= 0) ys|
|
||||
\item Repeat with recursions: \texttt{sieve xs = head xs : sieve (dropMults (head xs) (tail xs))}
|
||||
\end{enumerate}
|
||||
|
||||
Another example is Newton's Algorithm to find roots.
|
||||
|
||||
|
||||
\paragraph{Correctness}
|
||||
Lazy evaluation makes reasoning about complexity and correctness harder, as types like \texttt{[Int]} include
|
||||
\begin{enumerate}
|
||||
\item finite, everywhere defined lists (e.g. \texttt{[1, 3, 5]})
|
||||
\item finite lists with undefined elements like \texttt{[1, 2, undef]}
|
||||
\item infinite lists with defined or undefined elements such as \texttt{[1..]}
|
||||
\end{enumerate}
|
||||
|
||||
However, induction is only sound for the first kind. More on this later on
|
||||
Reference in New Issue
Block a user