Sitzung: Jeden Freitag in der Vorlesungszeit ab 16 Uhr c. t. im MAR 0.005. In der vorlesungsfreien Zeit unregelmäßig (Jemensch da?). Macht mit!

Opal FAQ

History Lesson!
Dieser Artikel spiegelt sehr wahrscheinlich nicht mehr den aktuellen Stand wider, könnte aber trotzdem von Interesse sein.

Vor noch nicht allzu langer Zeit, an einem nicht allzu weit entfernten Ort wurden einst alle Studis von einer schweren Programmiersprache dahingerafft ...

Aber nein! Nicht alle!

Eine kleine Gruppe unermüdlich lernender Studenten widersetzten sich dem Bösen, wild entschlossen diesem Missstand ein ende zu bereiten, setzten sie sich zusammen und schrien "FAQ-Opal" gen Himmel...

Nun ja, mehr spare ich mir hier, auf jeden Fall haben "damals" jede menge Leute in der Newsgroup informatik1 (bln.lv.tub.cs.informatik1) gepostet und sehr wertvolle Tips und Tricks zusammengetragen. Diese sind hier aufgeführt mit der ausdrücklichen Bitte, sie zu ergänzen.

Eine weitere (zum Großteil englische) Informationsquelle sind die offiziellen Dokumentationen. Außerdem gibt es seit 2012 Bestrebungen eine Web-Umgebung für OPAL zu entwickeln. Wer diese nutzen möchte, findet sie unter: http://opal.gehaxelt.in/, auch Entwickler sind herzlich willkommen: https://github.com/gehaxelt/PHP-Webopal/.

Inhaltsverzeichnis

Wie sieht eine OPAL-Struktur aus?

Minimalbeispiel für eine OPAL-Struktur

Na gut, es ginge noch kleiner, aber an diesem Beispiel kann man mehr erklären.

SIGNATURE MeineStruktur
IMPORT Nat ONLY nat

FUN meinPlus : nat ** nat -> nat

Für die Signatur (MeineStruktur.sign) und

IMPLEMENTATION MeineStruktur
IMPORT Nat COMPLETELY

FUN meinPlus : nat ** nat -> nat
DEF meinPlus(a,b) == a + b

für die Implementierung (MeineStruktur.impl)

Zuallererst: Der Dateinname (ohne Endung), also der Name der Struktur, muss in der Schreibung exakt mit dem Namen der Signatur (hinter SIGNATURE) und der Implementierung (hinter IMPLEMENTATION) übereinstimmen und darf keine Leerzeichen oder Sonderzeichen enthalten. Nach Übereinkunft werden Strukturnamen mit großem Anfangsbuchstaben geschrieben.

Als nächster Begriff taucht in beiden Dateien IMPORT auf. Hiermit werden andere Strukturen eingebunden. Diese müssen sich entweder im selben Verzeichnis befinden, oder zu OPAL gehören. Darauf folgt der Name der Struktur (im Beispiel Nat für die natürlichen Zahlen). In der Signatur folgt nun der Begriff ONLY. Damit sagt man, dass man nur bestimmte Teile der Struktur benötigt, im Beispiel nat (dies ist die Sorte der natürlichen Zahlen). Was man benötigt ist, in der Signatur, gewöhnlich nur das, was nach dem : steht, ohne die Zeichen ** und ->.

In der IMPLEMENTATION sieht der Import etwas anders aus: hier wird COMPLETELY benutzt. COMPLETELY bedeutet, dass alles aus der Struktur in importiert wird, in diesem Fall also die Zahlen, Funktionen für Addition und Multiplikation und noch eine ganze Menge mehr (man könnte auch ONLY benutzen, aber dann müsste man jede Funktion, Zahl, usw. hinter dem ONLY notieren). Was nun folgt ist in beiden Dateien wieder das gleiche:

FUN meinPlus : nat ** nat -> nat

Hiermit wird eine Funktion deklariert, das Schlüsselwort FUN bedeutet, dass an dieser Stelle eine Funktion angekündigt wird. Danach folgt der Name der Funktion (in diesem Fall meinPlus), nach dem Doppelpunkt folgt die Abbildung. Dies Funktion bildet zwei natürlichen Zahlen (nat) auf eine natürliche Zahl ab. Einfacher: die Funktion nimmt zwei Eingabewerte an und gibt einen Wert zurück und alle Werte haben denselben Typ. D.h. es dürfen nur (in diesem Fall) nur Zahlen angegeben werden.

Hier endet die Signatur bereits, denn sie dient nur dazu anderen Strukturen zu zeigen was in der Struktur enthalten ist. Ähnlich einem Taschenrecher: aus der Taste seht ihr das Pluszeichen und wisst: dieser Taschenrechner kann addieren. Was ihr nicht wisst, ist wie intern addiert wird. Bei OPAL steht das in der Implementierung nach dem Begriff DEF für Definition.

Wie sieht ein Funktions-Deklaration in OPAL aus?

FUN funktion : ausgabe

Für eine konstante Funktion. Diese Funktion wird aufgerufen und gibt immer den selben Wert zurück. In OPAL bspw. die Funktion pi, sie gibt immer (näherungsweise) Pi zurück.

FUN funktion : eingabe -> ausgabe

Für eine Funktion, die zu einem Wert übergeben bekommt und einen Wert zurück gibt. In OPAL bspw. die Funktion ~ sie erhält einen Wahrheitswert und negiert diesen.

FUN funktion : eingabe1 ** eingabe2 -> ausgabe

Für eine Funktion, die zwei Werte annimmt (ein Tupel) und daraus einen Wert macht. Beispiele in OPAL sind: +,-,*.

FUN funktion : eingabe -> ausgabe1 ** ausgabe2

Für eine Funktion, die einen Wert erhält und ein Tupel zurück gibt. Eine solche Funktion könnte bei einem benannten Punkt als Eingabe, die Koordinaten als Tupel zurück geben.

Vor bzw. nach dem -> können beliebig viele Eingaben bzw. Ausgabe notiert werden:

FUN funktion: eingabe1 ** eingabe2 ** eingabe3 -> ausgabe1 ** ausgabe2

Dies Funktion macht aus einem Tripel ein Tupel.

Wie sieht ein Kommentar in OPAL aus?

Warum überhaupt Kommentare?

Selbst geübte Programmierer wissen bei bestimmten Quelltexten nicht sofort, was sie bedeuten. Das einfachste Mittel um diesen Missstand auszuräumen ist die Nutzung von Kommentaren. Es gehört zum guten Stil seine Projekte angemessen zu kommentieren, außerdem sind Kommentare ein gute Gedächtnisstütze.

Wie sieht ein Kommentar in OPAL aus?

In OPAL gibt es zwei Arten von Kommentaren: einzeilige Kommentare und mehrzeilige:

-- Ich bin ein Kommentar.

Einzeilige Kommentare beginnen mit der Zeichenkombination --, danach muss ein Leerzeichen folgen. Nach diesem Zeichen wird alles bis zum Ende der Zeile als Kommentar gewertet uns nicht weiter interpretiert.

/* Ich bin ein
mehrzeiliger Kommentar
  /* P.S. Mich kann man verschachteln */
*/

Ein mehrzeiliger Kommentar beginnt mit der Zeichenfolge /* (hier muss nicht zwingend danach ein Leerzeichen gesetzt werden). Danach wird alles, was bis zur Zeichenfolge */ geschrieben wurde nicht weiter Interpretiert. Diese Kommentare eignen sich für längere Texte (z.B. Urhebervermerke, Lizenzen) und zum auskommentieren von unfertigem Code:

DEF test(<>) == <>
/* Hier ist irgendwo ein Fehler
DEF test(a::A) == IF a > 5 THEN a::<>
                  ELSE test(A)
                      <-- fehlt da was?
*/

Der vorige Quelltext ist fehlerhaft und würde deshalb nicht ausgeführt werden. Die fehlerhafte Stelle wurde auskommentiert, um das Programm ausführbar zu machen (außerdem wurde zusätzlicher Text vermerkt). D.h. ein Kommentar darf auch Schlüsselwörter enthalten, diese werden aber nicht beachtet.

DEF funktion(a,b) == IF a = b -- THEN c
                     ELSE d
                     FI

Die Funktion würde zu einem Fehler führen, denn das THEN würde nicht mehr interpretiert werden, weil ein Kommentarzeichen davor steht.

Wie konstruiert man eine Liste?

Herzlichen dank für diesen Beitrag an Florian Lorenzen .

> Man kann dann viele aussagekräftige Beispiele mit :: und <> > angeben (seq, seq[seq], leere seq usw.)

Da ich, wie ich fürchte, mit diesem unsäglichen % angefangen habe -- nicht ahnend welchen didaktischen Flurschaden ich damit anrichten würde -- hier zum Ausgleich einige erklärende Beispiele mit den Listenkonstruktoren.

1. Sei S eine Sequenz aus alphas (FUN S : seq[alpha]).

DEF S == a :: b :: c :: d :: <>

S ist eine Sequenz aus a, b, c, d und der leeren Liste. Da :: rechtsassoziativ ist, wird der obige Ausdruck wie folgt aufgelöst:

~~~> a :: (b :: (c :: (d :: <>)))

Es wird also zunächst eine Liste aus d und <> konstruiert, vor die dann c gehängt wird. Die rekursive Struktur der Liste

Liste := Element :: Liste

ist deutlich.

DEF S == <>

S ist eine leere Liste.

Bei langen Elementen kann die Liste der Übersicht halber ohne weiteres auch umgebrochen werden.

   "laaaaaaaaaaaaaaaaaaaaaaaaaaange Wortguppe" :: "nicht ganz so lange Wortgruppe" :: "etwas kürzere Wortgruppe" :: "noch kürzere Wortgr" :: "noch k" :: <>

vs.

   "laaaaaaaaaaaaaaaaaaaaaaaaaaange Wortguppe"
:: "nicht ganz so lange Wortgruppe"
:: "etwas kuerzere Wortgruppe"
:: "noch kuerzere Wortgr"
:: "noch k"
::  <>

2. Sei S eine Sequenz von Sequenzen von alphas (FUN S : seq[seq[alpha]]).

DEF S == (a :: b :: c :: <>) :: (d :: e :: <>) :: <>

Dieser Ausdruck wird wie folgt aufgelöst:

~~~> (a :: b :: c :: <>) :: ((d :: e :: <>) :: <>)

Es wird also zuerst eine Liste aus dem (d :: e :: <>) und <> kostruiert, vor die dann das Element (a :: b :: c :: <>) gehängt wird. Das die beiden erwähnten Elemente wiederum Listen sind, ändert nichts daran, wie die äußere Sequenz konstruiert wird.

DEF S == (a :: b :: c :: <>) :: <>

S ist eine Liste mit gerade genau einem Element, das wiederum eine Liste ist.

DEF S == <> :: <> :: <>

S ist eine Liste, die aus zwei leeren Listen besteht, nicht aus dreien, wie es zuerst scheint. Dies wird wiederum deutlich, wenn der Ausdruck aufgelöst wird:

~~~> <> :: (<> :: <>)

In der Klammer wird aus dem Listenelement "<>" durch ":: <>" eine Liste erzeugt, vor die dann das Element "<>" gehängt wird. dass die beiden Elemente der Liste nun leere Listen (dass es Listen seien müssen, ist sowieso klar) sind, ändert nichts daran, wie die äußere Liste konstruiert wird. Man beachte, dass die enstandene Liste nicht leer ist, dass also aus vielen leeren Listen eine nicht-leere Liste entstehen kann.

Eine Liste aus drei leeren Listen sieht dann dementsprechend folgendermaßen aus:

DEF S == <> :: <> :: <> :: <>

Eine Liste aus einer leeren Liste so:

DEF S == <> :: <>

Erst die Definition

DEF S == <>

erzeugt eine Liste von Listen, die wirklich leer ist.

Programmierpraktisch sollte beachtet werden, dass

S == <> :: <> oder S == <> :: <> :: <> :: ...

nicht durch <>?(S) oder das Muster f(<>) angefangen werden kann, da S eindeutig nicht leer ist.

Benutzung des Interpreters oasys

Starten von oasys, Ausführen von Programmen

Der OPAL-Interpreter oasys lässt sich von der Shell (auch Terminal oder Konsole) aus starten mit:

oasys

Um den Interpreter zu verlassen gibt man im Interpreter ein q für Quit ein:

> q

Hat man nun seine Dateien erstellt (z.B. Aufgabe1.impl und Aufgabe1.sign), muss man diese Dateien zuerst laden. Dafür muss man oasys in dem Verzeichnis starten, in dem die Dateien gespeichert sind. In welchem Verzeichnis ihr euch momentan befindet, erkennt ihr (meistens) an dem Verspann der Konsole.

user@opalix ~/MPGI1/HA1/ $

Dieser Nutzer befindet sich, ausgehend von seinem Benutzerverzeichnis, im Verzeichnis MPGI1/HA/. Welche Dateien und Ordner sich noch in diesem Verzeichnis befinden, kann man mit dem Befehl ls oder dir herrausfinden. Mir dem Befehl cd kann das Verzeichnis gewechselt werden (in das übergeordnete Verzeichnis gelangt man bspw. mit der Angabe cd ..:

user@opalix ~/MPGI1/HA1/ $ cd ..
user@opalix ~/MPGI1/ $

Also wechselt in das Verzeichnis wo sich die Dateien Aufgabe1.impl und Aufgabe1.sign beispielsweise befinden und starten von dort aus auf der shell den OPAL-Interpreter oasys. Wenn ihr oasys gestartet habt, könnt ihr das Verzeichnis nur wechseln, wenn ihr oasys vorher beendet (mit dem Befehl q), es ist jedoch ohne Probleme möglich eine weitere Shell zu öffnet und dort oasys in einem anderen Verzeichnis zu starten.

Als nächstes müssen die Dateien dem Interpreter zur Verfügung gestellt werden. Mit dem Interpreter-Befehl a (append file = Datei anfügen) lassen sich Dateien in den Interpreter laden. Dabei gibt man nur den Dateinamen ohne die Dateiendung an (bei langen Namen kann die Tab-Taste zur automatischen Vervollständigung genutzt werden, der Punkt am Ende ist egal).

> a Aufgabe1
loading Aufgabe1.impl
loading Aufgabe1.sign
>

Wenn die Dateien erfolgreich geladen wurden wird das wie hier in dem Beispiel gezeigt quittiert. Außerdem erstellt oasys ein Verzeichis namens OCS.

Für oasys ist es notwendig, dass man zunächst den Fokus auf eine spezielle Datei legt. Dies erreicht man mit dem Befehl f (f für set focus). Wenn man die impl-Datei fokussiert, kann man auf alle Funktionen zugreifen die in dort implementiert wurden. So kann man aber unter Umständen vergessen eine vernüftige Signature (sign-Datei) zu schreiben und eine entsprechende Kapselung vorzunehmen (wichtig sobald mit mehr als einem Modul gearbeitet wird). Es ist gut sinnvolle Kapselung gleich mit zu lernen.

> f Aufgabe1.impl
Aufgabe1.impl> 

oder: um zu testen welche Funktionen von außen erreichbar sind:

> f Aufgabe1.sign
Aufgabe1.sign> 

Wenn das Fokussieren funktioniert hat, verändert sich eure Eingabe wie im Beispiel angegeben. Um Funktionen auszuführen befehligt man oasys mit dem Befehl e (e für evaluate). Dabei wird aus der fokussierten Datei eine Funktion mit den Parametern die ihr in der Signatur festgelegt habt ausgewertet. Um diese vielen Begriffe zu verdeutlichen gehen wir mal davon aus, dass wir in der Datei eine Funktion add implementiert haben die zwei ganze Zahlen addiert. Diese Funktion ruft man dann so auf:

Aufgabe1.impl>e add(1,2)
starting evaluator process
3
Aufgabe1.impl>

Eingabe von Zahlen, Denotations (Strings), Sequenzen und einzelne Buchstaben (Chars)

Beispiel:

> e funktionMitVielenArgumenten("lkajbblsd", "627849"!, 30,"c"!, %(1,23,9,1,8))

Erklärung: Das erste Argument ist eine Denotation, d.h. einfach ein String. Die muss man einfach immer in Anführungszeichen setzen. :)

Das zweite ist eine Zahl. Da sie größer als 32 ist muss man sie als String hinschreiben (Anführungszeichen), den man dann mit dem Operator "!" in eine Zahl umwandeln kann.

Das dritte ist eine kleinere Zahl als 32, daher kann man sie direkt hinschreiben.

Das vierte ist ein Char, also ein einfacher Buchstabe. Da man das nicht direkt eingeben kann übergibt man Opal wieder einen String und wandelt ihn (da er nur einen Buchstaben enthält) mit dem "!" in einen Char um.

Das fünfte ist eine Sequenz, man kann sie direkt in dieser Form hinschreiben, zu beachten gibt es nur ;) dass man so höchstens 8 Elemente verknüpfen kann. Will man mehr eingeben muss man eben 2 (oder noch mehr) dieser Dinger mit ++ verknüpft hinschreiben.

Diese Art Sequenzen zu schreiben eignet sich für Programmtests, wenn man sich die etwas umständliche Schreibweise sparen möchte. Wenn in einer Aufgabe der Umgang mit Listen geprüft werden soll, wird diese Schreibweise nicht empfohlen, da sie den rekursiven Aufbau der Liste verschleiert. Wenn eine Liste aber ähnlich einer Menge benutzt wird, ist die %-Schreibweise durchaus legitim.

Bsp: eine seq[nat] könnte man also so schreiben.

%(4,3,5,8,2,21,5,5) ++ %(6,"78"!,"269"!,"987"!,"654"!,"321"!)

Eine seq[seq[nat]] könnte man dann so schreiben

%( %(1,3,2), %(1,2,3))

(man beachte die notwendige Umwandlung da die Zahlen zum Teil größer als 32 sind. :))

(!!!) Vorsicht (!!!) man sollte im Normalfall keine Leerzeichen zwischen die Argumente setzen! Das funktioniert zwar meistens, leider aber nicht immer! (Das gilt natürlich nur für die Kommandozeile, in den Syntax-Files selber sind Leerzeichen ziemlich wurscht. :))

Also nochmal: Vorsicht vor Leerzeichen in Funktionsaufrufen!

Ich hatte schon Fehlermeldungen weil ich in Funktionen nach dem Komma ein Leerzeichen eingebaut hatte.

Bsp: func1("9023"!, "0234"!)

Lässt man das weg, funktionierte (bisher) alles:

func1("9023"!,"0234"!)

Speziell wenn man Texte in Nummern umwandelt (siehe oben) scheint das zu Problemen zu führen. (Dieses Problem hatte ich selbst bisher nur wenn ich von der Shell (Kommandozeile aus direkt Argumente in eine Funktion füttern wollte. Ansonsten trat es nicht auf. :)

Oasys an der Shell herumkommandieren:

Herzlichen Dank an Roman Lechtchinsky

Warum funktioniert der Aufruf

> e f(\\x. 2 * x, 4)

unter oasys nicht?

Das liegt daran, dass Du in oasys fuer jeden Backslash \\ eingeben muss, also

> e f(\\\\x. ...)

Das hat nichts mit OPAL zu tun, sondern mit der Skript-Sprache (TCL), in der das Interpreter-Frontend implementiert ist (nehme ich zumindest an). Anm.: angeblich ist die readline-Bibliothek verantwortlich...

Alternativ kann man das quote von TCL (das sind geschweifte Klammern) um den ganzen Ausdruck legen, dann kann man es auch bequem, wie in einer Datei eingeben, also

> e {f(\\x. 2 * x, 4)}

Fehler/Probleme mit OPAL

Warum funktionieren Zahlen über 32 nicht?

In Opal sind die Zahlen auch Funktionen (genauer konstante Funktionen), d.h. wenn eine Zahl eingeben wird, wird eine Funktion mit dem Namen der Zahl ausgeführt, die die Zahl als Wert zurückgibt. Deshalb müsste für jede Zahl eine Funktion existieren. Da das nicht möglich ist, gibt es eine Funktion, die eine Zeichenkette (denotation) in eine Zahl umwandelt. Diese Funktion heißt !:

"zahl"!

Aber warum funktioniert das bei Zahlen unter 32 auch ohne diese Funktion? Naja, einige Zahlen sind für bequemeres Eingeben doch als Funktion definiert:

Die Zahlen von 0-32, die Zweierpotenzen 64,128,256,512,1024 und die Zahlen 100, 1000, 10000, 100000, 1000000. Daneben gibt es noch die Konstanten pi und e für die ludolfsche Zahl (Pi) und die eulersche Zahl.

Warum funktionieren (manchmal) auch Zahlen unter 32 nicht?

Dies ist meistens der Fall, wenn das Programm mit oasys über die Signatur aufgerufen wird. In der Signatur werden meistens nur die Sorten (bspw. nat, denotation) importiert, da Zahlen auch Funktionen sind, müsste man diese auch importieren. Etwas flexibler ist es, die Umwandlungsfunktion ! zu importieren. Dann müssen zwar alle Zahlen erst von einer Zeichenkette umgewandelt werden, aber so kann man überhaupt mit Zahlen arbeiten:

IMPORT Nat ONLY nat !

Natürlich kann man auch COMPLETELY statt ONLY benutzen. Dies wird aber teilweise als schlechter Stil empfunden.

Warum funktionieren Kommazahlen nicht?

Zuallererst: Kommazahlen werden in Opal mit einem Punkt notiert, bspw. ist 3,14159 in Opal 3.14159.

Wie bei der Frage zu Zahlen über 32 bereits beschrieben, müssten gleich benutzbare Zahlen als (konstante) Funktion in einer Struktur definiert (und deklariert) sein. Das ist nicht der Fall (außer bei pi und e). Der Standardfehler den oasys in diesem Fall ausgibt ist:

ERROR [at ...] no matching operation for .
ERROR [check] language error

oasys hält also das Komma (den Punkt) für eine (binäre) Funktion. Um Kommazahlen in Opal zu benutzen, müssen sie "kontruiert" werden. Hierfür enthalten Strukturen, die Kommazahlen nutzen, die Funktion !. Kommazahlen werden also als Denotation geschrieben: "3,14159" und dann mit der (unären) Funktion ! umgewandelt: !"3,14159" oder "3,14159"!, inbesondere bei der Nutzung in längeren Gleichungen oder der oasys-Eingabe kann dieses Ausrufezeichen (da unär) schon einmal für unerwartete Fehler sorgen (mehr dazu Was ist Präfix-, Infix- und Postfix-Notation?).

Warum kann man Denotations immer nutzen und Zahlen nicht?

Wenn eine OPAL-Struktur interpretiert (und später übersetzt) wird, lädt der Interpreter bestimmte Strukturen automatisch. Diese heißen bspw. BOOL und DENOTATION und stellen elementare Funktionen zur Verfügung. Deshalb kann man ohne den Import von irgendwelchen Strukturen IF-THEN-ELSE-Konstrukte in OPAL nutzen und mit Denotations arbeiten (nicht aber mit Zahlen, denn die werden nicht automatisch importiert). Die in der Schreibung ähnlichen Strukturen Bool und Denotation stellen aber noch weitere Funktionen zur Verfügung, deshalb müssen sie gelegentlich doch importiert werden.

Warum wird meine Backtick-Funktion (`) nicht benutzt?

Die Backtick-Funktion für eigene Datentypen muss in der .sign-Datei mit deklariert sein.


Warum funktioniert WHERE nicht ohne weiteres mit Lambda-Ausdrücken?

Wenn man Lambda mit WHERE kombinieren will muss man den ganzen Body der Funktion klammern

Das geht so:

DEF qsolve1 == \\ a, b, c .(   /* <--- */
   (x1, x2)
        WHERE
        x1 == (-(b) + d) / (2 * a)
        x2 == (-(b) - d) / (2 * a)
        d == sqrt((b * b) -  (4 * a * c))   

)  /* <----- */

Wichtig ist dabei, dass nach dem Punkt, mit dem der Lambda Ausdruck die Argumentliste abschließt, kein Leerzeichen oder irgendetwas anderes ist, sondern gleich die öffnende Klammer kommt. Ansonsten funktioniert es nicht!


Häufige Fehlermeldungen

Folgende Fehlermeldung ist ziemlich haeufig:

Expected ... instead of ....

Hier handelt es sich um einen einfachen Syntaxfehler. Falls Ihr meint, alles korrekt geschrieben zu haben, überprueft auch nochmal die Klammerung der Ausdrücke (es muss genausoviele öffnende wie schliessende Klammern geben). Vielleicht ist ja auch nur ein FI vergessen worden...

Expected was `:' instead of `->'

Dieser Fehler kann trotz korrekt vorhandenem : auftreten. Beispiel:

FUN `: nat -> stream -> denotation

erzeugt den Fehler, dahingegen

FUN ` : nat -> stream -> denotation

nicht.

Es hilft also eventuell das Einfügen von Whitespace.

improperly named function definition target or parameter ...

In diesem Fall sollte man ueberpruefen, ob die Deklaration der Funktion (FUN ...) fehlt. Vielleicht ist gerade dieser Teil auskommentiert, oder man hat schlicht und einfach vergessen, die Signaturdatei zu speichern. Mir ist letzteres auch schon unterlaufen, d.h. man sucht nach einem Fehler, der eigentlich nicht vorhanden ist.

missing else in ...

Passiert, wenn bei der Verwendung des Dijkstra-IFs während der Auswertung ein Fall eingetreten ist, der nicht abgedeckt wird. Bei dieser Funktion würde es z.B. bei testeZahl(10) auftreten:

DEF testeZahl(x) == IF x > 10 THEN true IF x < 10 THEN false FI

Es müsste ein < oder > durch <= bzw. >= ersetzt werden, oder der Fall = explizit aufgeführt werden.

ambigious infix application

Dieser Fehler deutet darauf hin, dass bei Infix-Notation die Klammern nicht eindeutig gesetzt werden konnten. Ein Beispiel sind die logischen Funktionen /\ (und) und \/ (oder). Opal hat hierfür keine Klammerregelung, der Ausdruck

false /\ true \/ true

kann auf zwei Arten geklammert werden: entweder (false /\ true) \/ true (ausgewertet zu true) oder false /\ (true \/ true) (ausgewertet zu false).

Was ist Präfix-, Infix- und Postfixnotation?

...oder wie man Minuszeichen hassen lernt.

Bei der Präfix-Notation wird der Funktionsname vor den Argumenten geschrieben, bspw. !"33", oder +(3,5). Bei Funktionen mit mehr als einem Parameter müssen die Argumente in Klammern gesetzt und durch Kommas getrennt werden.

Die Infix-Notation ist nur bei zweistelligen (oder binären) Funktionen möglich (also Funktionen mit genau zwei Parametern). Beispiele dafür sind die Funktionen +, /\, *, usw.:

5 + 6
true /\ false
4 * 6

Bei den meisten zweistelligen Funktione sind auch solche Konstruktionen möglich: 3 + 4 + 9 + 2, die Klammerung wird implizit vorgenommen, auch Mischungen wie 3 + 4 * 7 + 3 werden den mathematischen Gesetzen entsprechend geklammert.

Die Postfix-Notation ähnelt der Präfix-Notation, nur dass hier hier der Funktionsname nach den Parametern notiert wird. Bspw. "33"! oder (6,3)-.

Insbesondere bei einstelligen (unären) Funktionen, wie !, oder - (als Vorzeichen), kommt es öfter zu solchen Fehlermeldungen:

ERROR [HOF.impl at 32.5-32.33]: undefined identification
 1. <32,5-33> wrongly typed implementation
     left:  real->real
     right: ((nat**nat->nat)->real)->real
 2. <32,5-33> wrongly typed implementation
     left:  real->real
     right: ((real**real->real)->real)->real
 3. <32,5-33> wrongly typed implementation
     left:  real->real
     right: ((real->real)->real)->real

ERROR [check]: language error
aborted

Könnte das etwas mit dem Gebrauch von Minuszeichen zu tun haben. :(

Ich hatte größte Schwierigkeiten den folgenden Code zum Funktionieren zu überreden, Code-Zeilen wie das hier:

FUN test : ( real -> real ) -> ( real -> real )
DEF test( r ) == \\ x . r( -x )

Funktionieren nicht.

Das Problem ist das der Compiler für das minuszeichen ( r( -x )) zwei Argumente zu erwarten scheint. (Obwohl er das laut Spezifikation nicht müsste)

Die Löung lag für mich darin entweder "0 - x" zu schreiben oder das x in eigene Klammern zu setzen ( x ). Beides funktioniert hier.

Einen änlichen Fehler hatte ich als ich so etwas schreib: " a - b + c "

Ich musste das dann als ( a - ( b + c ) ) schreiben damit es funktionierte.

Hope it helps!

Hier habe ich noch eine Erklärung von Klaus Didrich bekommen (thx!)

Das Prinzip, dass Funktionen keine Sonderbehandlung erfahren, erstreckt sich auch auf das unäre Minuszeichen. Folgendes geht:

-(1)       (analog zu "f(1)")
(1-)    (analog etwa zu "(5!)")

Dein Problem mit "a - b + c" kann ich allerdings nicht nachvollziehen.

Und gleich nochmal etwas, hierfür danke ich Archi Varius.

+ ( 0, 1 ) = 0 + 1 = ( 0, 1 ) +

! 5 = 120 = 5 !

- 1 = 1 -


Und nochmal. :)) Diesmal herzlichen Dank wieder an Klaus Didrich.

Wird den in Opal, jeder Infixoperator der nur einen Operand bekommt hinter den Operand geschrieben wird? (Das fände ich ja etwas beschränkt...)

Es ist viel schlimmer :-) Es ist beides möglich. Du kannst jeden einstelligen Operator auch hinter den Operanden schreiben, dazu brauchst Du keine besondere Deklaration.
Ob man IF <>?(S) THEN ... oder IF S <>? THEN ... schreibt, macht keinen Unterschied. Eine Mischung aus Postfix- und Infix-Applikationen kann der Compiler leider nicht analysieren, da kommt dann die Meldung "no possible bracketing for infix found". In dem Fall hilft Klammern: (5!) + 3, (S#) > 0
Auch bei zweistelligen Funktionen kann man es sich aussuchen, ob man die Präfix- oder die Infixschreibweise vorzieht: +(5,3) oder 3 + 5 sind beide erlaubt.

Opal und Zugriffsrechte (ext2 versus fat)

Herzlichen Dank für diesen Tip an Thomas Brinker!

Du setzt Opal unter Linux ein

Opal-fehlermeldung: Operation not permitted

Grund: Die Dateien liegen nicht in einer ext2-Partition sondern in einer FAT-Partition. (in FAT geht das mit den named Pipes nicht)

Abhilfe:

Dateien in ext2 Partition umkopieren. Alternativ die aktuelle Opal-Version 2.3m benutzen, wenn /tmp auf einer Partition liegt, die named Pipes unterstützt.

Du setzt Opalix unter VirtualBox mit Shared Folders ein

Im Grunde das gleiche wie vorher, siehe Opalix-Seite.

Was bedeutet ein Fehler in Zeile 0?

Bei einem ansonsten korrekten Programm hat mich diese Fehlermeldung vorhin eine laaange Fehlerjagd gekostet...

Person.impl>e Birthday(date(30,12,"79"!)
ERROR [at 0.3-0.4]: Expected was `)' instead of `IN'
ERROR [check]: language error
aborted

In diesem Fall hatte ich auf der Eingebe-Zeile im Interpreter eine schließende Klammer vergessen. :))

Man kann diese Fehler recht leicht erkennen. Sie finden immer auf Zeile 0 statt. Das ist die Eingabe-Zeile in der Shell. :)) (" ERROR [at 0.3-0.4]:")

Der Fehler muss folglich auch nicht immer ein Syntax-Fehler sein (wie oben), sondern kann auch durch die Sprache in der die Eingabe von oasys programmiert ist hervorgerufen werden (siehe Oasys an der Shell herumkommandieren).

Nicht nachvollziehbare Laufzeitfehler

Wenn das eigene Programm eine Struktur S enthält und die Bibliotheca Opalica ebenfalls eine Struktur S bereitstellt, kann es unter Umständen zu nicht nachvollziehbaren Laufzeitfehlern komen, wie etwa

RUNTIME ERROR: TreeConv at <252,5-11> : missing else in asSeqIn'TreeConv:tree**seq->seq
Aborted (core dumped)

unter Ubuntu oder

Bus Error (core dumped)

unter Solaris 10 Sparc.

In diesem Fall enthält das Programm eine Struktur Tree und die Bibliotheca Opalica hat ebenfalls eine solche Struktur.

Die einzige Lösung des Problems ist die Umbenennung der Struktur S, da alle zu einem Opal-Programm gelinkten Strukutren verschiedene Namen haben müssen. Eine Übersicht aller in der Bibliotheca Opalica vorhandenen Strukturen gibt es bspw. über den "Structure Index" der Dokumentation: http://projects.uebb.tu-berlin.de/opal/dosfop/latest/bibopalicaman/Structure_Index.html, Alternativ können die Befehle l, oder ll (umfangreicher als l) in oasys benutzt werden.

Es ist ein Bug im Opal-Compiler, dass in solchen Fällen keine Fehlermeldung ausgegeben wird, sondern es so aussieht als ob das Programm korrekt übersetzt wurde. Details können in diesem Ticket nachgelesen werden: http://projects.uebb.tu-berlin.de/opal/trac/ticket/50

Unter MacOS X tritt das Problem aufgrund des Mach-O Formats und einer anderen Link-Semantik nicht auf.

oasys: Too many open files

oasys detektiert einen verboteten zyklischen Import innerhalb von IMPLEMENTATIONs nicht.

Es gibt stattdessen die Fehlermeldung sobald eine Funktion des Programms ausgewertet wird:

starting evaluator process
sh: 1: 1: Too many open files
evaluator: cannot retrieve symbols of `pconn-in-289-25/10/94'
connection lost

Ausserdem werden im temporären Verzeichnis (z. B. /tmp) Unmengen von Dateien liegengelassen.

Zyklische Importe sind in Opal verboten. Die einzige Losung ist also eine Umstrukturierung des Programms. Der Opal-Compiler ocs detektiert das Problem korrekt.

Hilfreiche Tipps

Zwischenergebnisse zur Fehlersuche ausgeben (printf-Debugging)

Oftmals wünscht man sich eine Möglichkeit, Zwischenergebnisse auf der Konsole auszugeben, z.B. um Fehler zu finden und den Programmablauf zu verfolgen. Da man in einer rein-funktionalen Sprache wie Opal nicht einfach ein printf einfügen kann, muss man etwas tricksen. Mit Hilfe der "Funktion" EXEC aus der Com-Struktur kann aus dem Ablauf heraus Text ausgeben, da sie eine Möglichkeit darstellt, Seiteneffekte auszufühen. Das sieht dann so aus:

SIGNATURE DebugHelper[ResultType]

SORT ResultType

FUN || : ResultType ** denotation -> ResultType
IMPLEMENTATION DebugHelper[ResultType]

IMPORT	Com		COMPLETELY
	Stream		COMPLETELY
	ComCompose	COMPLETELY

FUN || : ResultType ** denotation -> ResultType
DEF result || msg == EXEC(writeLine(stdOut, msg); succeed(result))

Speichert die beiden Code-Schnippsel in den Dateien DebugHelper.{sign,impl} und schon könnt ihr auf einfachste Weise Debug ausgaben machen. Schreibt dafür einfach die Debugausgabe auf die rechte Seite des "Debugoperators" ||.

Beispiel:

IMPLEMENTATION Beispiel

IMPORT 	Nat 		COMPLETELY
	NatConv		COMPLETELY
	Denotation	COMPLETELY
	DebugHelper	COMPLETELY

FUN foo: nat ** nat -> nat
DEF foo(x, y) ==
  LET
    res == (x + y) 	|| "in x=" ++ `(x) ++ " y=" ++ `(y)
  IN
    res 		|| "out =" ++ `(res)

Output:

Beispiel.impl>e foo(2,3)
starting evaluator process
in x=2 y=3                   <<----- Debug
out =5                       <<----- Infos
5
Beispiel.impl> 

Ähnliche Funktionalität bietet auch die DEBUG Struktur aus der Bibliotheca Opalica, allerdings sind dort die Funktionen IMHO nicht ganz so schön einzubauen.

Syntax Highlighting

Anleitungen und Vorgaben zum Opal Syntax-Highlighting finden sich auf einer extra Seite.

Opal-Quelltexte mit TeX/LaTeX setzen

TeX/LaTeX ist ein sehr mächtiges Textsatzsystem, das für kleinere Dokumente über Diplomarbeiten bis hin zu kompletten Büchern verwendet werden kann.

Da liegt es nahe, einfach auch Hausaufgaben optisch ansprechend in hoher Qualität damit setzen zu lassen.

Für das Setzen von Quellcodes gibt es u.a. das Listings-Package für TeX/LaTeX. Hier gibt es jedoch kein vordefiniertes Syntax-Highlighting für OPAL. Außerdem steht die opal2x.sty [1], [2] zur Verfügung.

Opal für Windows

Kurz: Es gibt kein Opal für Windows.

Lang: Jedenfalls in keiner benutzbaren Fassung. Im Rahmen einer Dissertation entsteht zwar Opal.NET (also Opal für die MS .NET Plattform), diese Version wird jedoch vorraussichtlich nicht als "stable release" erscheinen.

Opal unter Cygwin

Opal unter Cygwin wird nicht von der PSS Gruppe unterstützt und funktioniert erfahrungsgemäß nicht.

Erfahrungsbericht von Tobias Deichmann in der Info1 Newsgroup:

...also ich hab mich da letztes Jahr knapp 4 Wochen lang dran probiert und kann dir nur einen Tipp geben: Lass es.
Hab es zwar soweit geschafft, dass wenigstens einige Kommandozeilenbasierende OPAL-Programme wenigstens halbwegs fehlerfrei liefen (ok, sie stürzten trotzdem einigermaßen oft ab oder wir man das auch immer bezeichnen möchte), aber das richtige Problem kommt erst, wenn du das Ganze dann noch mit der grafischen Java Benutzeroperfläche (kurz GUI) von OPAL verwenden willst. Da funktioniert dann gar keins der Programme, es zerschießt dir dabei regelmäig die komplette cygwin bzw. OPAL Installation und es ist einfach nervig. Mein Rat an dich ist also, es lieber mit Opalix oder sonstwas zu probieren, als dich durch cygwin zu quälen, denn das führt eigentlich zu nichts...

Als Alternative bieten sich Opalix und Opal für Linux an.

Opal via SSH

Eine weitere Möglichkeit Opal von Zuhause aus zu benutzen ist sich über SSH auf einen der Uni-Server zu verbinden und so "remote" zu arbeiten. Welche Server zur Verfügung stehen und wie SSH benutzt wird steht auf der SSH-Seite.

Tabellen und Listen

Anmerkung: Die folgenden Tabellen werden noch erweitert.

Wichtige Strukturen

Name Inhalt Auswahl von Funktionen
Nat Stellt Funktionen und Sorten der natürlichen Zahlen bereit +,-,*,div,mod
NatConv Stellt Funktionen zur Umwandlung von natürlichen Zahlen in andere Typen bereit asChar, asInt, asReal, `
Real Stellt Funktionen und Sorten der reellen Zahlen bereit +,-,*,/
RealConv Stellt Funktionen zur Umwandlung von reellen Zahlen in andere Typen bereit asNat, asInt, `, ``
Int Stellt Funktionen und Sorten der ganzen Zahlen bereit +,-,*,div,mod
IntConv Stellt Funktionen zur Umwandlung von reellen Zahlen in andere Typen bereit asNat, asReal, `
Bool Stellt Funtionen und Sorten für boolsche Ausdrücke zur Verfügung /\, \/, =>
BoolConv Stellt eine Funktion zur Umwandlung von Wahrheitswerten (true/false) in Text zu Verfügung nur `
Char Stellt Funktionen für Buchstaben und Zeichen/Eingaben zur Verfügung pred, newline, lower?, whitespace?
CharConv Stellt Funktionen zur Umwandlung von Buchstaben/Zeichen zur Verfügung `, asNat, asDigitNat
Denotation Stellt Funktionen für Textverarbeitung zur Verfügung #, slice, empty?
String Stellt Funktion für dynamische Textverarbeitung bereit #, empty?, ++, %
StringConv Stellt Funktionen zur Umwandlung von Zeichenketten bereit asSeq, asString, `
Seq Stellt Grundfunktionen für die Arbeit mit Listen zur Verfügung <>, ::, exist?, %
SeqConv Stellt eine Grundfunktion zur Umwandlung von Listen bereit nur `
SeqFilter Stellt eine Filterfunktion auf Listen zzgl. Hilfsfunktionen zur Verfügung filter, partition, split
SeqMap Enthält eine Funktion zur Umwandlung von Listen nur map oder *
SeqReduce Stellt Funktionen zur Reduktion von Listen bereit reduce oder /, \
SeqZip Stellt Funktionen zum Zusammenfügen und Zertrennen von Listen zur Verfügung zip, unzip
Option Stellt einen Zugriffstyp und Zugriffsfunktionen für Datentypen zur Verfügung cont, <, =
Compose Stellt Funktionen zur Komposition von Funktionen bereit o, ;
Tree Stellt Grundfunktionen zur Arbeit mit Bäumen zur Verfügung lrotate, %, #
TreeConv Stellt Funktionen zur visuell ansprechenden Ausgabe von Bäumen bereit asSeqIn, `, graphic
Com Stellt Grundfunktionen für die Behandlung von Monaden bereit succeed, yield, break
ComCompose Funktionen zur Komposition von Monaden &, ;
BasicIO Stellt Grundfunktionen für Ein- und Ausgabe bereit ask, writeLine, write
File Stellt Funktionen für den Dateizugriff zu Verfügung open, reopen, close
Void Stellt einen leeren Datentyp bereit (wichtig für Monaden) nur void (Datentyp)

Außerdem stellt OPAL die Struktur Basics zur Verfügung, diese lädt automatisch folgende Strukturen: ABORT, BOOL, Bool, BoolConv, Char, CharConv, DENOTATION, Denotation, Int, IntConv, Nat, NatConv, Real, RealConv.

Hinweis: Einige Strukturen importieren bereits andere, die Einzelheiten finden sich in der Referenz.

Interessante Strukturen

Anmerkung: Diese Strukturen werden im Studium nicht zwingend behandelt, enthalten aber interessante Zusatzinformationen und erleichtern die Arbeit, wenn man über die Studieninhalte hinaus mit OPAL experimentieren möchte. Einige Strukturen sind auch hilfreich bei der sinnvollen Strukturierung von eigenen Strukturen (auch bei Hausaufgaben).

Name Inhalt Auswahl von Funktionen
ABORT Lässt eigene Fehlerbehandlungstexte zu nur ABORT
Set Struktur die Funktionen und Typen auf Mengen enthält {}, incl, forall?

Wichtige Funktionen

Anmerkung: Funktionen mit demselben Namen verhalten sich in verschiedenen Strukturen ähnlich (Prinzip der Orthogonalität). Einige Funktionen heißen aber auch gleich und machen völlig verschiedene Dinge. Für viele Funktionen gibt es mehrere Namen.

Name weitere Namen Beschreibung enthalten in
div / Divisions-Operator (für ganze Zahlen) Nat, Int
mod % Rest der ganzzahligen Division Nat, Int
/ keine Divisionsoperator Real
pow ^ Potenzfunktion Nat, Int, Real
++ keine Konkatenation von Werten Denotation, String, Seq
even? keine Gibt an, ob eine Zahle gerade ist Nat, Int
odd? keine Gibt an, ob eine Zahl ungerade ist Nat, Int

Wichtige oasys-Befehle

Mehr zu dieser Tabelle: http://projects.uebb.tu-berlin.de/opal/trac/raw-attachment/wiki/Documentation/OasysManual.pdf

Befehl LF Bedeutung Beispiel
a append Füge eine Struktur hinzu a MeineStruktur
f focus Öffne einen Teil der Struktur (Signatur oder Implementierung) f MeineStruktur.impl
e evaluate Werte einen Ausdruck aus e meineFunktion(12,"33"!)
ex evaluate
execute
Werte einen Ausdruck aus, führe die resultierenden Kommandos aus ex meineMonadenFunktion
x execute Führe das Kommando (die Funktion) aus x meineMonadenFunktion
r reload Erzwingt das Neuladen von veränderten Strukturen r ohne Argument
l, ll list Liste alle bekannten Strukturen auf (l: Kurzversion, ll: Langversion) l, oder ll ohne Argument