Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download

📚 The CoCalc Library - books, templates and other resources

132930 views
License: OTHER
1
%!TEX root = Programmierparadigmen.tex
2
\chapter{Programmiersprachen}
3
\begin{definition}\xindex{Programmiersprache}\xindex{Programm}%
4
Eine \textbf{Programmiersprache} ist eine formale Sprache, die durch eine
5
Spezifikation definiert wird und mit der Algorithmen beschrieben werden
6
können. Elemente dieser Sprache heißen \textbf{Programme}.
7
\end{definition}
8
9
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
10
eine Referenzimplementierung eine Form der Spezifikation.
11
12
Im Folgenden wird darauf eingegangen, anhand welcher Kriterien man
13
Programmiersprachen unterscheiden kann.
14
15
\section{Abstraktion}
16
Wie nah an den physikalischen Prozessen im Computer ist die Sprache?
17
Wie nah ist sie an einer mathematisch / algorithmischen Beschreibung?
18
19
\begin{definition}\xindex{Maschinensprache}\xindex{Befehlssatz}%
20
Eine \textbf{Maschinensprache} beinhaltet ausschließlich Instruktionen, die direkt
21
von einer CPU ausgeführt werden können. Die Menge dieser Instruktionen
22
sowie deren Syntax wird \textbf{Befehlssatz} genannt.
23
\end{definition}
24
25
\begin{beispiel}[Maschinensprachen]
26
\begin{bspenum}
27
\item \xindex{x86}x86
28
\item \xindex{SPARC}SPARC
29
\end{bspenum}
30
\end{beispiel}
31
32
\begin{definition}[Assembler]\xindex{Assembler}%
33
Eine Assemblersprache ist eine Programmiersprache, deren Befehle dem
34
Befehlssatz eines Prozessor entspricht.
35
\end{definition}
36
37
\begin{beispiel}[Assembler]%
38
Folgendes Beispiel stammt von \url{https://de.wikibooks.org/wiki/Assembler-Programmierung_für_x86-Prozessoren/_Das_erste_Assemblerprogramm}:
39
\inputminted[linenos, numbersep=5pt, tabsize=4, frame=lines, label=firstp.asm]{nasm}{scripts/assembler/firstp.asm}
40
\end{beispiel}
41
42
\begin{definition}[Höhere Programmiersprache]\xindex{Programmiersprache!höhere}%
43
Eine Programmiersprache heißt \textit{höher}, wenn sie nicht ausschließlich
44
für eine Prozessorarchitektur geschrieben wurde und turing-vollständig ist.
45
\end{definition}
46
47
\begin{beispiel}[Höhere Programmiersprachen]
48
Java, Python, Haskell, Ruby, TCL, \dots
49
\end{beispiel}
50
51
\begin{definition}[Domänenspezifische Sprache]\xindex{Sprache!domänenspezifische}%
52
Eine domänenspezifische Sprache (engl. domain-specific language; kurz DSL)
53
ist eine formale Sprache, die für ein bestimmtes Problemfeld
54
entworfen wurde.
55
\end{definition}
56
57
\begin{beispiel}[Domänenspezifische Sprache]
58
\begin{bspenum}
59
\item HTML
60
\item VHDL
61
\end{bspenum}
62
\end{beispiel}
63
64
\section{Paradigmen}
65
Eine weitere Art, wie man Programmiersprachen unterscheiden
66
kann ist das sog. \enquote{Programmierparadigma}, also die Art wie
67
man Probleme löst.
68
69
\begin{definition}[Imperatives Paradigma]\xindex{Programmierung!imperative}%
70
In der \textit{imperativen Programmierung} betrachtet man Programme als
71
eine Folge von Anweisungen, die vorgibt auf welche Art etwas
72
Schritt für Schritt gemacht werden soll.
73
\end{definition}
74
75
\begin{beispiel}[Imperative Programmierung]
76
In folgenden Programm erkennt man den imperativen Programmierstil vor allem
77
an den Variablenzuweisungen:
78
\inputminted[numbersep=5pt, tabsize=4]{c}{scripts/c/fibonacci-imperativ.c}
79
\end{beispiel}
80
81
\begin{definition}[Prozedurales Paradigma]\xindex{Programmierung!prozedurale}%
82
Die prozeduralen Programmierung ist eine Erweiterung des imperativen
83
Programmierparadigmas, bei dem man versucht die Probleme in
84
kleinere Teilprobleme zu zerlegen.
85
\end{definition}
86
87
\begin{definition}[Funktionales Paradigma]\xindex{Programmierung!funktionale}%
88
In der funktionalen Programmierung baut man auf Funktionen und
89
ggf. Funktionen höherer Ordnung, die eine Aufgabe ohne Nebeneffekte
90
lösen.
91
\end{definition}
92
93
\begin{beispiel}[Funktionale Programmierung]
94
Der Funktionale Stil kann daran erkannt werden, dass keine Werte zugewiesen werden:
95
\inputminted[numbersep=5pt, tabsize=4]{haskell}{scripts/haskell/fibonacci-akk.hs}
96
\end{beispiel}
97
98
Haskell ist eine funktionale Programmiersprache, C ist eine
99
nicht-funktionale Programmiersprache.
100
101
Wichtige Vorteile von funktionalen Programmiersprachen sind:
102
\begin{itemize}
103
\item Sie sind weitgehend (jedoch nicht vollständig) frei von Seiteneffekten.
104
\item Der Code ist häufig sehr kompakt und manche Probleme lassen
105
sich sehr elegant formulieren.
106
\end{itemize}
107
108
\begin{definition}[Logisches Paradigma]\xindex{Programmierung!logische}%
109
Das \textbf{logische Programmierparadigma} baut auf der formalen Logik auf.
110
Man verwendet \textbf{Fakten} und \textbf{Regeln}
111
und einen Inferenzalgorithmus um Probleme zu lösen.
112
\end{definition}
113
114
Der Inferenzalgorithmus kann z.~B. die Unifikation nutzen.
115
116
\begin{beispiel}[Logische Programmierung]
117
Obwohl die logische Programmierung für Zahlenfolgen weniger geeignet erscheint,
118
sei hier zur Vollständigkeit das letzte Fibonacci-Beispiel in Prolog:
119
\inputminted[numbersep=5pt, tabsize=4]{prolog}{scripts/prolog/fibonacci.pl}
120
\end{beispiel}
121
122
\section{Typisierung}
123
Programmiersprachen können anhand der Art ihrer Typisierung unterschieden werden.
124
125
\begin{definition}[Typisierungsstärke]\xindex{Typisierungsstärke}%
126
Es seien $X, Y$ Programmiersprachen.
127
128
$X$ heißt stärker typisiert als $Y$, wenn $X$ mehr bzw. nützlichere Typen hat als $Y$.
129
\end{definition}
130
131
\begin{beispiel}[Typisierungsstärke]
132
Die stärke der Typisierung ist abhängig von dem Anwendungszenario. So hat C im
133
Gegensatz zu Python, Java oder Haskell beispielsweise keine booleschen Datentypen.
134
135
Im Gegensatz zu Haskell hat Java keine GADTs\footnote{generalized algebraic data type}.
136
\end{beispiel}
137
138
\begin{definition}[Polymorphie]\xindex{Polymorphie}%
139
\begin{defenum}
140
\item Ein Typ heißt polymorph, wenn er mindestens einen Parameter hat.
141
\item Eine Funktion heißt polymorph, wenn ihr Verhalten nicht von dem
142
konkreten Typen der Parameter abhängt.
143
\end{defenum}
144
\end{definition}
145
146
\begin{beispiel}[Polymorphie]
147
In Java sind beispielsweise Listen polymorphe Typen:
148
149
\inputminted[numbersep=5pt, tabsize=4]{java}{scripts/java/list-example.java}
150
151
Entsprechend sind auf Listen polymorphe Operationen wie \texttt{add} und
152
\texttt{remove} definiert.
153
\end{beispiel}
154
155
\begin{definition}[Statische und dynamische Typisierung]\xindex{Typisierung!statische}\xindex{Typisierung!dynamische}%
156
\begin{defenum}
157
\item Eine Programmiersprache heißt \textbf{statisch typisiert}, wenn eine
158
Variable niemals ihren Typ ändern kann.
159
\item Eine Programmiersprache heißt \textbf{dynamisch typisiert}, wenn eine
160
Variable ihren Typ ändern kann.
161
\end{defenum}
162
\end{definition}
163
164
Beispiele für statisch typisierte Sprachen sind C, Haskell und Java.
165
Beispiele für dynamisch typisierte Sprachen sind Python und PHP.
166
\goodbreak
167
Vorteile statischer Typisierung sind:
168
169
\begin{itemize}
170
\item \textbf{Performance}: Der Compiler kann mehr Optimierungen vornehmen.
171
\item \textbf{Syntaxcheck}: Da der Compiler die Typen zur Compile-Zeit überprüft,
172
gibt es in statisch typisierten Sprachen zur
173
Laufzeit keine Typfehler.
174
\end{itemize}
175
176
Vorteile dynamischer Typisierung sind:
177
178
\begin{itemize}
179
\item Manche Ausdrücke, wie der Y-Combinator in Haskell, lassen sich nicht
180
typisieren.
181
\end{itemize}
182
183
Der Gedanke bei dynamischer Typisierung ist, dass Variablen keine Typen haben.
184
Nur Werte haben Typen. Man stellt sich also Variablen eher als Beschriftungen für
185
Werte vor. Bei statisch typisierten Sprachen stellt man sich hingegen Variablen
186
als Container vor.
187
188
\begin{definition}[Explizite und implizite Typisierung]\xindex{Typisierung!explizite}\xindex{Typisierung!implizite}%
189
Sei $X$ eine Programmiersprache.
190
\begin{defenum}
191
\item $X$ heißt \textbf{explizit typisiert}, wenn für jede
192
Variable der Typ explizit genannt wird.
193
\item $X$ heißt \textbf{implizit typisiert}, wenn der Typ einer
194
Variable aus den verwendeten Operationen abgeleitet werden kann.
195
\end{defenum}
196
\end{definition}
197
198
Sprachen, die implizit typisieren können nutzen dazu Typinferenz.
199
200
Beispiele für explizit typisierte Sprachen sind C, C++ und Java.
201
Beispiele für implizit typisierte Sprachen sind JavaScript, Python, PHP und Haskell.
202
203
Mir ist kein Beispiel einer Sprache bekannt, die dynamisch und explizit typisiert
204
ist.
205
206
Vorteile expliziter Typisierung sind:
207
208
\begin{itemize}
209
\item \textbf{Lesbarkeit}
210
\end{itemize}
211
212
Vorteile impliziter Typisierung sind:
213
214
\begin{itemize}
215
\item \textbf{Tippfreundlicher}: Es ist schneller zu schreiben.
216
\item \textbf{Anfängerfreundlicher}: Man muss sich bei einfachen Problemen
217
keine Gedanken um den Typ machen.
218
\end{itemize}
219
220
\begin{definition}[Duck-Typing und strukturelle Typisierung]\xindex{Duck-Typing}\xindex{Typisierung!strukturelle}%
221
\begin{defenum}
222
\item Eine Programmiersprache verwendet \textbf{Duck-Typing}, wenn die Parameter einer
223
Methode nicht durch die explizite Angabe von Typen festgelegt werden, sondern
224
durch die Art wie die Parameter verwendet werden.
225
\item Eine Programmiersprache verwendet \textbf{strukturelle Typisierung}, wenn die
226
Parameter einer Methode nicht durch die explizite Angabe von Typen
227
festgelegt werden, sondern explizit durch die Angabe von Methoden.
228
\end{defenum}
229
\end{definition}
230
231
Strukturelle Typsierung wird auch \textit{typsicheres Duck-Typing} genannt.
232
Der Satz, den man im Zusammenhang mit Duck-Typing immer höhrt, ist
233
234
\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.}
235
236
\begin{beispiel}[Strukturelle Typisierung]
237
Folgende Scala-Methode erwartet ein Objekt, das eine Methode namens \texttt{quack}
238
besitzt:
239
240
\inputminted[numbersep=5pt, tabsize=4]{scala}{scripts/scala/duck-typing-example.scala}
241
242
Diese Funktion ist vom Typ \texttt{(duck: AnyRef{def quack(value: String): String})Unit}.
243
\end{beispiel}
244
245
\section{Kompilierte und interpretierte Sprachen}
246
Sprachen werden überlicherweise entweder interpretiert oder kompiliert,
247
obwohl es Programmiersprachen gibt, die beides unterstützen.
248
249
C und Java werden kompiliert, Python und TCL interpretiert.
250
251
\section{Dies und das}
252
\begin{definition}[Seiteneffekt]\xindex{Seiteneffekt}\index{Nebeneffekt|see{Seiteneffekt}}\index{Wirkung|see{Seiteneffekt}}%
253
Seiteneffekte sind Veränderungen des Zustandes eines Programms.
254
\end{definition}
255
256
Manchmal werden Seiteneffekte auch als Nebeneffekt oder Wirkung bezeichnet.
257
Meistens meint man insbesondere unerwünschte oder überaschende Zustandsänderungen.
258
259
\begin{definition}[Unifikation]\xindex{Unifikation}%
260
Die Unifikation ist eine Operation in der Logik und dient zur Vereinfachung
261
prädikatenlogischer Ausdrücke.
262
Der Unifikator ist also eine Abbildung, die in einem Schritt dafür sorgt, dass
263
auf beiden Seiten der Gleichung das selbe steht.
264
\end{definition}
265
266
\begin{beispiel}[Unifikation\footnotemark]
267
Gegeben seien die Ausdrücke
268
\begin{align*}
269
A_1 &= \left(X, Y, f(b) \right)\\
270
A_2 &= \left(a, b, Z \right)
271
\end{align*}
272
Großbuchstaben stehen dabei für Variablen und Kleinbuchstaben für atomare
273
Ausdrücke.
274
275
Ersetzt man in $A_1$ nun $X$ durch $a$, $Y$ durch $b$ und in $A_2$
276
die Variable $Z$ durch $f\left(b\right)$, so sind sie gleich oder
277
\enquote{unifiziert}. Man erhält
278
279
\begin{align*}
280
\sigma(A_1) &= \left(a, b, f(b) \right)\\
281
\sigma(A_2) &= \left(a, b, f(b) \right)
282
\end{align*}
283
284
mit
285
\[\sigma = \{X \mapsto a, Y \mapsto b, Z \mapsto f(b)\}\]
286
\end{beispiel}
287
288
\begin{definition}[Allgemeinster Unifikator]\xindex{Unifikator!allgemeinster}%
289
Ein Unifikator $\sigma$ heißt \textit{allgemeinster Unifikator}, wenn
290
es für jeden Unifikator $\gamma$ eine Substitution $\delta$ mit
291
\[\gamma = \delta \circ \sigma\]
292
gibt.
293
\end{definition}
294
295
\begin{beispiel}[Allgemeinster Unifikator\footnotemark]
296
Sei
297
\[C = \Set{f(a,D) = Y, X = g(b), g(Z) = X}\]
298
eine Menge von Gleichungen über Terme.
299
300
Dann ist
301
\[\gamma = [Y \text{\pointer} f(a,b), D \text{\pointer} b, X \text{\pointer} g(b), Z \text{\pointer} b]\]
302
ein Unifikator für $C$. Jedoch ist
303
\[\sigma = [Y \text{\pointer} f(a,D), X \text{\pointer} g(b), Z \text{\pointer} b]\]
304
der allgemeinste Unifikator. Mit
305
\[\delta = [D \text{\pointer} b]\]
306
gilt $\gamma = \delta \circ \sigma$.
307
\end{beispiel}
308
\footnotetext{Folie 268 von Prof. Snelting}
309
310
\begin{algorithm}[h]
311
\begin{algorithmic}
312
\Function{unify}{Gleichungsmenge $C$}
313
\If{$C == \emptyset$}
314
\State \Return $[]$
315
\Else
316
\State Es sei $\Set{\theta_l = \theta_r} \cup C' == C$
317
318
\If{$\theta_l == \theta_r$}
319
\State \Call{unify}{$C'$}
320
\ElsIf{$\theta_l == Y$ and $Y \notin FV(\theta_r)$}
321
\State \Call{unify}{$[Y \text{\pointer} \theta_r] C'$} $\circ [Y \text{\pointer} \theta_r]$
322
\ElsIf{$\theta_r == Y$ and $Y \notin FV(\theta_l)$}
323
\State \Call{unify}{$[Y \text{\pointer} \theta_l] C'$} $\circ [Y \text{\pointer} \theta_l]$
324
\ElsIf{$\theta_l == f(\theta_l^1, \dots, \theta_l^n)$ and $\theta_r == f(\theta_r^1, \dots, \theta_r^n$}
325
\State \Call{unify}{$C' \cup \Set{\theta_l^1 = \theta_r^1, \dots \theta_l^n = \theta_r^n}$}
326
\Else
327
\State fail
328
\EndIf
329
\EndIf
330
\EndFunction
331
\end{algorithmic}
332
\caption{Klassischer Unifikationsalgorithmus}
333
\label{alg:klassischer-unifikationsalgorithmus}
334
\end{algorithm}
335
336
Dieser klassische Algorithmus hat eine Laufzeit von $\mathcal{O}(2^n)$ für folgendes
337
Beispiel:
338
339
\[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}) )\]
340
341
Der \textit{Paterson-Wegman-Unifikationsalgorithmus}\xindex{Paterson-Wegman-Unifikationsalgorithmus} ist deutlich effizienter.
342
Er basiert auf dem \textit{Union-Find-Algorithmus}\xindex{Union-Find-Algorithmus}
343
und funktioniert wie folgt:
344
345
\begin{algorithm}[h]
346
\begin{algorithmic}
347
\Function{unify}{Knoten $p$, Knoten $q$}
348
\State $s \gets \Call{find}{p}$
349
\State $t \gets \Call{find}{q}$
350
\If{$s == t$ oder $s.\Call{getAtom}{} == t.\Call{getAtom}{}$}
351
\State \Return \texttt{True}
352
\EndIf
353
354
\If{$s,t$ Knoten für gleichen Funktor, mit Nachfolgern $s_1, \dots , s_n$ bzw. $t_1 , \dots , t_n$ }
355
\State $\Call{union}{s,t}$
356
\State $k \gets 1$
357
\State $b \gets $ \texttt{True}
358
\While{$k \leq n$ and $b$}
359
\State $b \gets \Call{unify}{s_k, t_k}$
360
\State $k \gets k + 1$
361
\EndWhile
362
\State \Return \texttt{True}
363
\EndIf
364
365
\If{$s$ oder $t$ ist Variablen-Knoten}
366
\State $\Call{union}{s,t}$
367
\State \Return \texttt{True}
368
\EndIf
369
370
\State \Return \texttt{False}
371
\EndFunction
372
\end{algorithmic}
373
\caption{Paterson-Wegeman Unifikationsalgorithmus}
374
\label{alg:paterson-wegeman-unifikationsalgorithmus}
375
\end{algorithm}
376
377
378
\footnotetext{\url{https://de.wikipedia.org/w/index.php?title=Unifikation\_(Logik)&oldid=116848554\#Beispiel}}
379