„Funktionallogische Programmierung“ – Versionsunterschied
[gesichtete Version] | [gesichtete Version] |
Neu erstellt |
Prüm (Diskussion | Beiträge) |
||
(17 dazwischenliegende Versionen von 10 Benutzern werden nicht angezeigt) | |||
Zeile 1: | Zeile 1: | ||
Die '''Funktionallogische Programmierung''' ist die Vereinigung des funktionalen mit dem logischen Paradigma in einer Programmiersprache. Dies schließt die meisten starken Konzepte der Paradigmen mit ein, dazu gehören Funktionen höherer Ordnung, nicht-deterministische Ausführung |
Die '''Funktionallogische Programmierung''' ist die Vereinigung des funktionalen mit dem logischen Paradigma in einer Programmiersprache. Dies schließt die meisten starken Konzepte der Paradigmen mit ein, dazu gehören Funktionen höherer Ordnung, nicht-deterministische Ausführung und Unifikation. Wegbereiter dieser Schöpfung war [[λProlog]]<ref>{{Literatur |Autor=Gopalan Nadathur, D. Miller |Hrsg=D. M. Gabbay, C. J. Hogger, J. A. Robinson |Titel=Logic Programming |Reihe=Handbook of Logic in Artificial Intelligence and Logic Programming |BandReihe=5 |Verlag=Oxford University Press |Datum=1998 |ISBN=0-19-853792-1 |Kapitel=Higher-Order Logic Programming |Seiten=499–590}}</ref> in den Neunzigern. Andere, neuere funktionallogische Programmiersprachen sind [[Curry (Programmiersprache)|Curry]] und [[Mercury (Programmiersprache)|Mercury]]. |
||
|first1=Gopalan |
|||
|last1=Nadathur |
|||
|first2=D. |
|||
|last2=Miller |
|||
|chapter=Higher-Order Logic Programming |
|||
|title=Logic Programming |
|||
|volume=5 | isbn=0198537921 |
|||
|series=Handbook of Logic in Artificial Intelligence and Logic Programming |
|||
|editor1-first=D. M. |
|||
|editor1-last=Gabbay |
|||
|editor1-link=Dov Gabbay |
|||
|editor2-first=C. J. |
|||
|editor2-last=Hogger |
|||
|editor3-first=J. A. |
|||
|editor3-last=Robinson |
|||
|editor3-link=John Alan Robinson |
|||
|publisher=Oxford University Press |
|||
|year=1998 |
|||
|pages=499–590}}</ref> in den Neunzigern. Andere, neuere [[:Kategorie:Funktionallogische Programmiersprache|Funktionallogische Programmiersprachen]] sind [[Curry (Programmiersprache)|Curry]] and [[Mercury (Programmiersprache)|Mercury]]. |
|||
==Grundlagen== |
== Grundlagen == |
||
Die Beispiele sind in der Syntax von [[Curry (Programmiersprache)|Curry]]. |
|||
===Einfache Konzepte=== |
|||
⚫ | Ein funktionales Programm ist eine Ansammlung von Funktionen oder Regeln. Eine funktionale Berechnung besteht aus dem Ersetzen von Teilausdrücken durch (unter Berücksichtigung der Funktionsdefinition) gleichwertige Teilausdrücke, solange bis keine Ersetzungen bzw. Reduktionen mehr möglich sind und ein Ergebnis |
||
=== Funktionale Programmierung === |
|||
⚫ | Ein funktionales Programm ist eine Ansammlung von Funktionen oder Regeln. Eine funktionale Berechnung besteht aus dem Ersetzen von Teilausdrücken durch (unter Berücksichtigung der Funktionsdefinition) gleichwertige Teilausdrücke, solange bis keine Ersetzungen bzw. Reduktionen mehr möglich sind und ein Ergebnis bestimmt oder eine [[Normalform]] erreicht wird. Einen Teilausdruck, der reduziert werden kann, wird auch als Redex (von {{enS|reducible expression|Audio=|IPA=|de=reduzierbarer Ausdruck}}) bezeichnet. Für die nächsten Beispiele sei die Funktion <code>verdoppeln</code> definiert durch |
||
verdoppeln x <span style="color:green">'''='''</span> x <span style="color:darkgreen">'''+'''</span> x |
verdoppeln x <span style="color:green">'''='''</span> x <span style="color:darkgreen">'''+'''</span> x |
||
Der Ausdruck <code>verdoppeln 1</code> wird durch <code>1 + 1</code> ersetzt, was wiederum durch <code>2</code> ersetzt werden kann, |
Der Ausdruck <code>verdoppeln 1</code> ist ein Redex und wird durch <code>1 + 1</code> ersetzt, was wiederum durch <code>2</code> ersetzt werden kann, wobei man den <code>+</code>-Operator vereinfacht als eine unendliche Menge von Gleichungen der Form <code>1 + 1 = 2</code>, <code>1 + 2 = 3</code> usw. auffasst. Auf ähnliche Weise können Teilausdrücke ausgewertet werden. Im Beispiel wird der zu ersetzende Teilausdruck kursiv hervorgehoben. |
||
''verdoppeln (''1 <span style="color:darkgreen">'''+'''</span> 2'')'' → ''(1 <span style="color:darkgreen">'''+'''</span> 2)'' <span style="color:darkgreen">'''+'''</span> (1 <span style="color:darkgreen">'''+'''</span> 2) → 3 <span style="color:darkgreen">'''+'''</span> ''(1 <span style="color:darkgreen">'''+'''</span> 2)'' → ''3 <span style="color:darkgreen">'''+'''</span> 3'' → 6 |
''verdoppeln (''1 <span style="color:darkgreen">'''+'''</span> 2'')'' → ''(1 <span style="color:darkgreen">'''+'''</span> 2)'' <span style="color:darkgreen">'''+'''</span> (1 <span style="color:darkgreen">'''+'''</span> 2) → 3 <span style="color:darkgreen">'''+'''</span> ''(1 <span style="color:darkgreen">'''+'''</span> 2)'' → ''3 <span style="color:darkgreen">'''+'''</span> 3'' → 6 |
||
Zeile 32: | Zeile 15: | ||
Es gibt aber auch noch eine andere Reihenfolge in der die Ausdrücke ersetzt werden können: |
Es gibt aber auch noch eine andere Reihenfolge in der die Ausdrücke ersetzt werden können: |
||
verdoppeln (''1 <span style="color:darkgreen">'''+'''</span> 2'') → ''verdoppeln 3'' → ''3 <span style="color:darkgreen">'''+'''</span> 3'' → 6 |
|||
In diesem Fall führen beide Auswertungen zum gleichen Resultat. Diese Eigenschaft heißt [[Konfluenz]] und folgt aus der fundamentalen Eigenschaft reiner funktionaler Sprachen, der [[ |
In diesem Fall führen beide Auswertungen zum gleichen Resultat. Diese Eigenschaft heißt [[Konfluenz (Informatik)|Konfluenz]] und folgt aus der fundamentalen Eigenschaft reiner funktionaler Sprachen, der [[Referenzielle Transparenz|referenziellen Transparenz]]: der zu bestimmende Wert eines Ausdrucks hängt nicht (z. B. aufgrund von Seiteneffekten) von der Reihenfolge oder dem Zeitpunkt der Auswertung ab. Daher sind Beweise leichter zu führen und die Programme leicht wartbar. |
||
=== Algebraische Datentypen === |
|||
Ebenso wie funktionale Sprachen |
Ebenso wie funktionale Sprachen unterstützen viele funktionallogische Sprachen die Definition von algebraischen Typen, indem ihre Konstruktoren aufgelistet werden. Beispielsweise wird der Datentyp <code>Bool</code> für Wahrheitswerte mit den Konstruktoren <code>True</code> und <code>False</code> wie folgt definiert: |
||
'''<span style="color:darkblue">data</span>''' <span style="color:purple">Bool</span> <span style="color:green">'''='''</span> <span style="color:maroon">True</span> <span style="color:green">'''|'''</span> <span style="color:maroon">False</span> |
'''<span style="color:darkblue">data</span>''' <span style="color:purple">Bool</span> <span style="color:green">'''='''</span> <span style="color:maroon">True</span> <span style="color:green">'''|'''</span> <span style="color:maroon">False</span> |
||
Funktionen, deren Parameter solche Wahrheitswerte sind, können durch [[Pattern Matching]] definiert werden, |
Funktionen, deren Parameter solche Wahrheitswerte sind, können durch Mustervergleich ([[Pattern Matching]]) definiert werden, meistens durch mehrere definierende Gleichungen: |
||
not <span style="color:maroon">True</span> <span style="color:green">'''='''</span> <span style="color:maroon">False</span> |
not <span style="color:maroon">True</span> <span style="color:green">'''='''</span> <span style="color:maroon">False</span> |
||
not <span style="color:maroon">False</span> <span style="color:green">'''='''</span> <span style="color:maroon">True</span> |
not <span style="color:maroon">False</span> <span style="color:green">'''='''</span> <span style="color:maroon">True</span> |
||
Das Vorgehen Ausdrücke durch gleichwertige zu ersetzen ist weiterhin gültig, wenn die Ausdrücke die entsprechende Form haben: |
Das Vorgehen Ausdrücke durch gleichwertige zu ersetzen ist weiterhin gültig, wenn die Ausdrücke die entsprechende Form haben: |
||
⚫ | |||
⚫ | |||
⚫ | Kompliziertere Datenstrukturen werden durch rekursive Datentypen dargestellt. Beispielsweise eine Liste über einem beliebigen Typen, deren Elemente ebendiesen Typ haben, ist entweder die leere Liste <code>[]</code> oder eine nicht-leere Liste mit einem ersten Element <code>x</code> und einem Restglied <code>xs</code>, dargestellt durch <code>x : xs</code>. |
||
⚫ | Kompliziertere Datenstrukturen werden durch rekursive Datentypen dargestellt. Beispielsweise eine Liste über einem beliebigen Typen, deren Elemente ebendiesen Typ haben, ist entweder die leere Liste <code>[]</code> oder eine nicht-leere Liste mit einem ersten Element <code>x</code> und einem Restglied <code>xs</code>, dargestellt durch <code>x : xs</code>. |
||
'''<span style="color:darkblue">data</span>''' <span style="color:purple">List</span> a <span style="color:green">'''='''</span> <span style="color:maroon">[]</span> <span style="color:darkgreen">-- [] ist ein parameterloser Konstruktor (d. h. ein Literal), das die leere Liste darstellt.</span> |
'''<span style="color:darkblue">data</span>''' <span style="color:purple">List</span> a <span style="color:green">'''='''</span> <span style="color:maroon">[]</span> <span style="color:darkgreen">-- [] ist ein parameterloser Konstruktor (d. h. ein Literal), das die leere Liste darstellt.</span> |
||
<span style="color:green">'''|'''</span> a <span style="color:maroon">''':'''</span> <span style="color:purple">List</span> a <span style="color:darkgreen">-- Der Konstruktor wird infix notiert und ist der Doppelpunkt. Er hat zwei Parameter: ein Listenelement und ein Restglied, welches auch leer sein darf.</span> |
<span style="color:green">'''|'''</span> a <span style="color:maroon">''':'''</span> <span style="color:purple">List</span> a <span style="color:darkgreen">-- Der Konstruktor wird infix notiert und ist der Doppelpunkt.</span> |
||
<span style="color:darkgreen">-- Er hat zwei Parameter: ein Listenelement und ein Restglied, welches auch leer sein darf.</span> |
|||
Üblicherweise notiert man den Typ <code>List a</code> mit <code>[a]</code> und endliche Listen <code>x1 : x2 : … : xn : []</code> mit <code>[x1, x2, …, xn]</code>. Funktionen auf rekursiven Typen können induktiv definiert werden, wobei Pattern Matching bequemes Fallunterscheiden ermöglicht. Die [[Konkatenation (Listen)|Konkatenation]] kann wie folgt definiert werden. Die beiden konkatenierten Listen müssen vom gleichen Typen sein |
Üblicherweise notiert man den Typ <code>List a</code> mit <code>[a]</code> und endliche Listen <code>x1 : x2 : … : xn : []</code> mit <code>[x1, x2, …, xn]</code>. Funktionen auf rekursiven Typen können induktiv definiert werden, wobei Pattern Matching bequemes Fallunterscheiden ermöglicht. Die [[Konkatenation (Listen)|Konkatenation]] kann wie folgt definiert werden. Die beiden konkatenierten Listen müssen vom gleichen Typen sein. Variablen für Listen enden oft mit [[S]], vgl. die Pluralbildung in der englischen Sprache. |
||
(<span style="color:darkgreen">++</span>) <span style="color:green">'''::'''</span> <span style="color:purple">[</span>a<span style="color:purple">]</span> <span style="color:green">'''->'''</span> <span style="color:purple">[</span>a<span style="color:purple">]</span> <span style="color:green">'''->'''</span> <span style="color:purple">[</span>a<span style="color:purple">]</span> <span style="color:darkgreen">-- Typdefinition: Zwei Listen mit gleichem Typ werden zu einer.</span> |
(<span style="color:darkgreen">++</span>) <span style="color:green">'''::'''</span> <span style="color:purple">[</span>a<span style="color:purple">]</span> <span style="color:green">'''->'''</span> <span style="color:purple">[</span>a<span style="color:purple">]</span> <span style="color:green">'''->'''</span> <span style="color:purple">[</span>a<span style="color:purple">]</span> <span style="color:darkgreen">-- Typdefinition: Zwei Listen mit gleichem Typ werden zu einer.</span> |
||
<span style="color:maroon">[]</span> <span style="color:darkgreen">++</span> ys <span style="color:green">'''='''</span> ys <span style="color:darkgreen">-- Leere Liste mit beliebiger Liste konkatenieren</span> |
<span style="color:maroon">[]</span> <span style="color:darkgreen">++</span> ys <span style="color:green">'''='''</span> ys <span style="color:darkgreen">-- Leere Liste mit beliebiger Liste konkatenieren</span> |
||
(x <span style="color:maroon">''':'''</span> xs) <span style="color:darkgreen">++</span> ys <span style="color:green">'''='''</span> x <span style="color:maroon">''':'''</span> (xs <span style="color:darkgreen">++</span> ys) <span style="color:darkgreen">-- Nicht-leere Liste rekursiv konkatenieren</span> |
(x <span style="color:maroon">''':'''</span> xs) <span style="color:darkgreen">++</span> ys <span style="color:green">'''='''</span> x <span style="color:maroon">''':'''</span> (xs <span style="color:darkgreen">++</span> ys) <span style="color:darkgreen">-- Nicht-leere Liste rekursiv konkatenieren</span> |
||
=== Logische Programmierung === |
|||
Es sei zur Demonstration die Funktion <code>last</code> beschrieben, die das letzte Element einer nicht-leeren Liste zurückgibt. Für alle Listen <code>xs</code> und Elemente <code>e</code> gilt: <code>last xs</code> ergibt <code>e</code> genau dann, wenn <code>ys ++ [e]</code> gleich <code>xs</code> für eine geeignete Liste <code>ys</code>. |
|||
Basierend auf dieser Spezifikation können wir mittels logischer Programmiertechniken eine Funktion definieren, die diese erfüllt. Ähnlich wie logische Sprachen bieten funktionallogische Sprachen Lösungen für die Suche nach existentialquantifizierten Variablen. Im Gegensatz zu |
Basierend auf dieser Spezifikation können wir mittels logischer Programmiertechniken eine Funktion definieren, die diese erfüllt. Ähnlich wie logische Sprachen bieten funktionallogische Sprachen Lösungen für die Suche nach existentialquantifizierten Variablen. Im Gegensatz zu rein logischen Sprachen unterstützen sie das Lösen von Gleichungen mit funktionalen Teilausdrücken, sodass <code>ys ++ [e] = [1, 2, 3]</code> gelöst wird, indem <code>ys</code> mit der Liste <code>[1, 2]</code> und <code>e</code> mit dem Wert <code>3</code> instantiiert wird. Dann kann <code>last</code> wie folgt definiert werden: |
||
last <span style="color:green">'''::'''</span> <span style="color:purple">[</span>a<span style="color:purple">]</span> <span style="color:green">'''->'''</span> a |
last <span style="color:green">'''::'''</span> <span style="color:purple">[</span>a<span style="color:purple">]</span> <span style="color:green">'''->'''</span> a |
||
last xs |
|||
<span style="color:green">'''|'''</span> ys <span style="color:darkgreen">++</span> <span style="color:maroon">[</span>e<span style="color:maroon">]</span> <span style="color:green">'''=:='''</span> xs |
|||
<span style="color:green">'''='''</span> e |
|||
'''<span style="color:darkblue">where</span>''' ys, e '''<span style="color:darkblue">free</span>''' |
|||
Hier wird das Symbol <code>=:=</code> für Gleichheitsbedingungen benutzt, um sie syntaktisch von definierenden Gleichheiten |
Hier wird das Symbol <code>=:=</code> für Gleichheitsbedingungen benutzt, um sie syntaktisch von definierenden Gleichheiten und Vergleichen zu unterscheiden. Auf ähnliche Weise können zusätzliche Variablen gebunden werden, indem sie explizit mit <code>where … free</code> deklariert werden. Die Deklaration ist notwendig und sinnvoll um Tippfehler zu vermeiden. Eine bedingte Gleichung der Form <code>L | B = R</code> kann angewendet werden, falls die Bedingung <code>B</code> gelöst wurde. Im Gegensatz zu rein funktionalen Sprachen, deren Bedingungen ausschließlich Muster oder Ausdrücke vom Typ <code>Bool</code> sein können, unterstützen funktionallogische Sprachen außerdem das Lösen von Bedingungen durch das Erraten von Werten der Unbekannten in der Bedingung; Muster-Bedingungen sind ein Spezialfall davon. Einschränkungen, die im Folgenden erklärt werden, werden dafür herangezogen. |
||
===Einschränkungen=== |
=== Einschränkungen === |
||
Das Einschränken ist ein Mechanismus, bei dem eine Variable an einen Wert anhand von Alternativen, die von Einschränkungen auferlegt werden, gebunden wird. Alle möglichen Werte werden in einer bestimmten Reihenfolge ausprobiert, wobei der Rest des Programms aufgerufen wird, um die Korrektheit der Bindung zu überprüfen. Einschränkungen sind insofern eine Erweiterung des logischen Programmierens, dass sie eine ähnliche Suche ergeben, jedoch kann sie vielmehr Werte als Teil der Suche erzeugen als lediglich auf das Überprüfen beschränkt zu sein. |
Das Einschränken ist ein Mechanismus, bei dem eine Variable an einen Wert anhand von Alternativen, die von Einschränkungen auferlegt werden, gebunden wird. Alle möglichen Werte werden in einer bestimmten Reihenfolge ausprobiert, wobei der Rest des Programms aufgerufen wird, um die Korrektheit der Bindung zu überprüfen. Einschränkungen sind insofern eine Erweiterung des logischen Programmierens, dass sie eine ähnliche Suche ergeben, jedoch kann sie vielmehr Werte als Teil der Suche erzeugen als lediglich auf das Überprüfen beschränkt zu sein. |
||
Das Einschränken ist insbesondere nützlich, da ermöglicht Funktionen wie Relationen zu handhaben; Werte können auf gewisse Art in beide Richtungen bestimmt werden. Unter anderem illustriert dies das vorhergehende Beispiel. |
Das Einschränken ist insbesondere nützlich, da es ermöglicht, Funktionen wie Relationen zu handhaben; Werte können auf gewisse Art in beide Richtungen bestimmt werden. Unter anderem illustriert dies das vorhergehende Beispiel. |
||
Wie bereits angemerkt können Einschränkungen wie eine Reduktion des Auswertegraphs aufgefasst werden und es gibt häufig viele verschiedene Wege ( |
Wie bereits angemerkt können Einschränkungen wie eine Reduktion des Auswertegraphs aufgefasst werden und es gibt häufig viele verschiedene Wege (Strategien) einen Graphen zu reduzieren. Antoy et al.<ref>{{Literatur |Autor=Sergio Antoy, Rachid Echahed and Michael Hanus |Titel=A Needed Narrowing Strategy |Sammelwerk=Journal of the ACM |Band=47 |Nummer=4 |Verlag=ACM |Datum=2007 |ISSN=0004-5411 |Seiten=776–822 |Online=https://dl.acm.org/doi/10.1145/347476.347484 |DOI=10.1145/347476.347484}}</ref> bewiesen in den Neunzigern, dass eine bestimmte Strategie, das ''needed narrowing,'' optimal ist mit möglichst wenigen Reduktionen eine Normalform zu erhalten. Needed narrowing ist eine Form von [[Lazy Evaluation|Bedarfsauswertung]], im Gegensatz zur SLD-Auflösungsstrategie<!-- siehe [[:en:SLD resolution|SLD resolution]] (engl. WP) --> von Prolog. |
||
| author = Sergio Antoy, Rachid Echahed and Michael Hanus |
|||
| title = A Needed Narrowing Strategy |
|||
| journal = Journal of the ACM |
|||
| volume = 47 |
|||
| issue = 4 |
|||
| pages = 776–822 |
|||
| publisher = ACM |
|||
| year = 2007 |
|||
| url = http://doi.acm.org/10.1145/347476.347484 |
|||
| issn = 0004-5411 |
|||
| doi = 10.1145/347476.347484 |
|||
}}</ref> bewiesen in den Neunzigern, dass eine bestimmte Strategie, das ''needed narrowing,'' optimal ist mit möglichst wenigen Reduktionen eine Normalform zu erhalten. Needed narrowing ist eine Form von [[Lazy Evaluation|Bedarfsauswertung]], im Gegensatz zur [[en:SLD resolution|SLD-Auflösungsstrategie [engl.]]] von Prolog. |
|||
===Funktionale |
=== Funktionale Muster === |
||
Die Regel, mit der oben die Funktion <code>last</code> definiert wurde, zeigt die Tatsache, dass das eigentliche Argument das Ergebnis des Ausdrucks <code>ys ++ [e]</code> treffen muss. |
Die Regel, mit der oben die Funktion <code>last</code> definiert wurde, zeigt die Tatsache, dass das eigentliche Argument das Ergebnis des Ausdrucks <code>ys ++ [e]</code> treffen muss, wobei für <code>ys</code> und <code>e</code> beliebige Werte eingesetzt werden dürfen. Da auf einer Seite nur ein Parameter vorkommt, kann man das alternativ auch auf eine sehr prägnante Art ausdrücken: |
||
last <span style="color:green">'''::'''</span> <span style="color:purple">[</span>a<span style="color:purple">]</span> <span style="color:green">'''->'''</span> a |
last <span style="color:green">'''::'''</span> <span style="color:purple">[</span>a<span style="color:purple">]</span> <span style="color:green">'''->'''</span> a |
||
last (ys <span style="color:darkgreen">++</span> <span style="color:maroon">[</span>e<span style="color:maroon">]</span>) <span style="color:green">'''='''</span> e |
last (ys <span style="color:darkgreen">++</span> <span style="color:maroon">[</span>e<span style="color:maroon">]</span>) <span style="color:green">'''='''</span> e |
||
Rein funktionale Sprachen |
Rein funktionale Sprachen erlauben eine derartige Definition nicht, da das Muster auf der linken Seite eine definierte Funktion enthält (nämlich den Operator <code>++</code>), die nicht notwendig [[Injektivität|injektiv]] ist; ein solches Muster heißt funktionales Muster.<ref>Sergio Antoy and Michael Hanus: Declarative Programming with Function Patterns, LOPSTR 2005 {{DOI|10.1007/11680093_2}}</ref> Funktionale Muster werden durch die Kombination der funktionalen und logischen Eigenschaften sowie der Unterstützung von einfachen Aufgabendefinitionen, die tiefe Mustervergleiche in hierarchische Strukturen erfordern. In diesem Beispiel ist <code>++</code> allgemein nicht injektiv, denn jede nicht-leere Liste kann mit <code>[] ++ l</code> als auch <code>l ++ []</code> erzeugt werden; jedoch können die freien Variablen hier nur auf eine mögliche Art gewählt werden, da sie Einschränkungen unterliegen. |
||
=== |
=== Nichtdeterminismus === |
||
Da |
Da funktionallogische Sprachen in der Lage sind, Funktionsaufrufe mit unbekannten Parametern enthaltende Gleichungen zu lösen, baut das Ausführungssystem auf [[Determinismus (Algorithmus)|nichtdeterministischen]] Berechnungen auf. Dieser Mechanismus ermöglicht auch die Definition von nicht-deterministischen Operationen, die mehrere verschiedene Ergebnisse zu einer gegebenen Eingabe liefern. Die Mutter der nichtdeterministischen Operationen ist der vordefinierte Infix-Operator <code>?</code>, der Auswahl-Operator, der nichtdeterministisch einen seiner Operanden zurückliefert. |
||
(<span style="color:darkgreen">?</span>) <span style="color:green">'''::'''</span> a <span style="color:green">'''->'''</span> a <span style="color:green">'''->'''</span> a |
(<span style="color:darkgreen">?</span>) <span style="color:green">'''::'''</span> a <span style="color:green">'''->'''</span> a <span style="color:green">'''->'''</span> a |
||
Zeile 105: | Zeile 82: | ||
x <span style="color:darkgreen">?</span> y <span style="color:green">'''='''</span> y |
x <span style="color:darkgreen">?</span> y <span style="color:green">'''='''</span> y |
||
Daher gibt die Auswertung des Ausdrucks <code>0 ? 1</code> sowohl <code>0</code> als auch <code>1</code> zurück. Das Rechnen mit |
Daher gibt die Auswertung des Ausdrucks <code>0 ? 1</code> sowohl <code>0</code> als auch <code>1</code> zurück. Das Rechnen mit nichtdeterministischen Operationen und das Rechnen mit freien Variablen unter Einschränkungen hat die gleiche Ausdrucksstärke.<ref>Sergio Antoy and Michael Hanus: Overlapping Rules and Logic Variables in Functional Logic Programs, International Conference on Logic Programming 2006 {{DOI|10.1007/11799573_9}}</ref> |
||
Die Regeln, mit denen <code>?</code> definiert wurde, zeigen ein wichtiges Merkmal |
Die Regeln, mit denen <code>?</code> definiert wurde, zeigen ein wichtiges Merkmal funktionallogischer Sprachen: Alle Regeln werden versucht um eine bestimmte Operation auszuwerten; insbesondere wird nicht die oberste realisierbare Gleichung angewendet. Daher kann man mit |
||
insert <span style="color:green">'''::'''</span> a <span style="color:green">'''->'''</span> <span style="color:purple">[</span>a<span style="color:purple">]</span> <span style="color:green">'''->'''</span> <span style="color:purple">[</span>a<span style="color:purple">]</span> |
insert <span style="color:green">'''::'''</span> a <span style="color:green">'''->'''</span> <span style="color:purple">[</span>a<span style="color:purple">]</span> <span style="color:green">'''->'''</span> <span style="color:purple">[</span>a<span style="color:purple">]</span> |
||
Zeile 113: | Zeile 90: | ||
insert x (y <span style="color:maroon">''':'''</span> ys) <span style="color:green">'''='''</span> y <span style="color:maroon">''':'''</span> insert x ys |
insert x (y <span style="color:maroon">''':'''</span> ys) <span style="color:green">'''='''</span> y <span style="color:maroon">''':'''</span> insert x ys |
||
eine Operation definieren, die ein Element an eine unbestimmte Position einfügt. In funktionalen Sprachen wäre die zweite Gleichung unerreichbar, weil die erste alle möglichen Parameter annehmen kann. Werden die Gleichungen vertauscht, würde die zweite Gleichung nur für leere Listen verwendet, weil die erste alle nichtleeren Listen angewendet wird. In der funktionallogischen Programmierung kann daher das Prinzip, schon in oberen Gleichungen behandelte Fälle nicht mehr zu beachten, nicht verwendet werden; in diesem Sinne muss jede Gleichung so betrachtet werden, als wäre sie die oberste. |
|||
eine Operation definieren, die ein Element an eine unbestimmte Position einfügt, mit der die Operation <code>perm</code>, die durch |
|||
Mit <code>insert</code> wird die Funktion <code>perm</code> durch |
|||
perm <span style="color:green">'''::'''</span> <span style="color:purple">[</span>a<span style="color:purple">]</span> <span style="color:green">'''->'''</span> <span style="color:purple">[</span>a<span style="color:purple">]</span> |
perm <span style="color:green">'''::'''</span> <span style="color:purple">[</span>a<span style="color:purple">]</span> <span style="color:green">'''->'''</span> <span style="color:purple">[</span>a<span style="color:purple">]</span> |
||
Zeile 119: | Zeile 98: | ||
perm (x <span style="color:maroon">''':'''</span> xs) <span style="color:green">'''='''</span> insert x (perm xs) |
perm (x <span style="color:maroon">''':'''</span> xs) <span style="color:green">'''='''</span> insert x (perm xs) |
||
definiert |
definiert, die jede Permutation einer gegebenen Liste zurückgibt. |
||
== |
== Weblinks == |
||
⚫ | |||
⚫ | |||
== Einzelnachweise == |
|||
==Weblinks== |
|||
⚫ | |||
⚫ | |||
[[Kategorie:Programmierparadigma]] |
[[Kategorie:Programmierparadigma]] |
||
[[Kategorie:Programmiersprache als Thema]] |
Aktuelle Version vom 15. April 2024, 06:19 Uhr
Die Funktionallogische Programmierung ist die Vereinigung des funktionalen mit dem logischen Paradigma in einer Programmiersprache. Dies schließt die meisten starken Konzepte der Paradigmen mit ein, dazu gehören Funktionen höherer Ordnung, nicht-deterministische Ausführung und Unifikation. Wegbereiter dieser Schöpfung war λProlog[1] in den Neunzigern. Andere, neuere funktionallogische Programmiersprachen sind Curry und Mercury.
Grundlagen
[Bearbeiten | Quelltext bearbeiten]Die Beispiele sind in der Syntax von Curry.
Funktionale Programmierung
[Bearbeiten | Quelltext bearbeiten]Ein funktionales Programm ist eine Ansammlung von Funktionen oder Regeln. Eine funktionale Berechnung besteht aus dem Ersetzen von Teilausdrücken durch (unter Berücksichtigung der Funktionsdefinition) gleichwertige Teilausdrücke, solange bis keine Ersetzungen bzw. Reduktionen mehr möglich sind und ein Ergebnis bestimmt oder eine Normalform erreicht wird. Einen Teilausdruck, der reduziert werden kann, wird auch als Redex (von englisch reducible expression ‚reduzierbarer Ausdruck‘) bezeichnet. Für die nächsten Beispiele sei die Funktion verdoppeln
definiert durch
verdoppeln x = x + x
Der Ausdruck verdoppeln 1
ist ein Redex und wird durch 1 + 1
ersetzt, was wiederum durch 2
ersetzt werden kann, wobei man den +
-Operator vereinfacht als eine unendliche Menge von Gleichungen der Form 1 + 1 = 2
, 1 + 2 = 3
usw. auffasst. Auf ähnliche Weise können Teilausdrücke ausgewertet werden. Im Beispiel wird der zu ersetzende Teilausdruck kursiv hervorgehoben.
verdoppeln (1 + 2) → (1 + 2) + (1 + 2) → 3 + (1 + 2) → 3 + 3 → 6
Es gibt aber auch noch eine andere Reihenfolge in der die Ausdrücke ersetzt werden können:
verdoppeln (1 + 2) → verdoppeln 3 → 3 + 3 → 6
In diesem Fall führen beide Auswertungen zum gleichen Resultat. Diese Eigenschaft heißt Konfluenz und folgt aus der fundamentalen Eigenschaft reiner funktionaler Sprachen, der referenziellen Transparenz: der zu bestimmende Wert eines Ausdrucks hängt nicht (z. B. aufgrund von Seiteneffekten) von der Reihenfolge oder dem Zeitpunkt der Auswertung ab. Daher sind Beweise leichter zu führen und die Programme leicht wartbar.
Algebraische Datentypen
[Bearbeiten | Quelltext bearbeiten]Ebenso wie funktionale Sprachen unterstützen viele funktionallogische Sprachen die Definition von algebraischen Typen, indem ihre Konstruktoren aufgelistet werden. Beispielsweise wird der Datentyp Bool
für Wahrheitswerte mit den Konstruktoren True
und False
wie folgt definiert:
data Bool = True | False
Funktionen, deren Parameter solche Wahrheitswerte sind, können durch Mustervergleich (Pattern Matching) definiert werden, meistens durch mehrere definierende Gleichungen:
not True = False not False = True
Das Vorgehen Ausdrücke durch gleichwertige zu ersetzen ist weiterhin gültig, wenn die Ausdrücke die entsprechende Form haben:
not (not False) → not True → False
Kompliziertere Datenstrukturen werden durch rekursive Datentypen dargestellt. Beispielsweise eine Liste über einem beliebigen Typen, deren Elemente ebendiesen Typ haben, ist entweder die leere Liste []
oder eine nicht-leere Liste mit einem ersten Element x
und einem Restglied xs
, dargestellt durch x : xs
.
data List a = [] -- [] ist ein parameterloser Konstruktor (d. h. ein Literal), das die leere Liste darstellt. | a : List a -- Der Konstruktor wird infix notiert und ist der Doppelpunkt. -- Er hat zwei Parameter: ein Listenelement und ein Restglied, welches auch leer sein darf.
Üblicherweise notiert man den Typ List a
mit [a]
und endliche Listen x1 : x2 : … : xn : []
mit [x1, x2, …, xn]
. Funktionen auf rekursiven Typen können induktiv definiert werden, wobei Pattern Matching bequemes Fallunterscheiden ermöglicht. Die Konkatenation kann wie folgt definiert werden. Die beiden konkatenierten Listen müssen vom gleichen Typen sein. Variablen für Listen enden oft mit S, vgl. die Pluralbildung in der englischen Sprache.
(++) :: [a] -> [a] -> [a] -- Typdefinition: Zwei Listen mit gleichem Typ werden zu einer.
[] ++ ys = ys -- Leere Liste mit beliebiger Liste konkatenieren (x : xs) ++ ys = x : (xs ++ ys) -- Nicht-leere Liste rekursiv konkatenieren
Logische Programmierung
[Bearbeiten | Quelltext bearbeiten]Es sei zur Demonstration die Funktion last
beschrieben, die das letzte Element einer nicht-leeren Liste zurückgibt. Für alle Listen xs
und Elemente e
gilt: last xs
ergibt e
genau dann, wenn ys ++ [e]
gleich xs
für eine geeignete Liste ys
.
Basierend auf dieser Spezifikation können wir mittels logischer Programmiertechniken eine Funktion definieren, die diese erfüllt. Ähnlich wie logische Sprachen bieten funktionallogische Sprachen Lösungen für die Suche nach existentialquantifizierten Variablen. Im Gegensatz zu rein logischen Sprachen unterstützen sie das Lösen von Gleichungen mit funktionalen Teilausdrücken, sodass ys ++ [e] = [1, 2, 3]
gelöst wird, indem ys
mit der Liste [1, 2]
und e
mit dem Wert 3
instantiiert wird. Dann kann last
wie folgt definiert werden:
last :: [a] -> a last xs | ys ++ [e] =:= xs = e where ys, e free
Hier wird das Symbol =:=
für Gleichheitsbedingungen benutzt, um sie syntaktisch von definierenden Gleichheiten und Vergleichen zu unterscheiden. Auf ähnliche Weise können zusätzliche Variablen gebunden werden, indem sie explizit mit where … free
deklariert werden. Die Deklaration ist notwendig und sinnvoll um Tippfehler zu vermeiden. Eine bedingte Gleichung der Form L | B = R
kann angewendet werden, falls die Bedingung B
gelöst wurde. Im Gegensatz zu rein funktionalen Sprachen, deren Bedingungen ausschließlich Muster oder Ausdrücke vom Typ Bool
sein können, unterstützen funktionallogische Sprachen außerdem das Lösen von Bedingungen durch das Erraten von Werten der Unbekannten in der Bedingung; Muster-Bedingungen sind ein Spezialfall davon. Einschränkungen, die im Folgenden erklärt werden, werden dafür herangezogen.
Einschränkungen
[Bearbeiten | Quelltext bearbeiten]Das Einschränken ist ein Mechanismus, bei dem eine Variable an einen Wert anhand von Alternativen, die von Einschränkungen auferlegt werden, gebunden wird. Alle möglichen Werte werden in einer bestimmten Reihenfolge ausprobiert, wobei der Rest des Programms aufgerufen wird, um die Korrektheit der Bindung zu überprüfen. Einschränkungen sind insofern eine Erweiterung des logischen Programmierens, dass sie eine ähnliche Suche ergeben, jedoch kann sie vielmehr Werte als Teil der Suche erzeugen als lediglich auf das Überprüfen beschränkt zu sein.
Das Einschränken ist insbesondere nützlich, da es ermöglicht, Funktionen wie Relationen zu handhaben; Werte können auf gewisse Art in beide Richtungen bestimmt werden. Unter anderem illustriert dies das vorhergehende Beispiel.
Wie bereits angemerkt können Einschränkungen wie eine Reduktion des Auswertegraphs aufgefasst werden und es gibt häufig viele verschiedene Wege (Strategien) einen Graphen zu reduzieren. Antoy et al.[2] bewiesen in den Neunzigern, dass eine bestimmte Strategie, das needed narrowing, optimal ist mit möglichst wenigen Reduktionen eine Normalform zu erhalten. Needed narrowing ist eine Form von Bedarfsauswertung, im Gegensatz zur SLD-Auflösungsstrategie von Prolog.
Funktionale Muster
[Bearbeiten | Quelltext bearbeiten]Die Regel, mit der oben die Funktion last
definiert wurde, zeigt die Tatsache, dass das eigentliche Argument das Ergebnis des Ausdrucks ys ++ [e]
treffen muss, wobei für ys
und e
beliebige Werte eingesetzt werden dürfen. Da auf einer Seite nur ein Parameter vorkommt, kann man das alternativ auch auf eine sehr prägnante Art ausdrücken:
last :: [a] -> a last (ys ++ [e]) = e
Rein funktionale Sprachen erlauben eine derartige Definition nicht, da das Muster auf der linken Seite eine definierte Funktion enthält (nämlich den Operator ++
), die nicht notwendig injektiv ist; ein solches Muster heißt funktionales Muster.[3] Funktionale Muster werden durch die Kombination der funktionalen und logischen Eigenschaften sowie der Unterstützung von einfachen Aufgabendefinitionen, die tiefe Mustervergleiche in hierarchische Strukturen erfordern. In diesem Beispiel ist ++
allgemein nicht injektiv, denn jede nicht-leere Liste kann mit [] ++ l
als auch l ++ []
erzeugt werden; jedoch können die freien Variablen hier nur auf eine mögliche Art gewählt werden, da sie Einschränkungen unterliegen.
Nichtdeterminismus
[Bearbeiten | Quelltext bearbeiten]Da funktionallogische Sprachen in der Lage sind, Funktionsaufrufe mit unbekannten Parametern enthaltende Gleichungen zu lösen, baut das Ausführungssystem auf nichtdeterministischen Berechnungen auf. Dieser Mechanismus ermöglicht auch die Definition von nicht-deterministischen Operationen, die mehrere verschiedene Ergebnisse zu einer gegebenen Eingabe liefern. Die Mutter der nichtdeterministischen Operationen ist der vordefinierte Infix-Operator ?
, der Auswahl-Operator, der nichtdeterministisch einen seiner Operanden zurückliefert.
(?) :: a -> a -> a x ? y = x x ? y = y
Daher gibt die Auswertung des Ausdrucks 0 ? 1
sowohl 0
als auch 1
zurück. Das Rechnen mit nichtdeterministischen Operationen und das Rechnen mit freien Variablen unter Einschränkungen hat die gleiche Ausdrucksstärke.[4]
Die Regeln, mit denen ?
definiert wurde, zeigen ein wichtiges Merkmal funktionallogischer Sprachen: Alle Regeln werden versucht um eine bestimmte Operation auszuwerten; insbesondere wird nicht die oberste realisierbare Gleichung angewendet. Daher kann man mit
insert :: a -> [a] -> [a] insert x ys = x : ys insert x (y : ys) = y : insert x ys
eine Operation definieren, die ein Element an eine unbestimmte Position einfügt. In funktionalen Sprachen wäre die zweite Gleichung unerreichbar, weil die erste alle möglichen Parameter annehmen kann. Werden die Gleichungen vertauscht, würde die zweite Gleichung nur für leere Listen verwendet, weil die erste alle nichtleeren Listen angewendet wird. In der funktionallogischen Programmierung kann daher das Prinzip, schon in oberen Gleichungen behandelte Fälle nicht mehr zu beachten, nicht verwendet werden; in diesem Sinne muss jede Gleichung so betrachtet werden, als wäre sie die oberste.
Mit insert
wird die Funktion perm
durch
perm :: [a] -> [a] perm [] = [] perm (x : xs) = insert x (perm xs)
definiert, die jede Permutation einer gegebenen Liste zurückgibt.
Weblinks
[Bearbeiten | Quelltext bearbeiten]- Functional logic programming an der Universität Kiel
Einzelnachweise
[Bearbeiten | Quelltext bearbeiten]- ↑ Gopalan Nadathur, D. Miller: Logic Programming. Hrsg.: D. M. Gabbay, C. J. Hogger, J. A. Robinson (= Handbook of Logic in Artificial Intelligence and Logic Programming. Band 5). Oxford University Press, 1998, ISBN 0-19-853792-1, Higher-Order Logic Programming, S. 499–590.
- ↑ Sergio Antoy, Rachid Echahed and Michael Hanus: A Needed Narrowing Strategy. In: Journal of the ACM. Band 47, Nr. 4. ACM, 2007, ISSN 0004-5411, S. 776–822, doi:10.1145/347476.347484 (acm.org).
- ↑ Sergio Antoy and Michael Hanus: Declarative Programming with Function Patterns, LOPSTR 2005 doi:10.1007/11680093_2
- ↑ Sergio Antoy and Michael Hanus: Overlapping Rules and Logic Variables in Functional Logic Programs, International Conference on Logic Programming 2006 doi:10.1007/11799573_9