mirror of
https://github.com/MartinThoma/LaTeX-examples.git
synced 2025-04-25 06:18:05 +02:00
345 lines
No EOL
14 KiB
TeX
345 lines
No EOL
14 KiB
TeX
%!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}.
|
|
|
|
|
|
\footnotetext{\url{https://de.wikipedia.org/w/index.php?title=Unifikation\_(Logik)&oldid=116848554\#Beispiel}} |