123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378 |
- %!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}}
|