%!TEX root = Programmierparadigmen.tex
\chapter{Programmiersprachen}
\begin{definition}\xindex{Programmiersprache}\xindex{Programm}%
    Eine \textbf{Programmiersprache} ist eine formale Sprache, die durch eine
    Spezifikation definiert wird und mit der Algorithmen beschrieben werden
    können. Elemente dieser Sprache heißen \textbf{Programme}.
\end{definition}

Ein Beispiel für eine Sprachspezifikation ist die \textit{Java Language Specification}.\footnote{Zu finden unter \url{http://docs.oracle.com/javase/specs/}} Obwohl es kein guter Stil ist, ist auch
eine Referenzimplementierung eine Form der Spezifikation.

Im Folgenden wird darauf eingegangen, anhand welcher Kriterien man 
Programmiersprachen unterscheiden kann.

\section{Abstraktion}
Wie nah an den physikalischen Prozessen im Computer ist die Sprache?
Wie nah ist sie an einer mathematisch / algorithmischen Beschreibung?

\begin{definition}\xindex{Maschinensprache}\xindex{Befehlssatz}%
    Eine \textbf{Maschinensprache} beinhaltet ausschließlich Instruktionen, die direkt
    von einer CPU ausgeführt werden können. Die Menge dieser Instruktionen 
    sowie deren Syntax wird \textbf{Befehlssatz} genannt.
\end{definition}

\begin{beispiel}[Maschinensprachen]
    \begin{bspenum}
        \item \xindex{x86}x86
        \item \xindex{SPARC}SPARC
    \end{bspenum}
\end{beispiel}

\begin{definition}[Assembler]\xindex{Assembler}%
    Eine Assemblersprache ist eine Programmiersprache, deren Befehle dem
    Befehlssatz eines Prozessor entspricht.
\end{definition}

\begin{beispiel}[Assembler]%
    Folgendes Beispiel stammt von \url{https://de.wikibooks.org/wiki/Assembler-Programmierung_für_x86-Prozessoren/_Das_erste_Assemblerprogramm}:
    \inputminted[linenos, numbersep=5pt, tabsize=4, frame=lines, label=firstp.asm]{nasm}{scripts/assembler/firstp.asm}
\end{beispiel}

\begin{definition}[Höhere Programmiersprache]\xindex{Programmiersprache!höhere}%
    Eine Programmiersprache heißt \textit{höher}, wenn sie nicht ausschließlich
    für eine Prozessorarchitektur geschrieben wurde und turing-vollständig ist.
\end{definition}

\begin{beispiel}[Höhere Programmiersprachen]
    Java, Python, Haskell, Ruby, TCL, \dots
\end{beispiel}

\begin{definition}[Domänenspezifische Sprache]\xindex{Sprache!domänenspezifische}%
    Eine domänenspezifische Sprache (engl. domain-specific language; kurz DSL) 
    ist eine formale Sprache, die für ein bestimmtes Problemfeld 
    entworfen wurde.
\end{definition}

\begin{beispiel}[Domänenspezifische Sprache]
    \begin{bspenum}
        \item HTML
        \item VHDL
    \end{bspenum}
\end{beispiel}

\section{Paradigmen}
Eine weitere Art, wie man Programmiersprachen unterscheiden
kann ist das sog. \enquote{Programmierparadigma}, also die Art wie
man Probleme löst.

\begin{definition}[Imperatives Paradigma]\xindex{Programmierung!imperative}%
    In der \textit{imperativen Programmierung} betrachtet man Programme als
    eine Folge von Anweisungen, die vorgibt auf welche Art etwas 
    Schritt für Schritt gemacht werden soll.
\end{definition}

\begin{beispiel}[Imperative Programmierung]
    In folgenden Programm erkennt man den imperativen Programmierstil vor allem
    an den Variablenzuweisungen:
    \inputminted[numbersep=5pt, tabsize=4]{c}{scripts/c/fibonacci-imperativ.c}
\end{beispiel}

\begin{definition}[Prozedurales Paradigma]\xindex{Programmierung!prozedurale}%
    Die prozeduralen Programmierung ist eine Erweiterung des imperativen
    Programmierparadigmas, bei dem man versucht die Probleme in 
    kleinere Teilprobleme zu zerlegen.
\end{definition}

\begin{definition}[Funktionales Paradigma]\xindex{Programmierung!funktionale}%
    In der funktionalen Programmierung baut man auf Funktionen und
    ggf. Funktionen höherer Ordnung, die eine Aufgabe ohne Nebeneffekte
    lösen.
\end{definition}

\begin{beispiel}[Funktionale Programmierung]
    Der Funktionale Stil kann daran erkannt werden, dass keine Werte zugewiesen werden:
    \inputminted[numbersep=5pt, tabsize=4]{haskell}{scripts/haskell/fibonacci-akk.hs}
\end{beispiel}

Haskell ist eine funktionale Programmiersprache, C ist eine
nicht-funktionale Programmiersprache.

Wichtige Vorteile von funktionalen Programmiersprachen sind:
\begin{itemize}
    \item Sie sind weitgehend (jedoch nicht vollständig) frei von Seiteneffekten.
    \item Der Code ist häufig sehr kompakt und manche Probleme lassen
          sich sehr elegant formulieren.
\end{itemize}

\begin{definition}[Logisches Paradigma]\xindex{Programmierung!logische}%
    Das \textbf{logische Programmierparadigma} baut auf der formalen Logik auf. 
    Man verwendet \textbf{Fakten} und \textbf{Regeln}
    und einen Inferenzalgorithmus um Probleme zu lösen.
\end{definition}

Der Inferenzalgorithmus kann z.~B. die Unifikation nutzen.

\begin{beispiel}[Logische Programmierung]
    Obwohl die logische Programmierung für Zahlenfolgen weniger geeignet erscheint,
    sei hier zur Vollständigkeit das letzte Fibonacci-Beispiel in Prolog:
    \inputminted[numbersep=5pt, tabsize=4]{prolog}{scripts/prolog/fibonacci.pl}
\end{beispiel}

\section{Typisierung}
Programmiersprachen können anhand der Art ihrer Typisierung unterschieden werden.

\begin{definition}[Typisierungsstärke]\xindex{Typisierungsstärke}%
    Es seien $X, Y$ Programmiersprachen.

    $X$ heißt stärker typisiert als $Y$, wenn $X$ mehr bzw. nützlichere Typen hat als $Y$.
\end{definition}

\begin{beispiel}[Typisierungsstärke]
    Die stärke der Typisierung ist abhängig von dem Anwendungszenario. So hat C im 
    Gegensatz zu Python, Java oder Haskell beispielsweise keine booleschen Datentypen.

    Im Gegensatz zu Haskell hat Java keine GADTs\footnote{generalized algebraic data type}.
\end{beispiel}

\begin{definition}[Polymorphie]\xindex{Polymorphie}%
    \begin{defenum}
        \item Ein Typ heißt polymorph, wenn er mindestens einen Parameter hat.
        \item Eine Funktion heißt polymorph, wenn ihr Verhalten nicht von dem
              konkreten Typen der Parameter abhängt.
    \end{defenum}
\end{definition}

\begin{beispiel}[Polymorphie]
    In Java sind beispielsweise Listen polymorphe Typen:

    \inputminted[numbersep=5pt, tabsize=4]{java}{scripts/java/list-example.java}

    Entsprechend sind auf Listen polymorphe Operationen wie \texttt{add} und
    \texttt{remove} definiert.
\end{beispiel}

\begin{definition}[Statische und dynamische Typisierung]\xindex{Typisierung!statische}\xindex{Typisierung!dynamische}%
    \begin{defenum}
        \item Eine Programmiersprache heißt \textbf{statisch typisiert}, wenn eine 
              Variable niemals ihren Typ ändern kann.
        \item Eine Programmiersprache heißt \textbf{dynamisch typisiert}, wenn eine 
              Variable ihren Typ ändern kann.
    \end{defenum}
\end{definition}

Beispiele für statisch typisierte Sprachen sind C, Haskell und Java.
Beispiele für dynamisch typisierte Sprachen sind Python und PHP.
\goodbreak
Vorteile statischer Typisierung sind:

\begin{itemize}
    \item \textbf{Performance}: Der Compiler kann mehr Optimierungen vornehmen.
    \item \textbf{Syntaxcheck}: Da der Compiler die Typen zur Compile-Zeit überprüft,
                                gibt es in statisch typisierten Sprachen zur
                                Laufzeit keine Typfehler.
\end{itemize}

Vorteile dynamischer Typisierung sind:

\begin{itemize}
    \item Manche Ausdrücke, wie der Y-Combinator in Haskell, lassen sich nicht
          typisieren.
\end{itemize}

Der Gedanke bei dynamischer Typisierung ist, dass Variablen keine Typen haben.
Nur Werte haben Typen. Man stellt sich also Variablen eher als Beschriftungen für
Werte vor. Bei statisch typisierten Sprachen stellt man sich hingegen Variablen
als Container vor.

\begin{definition}[Explizite und implizite Typisierung]\xindex{Typisierung!explizite}\xindex{Typisierung!implizite}%
    Sei $X$ eine Programmiersprache.
    \begin{defenum}
        \item $X$ heißt \textbf{explizit typisiert}, wenn für jede 
              Variable der Typ explizit genannt wird.
        \item $X$ heißt \textbf{implizit typisiert}, wenn der Typ einer
              Variable aus den verwendeten Operationen abgeleitet werden kann.
    \end{defenum}
\end{definition}

Sprachen, die implizit typisieren können nutzen dazu Typinferenz.

Beispiele für explizit typisierte Sprachen sind C, C++ und Java.
Beispiele für implizit typisierte Sprachen sind JavaScript, Python, PHP und Haskell.

Mir ist kein Beispiel einer Sprache bekannt, die dynamisch und explizit typisiert
ist.

Vorteile expliziter Typisierung sind:

\begin{itemize}
    \item \textbf{Lesbarkeit}
\end{itemize}

Vorteile impliziter Typisierung sind:

\begin{itemize}
    \item \textbf{Tippfreundlicher}: Es ist schneller zu schreiben.
    \item \textbf{Anfängerfreundlicher}: Man muss sich bei einfachen Problemen
          keine Gedanken um den Typ machen.
\end{itemize}

\begin{definition}[Duck-Typing und strukturelle Typisierung]\xindex{Duck-Typing}\xindex{Typisierung!strukturelle}%
    \begin{defenum}
        \item Eine Programmiersprache verwendet \textbf{Duck-Typing}, wenn die Parameter einer 
              Methode nicht durch die explizite Angabe von Typen festgelegt werden, sondern
              durch die Art wie die Parameter verwendet werden.
        \item Eine Programmiersprache verwendet \textbf{strukturelle Typisierung}, wenn die
              Parameter einer Methode nicht durch die explizite Angabe von Typen
              festgelegt werden, sondern explizit durch die Angabe von Methoden.
    \end{defenum}
\end{definition}

Strukturelle Typsierung wird auch \textit{typsicheres Duck-Typing} genannt. 
Der Satz, den man im Zusammenhang mit Duck-Typing immer höhrt, ist

\enquote{When I see a bird that walks like a duck and swims like a duck and quacks like a duck, I call that bird a duck.}

\begin{beispiel}[Strukturelle Typisierung]
    Folgende Scala-Methode erwartet ein Objekt, das eine Methode namens \texttt{quack}
    besitzt:

    \inputminted[numbersep=5pt, tabsize=4]{scala}{scripts/scala/duck-typing-example.scala}

    Diese Funktion ist vom Typ \texttt{(duck: AnyRef{def quack(value: String): String})Unit}.
\end{beispiel}

\section{Kompilierte und interpretierte Sprachen}
Sprachen werden überlicherweise entweder interpretiert oder kompiliert,
obwohl es Programmiersprachen gibt, die beides unterstützen.

C und Java werden kompiliert, Python und TCL interpretiert.

\section{Dies und das}
\begin{definition}[Seiteneffekt]\xindex{Seiteneffekt}\index{Nebeneffekt|see{Seiteneffekt}}\index{Wirkung|see{Seiteneffekt}}%
    Seiteneffekte sind Veränderungen des Zustandes eines Programms.
\end{definition}

Manchmal werden Seiteneffekte auch als Nebeneffekt oder Wirkung bezeichnet.
Meistens meint man insbesondere unerwünschte oder überaschende Zustandsänderungen.

\begin{definition}[Unifikation]\xindex{Unifikation}%
    Die Unifikation ist eine Operation in der Logik und dient zur Vereinfachung
    prädikatenlogischer Ausdrücke.
    Der Unifikator ist also eine Abbildung, die in einem Schritt dafür sorgt, dass
    auf beiden Seiten der Gleichung das selbe steht.
\end{definition}

\begin{beispiel}[Unifikation\footnotemark]
    Gegeben seien die Ausdrücke
    \begin{align*}
        A_1 &= \left(X, Y, f(b) \right)\\
        A_2 &= \left(a, b, Z \right)
    \end{align*}
    Großbuchstaben stehen dabei für Variablen und Kleinbuchstaben für atomare 
    Ausdrücke.

    Ersetzt man in $A_1$ nun $X$ durch $a$, $Y$ durch $b$ und in $A_2$ 
    die Variable $Z$ durch $f\left(b\right)$, so sind sie gleich oder 
    \enquote{unifiziert}. Man erhält

    \begin{align*}
        \sigma(A_1) &= \left(a, b, f(b) \right)\\
        \sigma(A_2) &= \left(a, b, f(b) \right)
    \end{align*}

    mit
    \[\sigma = \{X \mapsto a, Y \mapsto b, Z \mapsto f(b)\}\]
\end{beispiel}

\begin{definition}[Allgemeinster Unifikator]\xindex{Unifikator!allgemeinster}%
    Ein Unifikator $\sigma$ heißt \textit{allgemeinster Unifikator}, wenn 
    es für jeden Unifikator $\gamma$ eine Substitution $\delta$ mit
    \[\gamma = \delta \circ \sigma\]
    gibt.
\end{definition}

\begin{beispiel}[Allgemeinster Unifikator\footnotemark]
    Sei
    \[C = \Set{f(a,D) = Y, X = g(b), g(Z) = X}\]
    eine Menge von Gleichungen über Terme.

    Dann ist 
    \[\gamma = [Y \text{\pointer} f(a,b), D \text{\pointer} b, X \text{\pointer} g(b), Z \text{\pointer} b]\]
    ein Unifikator für $C$. Jedoch ist
    \[\sigma = [Y \text{\pointer} f(a,D), X \text{\pointer} g(b), Z \text{\pointer} b]\]
    der allgemeinste Unifikator. Mit
    \[\delta = [D \text{\pointer} b]\]
    gilt $\gamma = \delta \circ \sigma$.
\end{beispiel}
\footnotetext{Folie 268 von Prof. Snelting}

\begin{algorithm}[h]
    \begin{algorithmic}
        \Function{unify}{Gleichungsmenge $C$}
        \If{$C == \emptyset$}
            \State \Return $[]$
        \Else
            \State Es sei $\Set{\theta_l = \theta_r} \cup C' == C$

            \If{$\theta_l == \theta_r$}
                \State \Call{unify}{$C'$}
            \ElsIf{$\theta_l == Y$ and $Y \notin FV(\theta_r)$}
                \State \Call{unify}{$[Y \text{\pointer} \theta_r] C'$} $\circ [Y \text{\pointer} \theta_r]$
            \ElsIf{$\theta_r == Y$ and $Y \notin FV(\theta_l)$}
                \State \Call{unify}{$[Y \text{\pointer} \theta_l] C'$} $\circ [Y \text{\pointer} \theta_l]$
            \ElsIf{$\theta_l == f(\theta_l^1, \dots, \theta_l^n)$ and $\theta_r == f(\theta_r^1, \dots, \theta_r^n$}
                \State \Call{unify}{$C' \cup \Set{\theta_l^1 = \theta_r^1, \dots \theta_l^n = \theta_r^n}$}
            \Else
                \State fail
            \EndIf
        \EndIf
        \EndFunction
    \end{algorithmic}
\caption{Klassischer Unifikationsalgorithmus}
\label{alg:klassischer-unifikationsalgorithmus}
\end{algorithm}

Dieser klassische Algorithmus hat eine Laufzeit von $\mathcal{O}(2^n)$ für folgendes
Beispiel:

\[f(X_1, X_2, \dots, X_n) = f(g(X_0, X_0), g(X_1, X_1), \dots, g(X_{n-1}, X_{n-1}) )\]

Der \textit{Paterson-Wegman-Unifikationsalgorithmus}\xindex{Paterson-Wegman-Unifikationsalgorithmus} ist deutlich effizienter.
Er basiert auf dem \textit{Union-Find-Algorithmus}\xindex{Union-Find-Algorithmus}
und funktioniert wie folgt:

\begin{algorithm}[h]
    \begin{algorithmic}
        \Function{unify}{Knoten $p$, Knoten $q$}
            \State $s \gets \Call{find}{p}$
            \State $t \gets \Call{find}{q}$
            \If{$s == t$ oder $s.\Call{getAtom}{} == t.\Call{getAtom}{}$}
                \State \Return \texttt{True}
            \EndIf

            \If{$s,t$ Knoten für gleichen Funktor, mit Nachfolgern $s_1, \dots , s_n$ bzw. $t_1 , \dots , t_n$ }
                \State $\Call{union}{s,t}$
                \State $k \gets 1$
                \State $b \gets $ \texttt{True}
                \While{$k \leq n$ and $b$}
                    \State $b \gets \Call{unify}{s_k, t_k}$
                    \State $k \gets k + 1$
                \EndWhile
                \State \Return \texttt{True}
            \EndIf

            \If{$s$ oder $t$ ist Variablen-Knoten}
                \State $\Call{union}{s,t}$
                \State \Return \texttt{True}
            \EndIf

            \State \Return \texttt{False}
        \EndFunction
    \end{algorithmic}
\caption{Paterson-Wegeman Unifikationsalgorithmus}
\label{alg:paterson-wegeman-unifikationsalgorithmus}
\end{algorithm}


\footnotetext{\url{https://de.wikipedia.org/w/index.php?title=Unifikation\_(Logik)&oldid=116848554\#Beispiel}}