Eine kurze Einführung in

BETA

von Michael Neumann

letzte Änderung: 3.1.1999

download Offline-Version


Was ist BETA und wo kommt die Sprache her?

Wo bekomme ich BETA her?

Das Konzept des Patterns

Virtuelle Patterns

Standard-Typen

Generelles

Der Zuweisungsoperator

Blöcke

Kontrollkonstrukte

Rekursion


Was ist BETA und wo kommt die Sprache her?

BETA ist eine moderne objekt-orientierte Programmiersprache. Alle Konzepte wie z.B. Prozeduren, Funktionen, Klassen, Konstanten, virtuelle Funktionen, Co-Routinen, Typen etc. sind in einem einzigen Konzept vereint, dem Pattern. Im Gesamten ist die Sprache und deren Aufbau sehr gut durchdacht worden, was aufgrund der Herkunft auch nicht anders zu erwarten ist, denn BETA wurde am selben Institut an dem in den 60'er Jahren Simula, die erste objekt-orientierte Programmiersprache, entworfen wurde, an der "Scandinavian school of object-orientation" entwickelt.

 

Wo bekomme ich BETA her?

BETA ist bei Mjolner Informatics erhältlich. Für den nicht-kommerziellen Gebrauch ist diese Version kostenlos.

 

Das Konzept des Patterns

Wie oben schon genannt kann ein Pattern z.B. eine Prozedure, Funktion, Konstante, Klasse, Makro, lokaler Block, virtuelle Funktion, Typ, Co-Routine und vieles mehr sein. Dies alles wird durch folgendes Muster beschrieben:

	<names>: <prefix>	(#		<declarations>
				enter		<input-list>
				do		<imperatives>
				exit		<output-list>
				#)

Alles was zwischen (# und #) steht, wird Objekt-Deskriptor oder einfach Deskriptor genannt. Wenn wir z.B. eine Funktion "add" deklarieren wollen, der zwei Werte übergeben werden und die einen Wert zurückgibt, dann schreiben wird folgendes:

	add:	(#	a,b : @integer
		enter	(a,b)
		exit	a+b
		#);

Wie man sieht werden zuerst zwei Integer deklariert, im Stil von Pascal. Das @ bedeutet, daß "a" und "b" statische Variablen vom Typ "integer" sind. Die folgende "enter"-Anweisung legt die Paramteranzahl und deren Typen fest. Es müssen also zwei Parameter vom Typ Integer dem Pattern übergeben werden, die in "a" und "b" gespeichert werden. Der "do"-Teil fehlt ganz, d.h. es wird einfach nichts bei ihrem Aufruf ausgeführt. Die "exit"-Anweisung gibt an welche Werte an den Aufrufer zurückgegeben werden. Anders als bei "herkömmlichen" Programmiersprachen (Pascal, C++,...) können mehrere Werte zurückgegeben werden. Dies kann einfach wie bei der "enter"-Anweisung durch (a,b,c,....) bewerkstelligt werden. Wie diese Funktion aufgerufen wird werde ich später erklären. Um eine Prozedure zu deklarieren läßt man einfach die "exit"-Anweisung weg, da eine Prozedure keine Werte zurückliefert und fügt einen "do"-Teil ein, es sei denn die Prozedure soll nichts tun, dann läßt man diesen Teil weg (wenig sinnvoll :-). Bei einer parameterlosen Prozedure wird einfach der "enter"-Teil weggelassen. Eine Konstante ist eine parameterlose Funktion, die nur einen "exit"-Teil besitzt:

	PI:	(#
		exit	3,1415926535897932384626433832795
		#);

In einem Pattern kann man nun wiederrum sogenannte Subpatterns deklarieren, indem man in dem Deklarationsabschnitt (<declaration>) Prozeduren oder Funktionen deklariert. In BETA können Sie sogar, wie in keiner anderen mir bekannten Sprache außer Simula, in einer Klasse eine verschachtelte Klasse deklarieren. Diese Schachtelung kann mit allen Patternformen erfolgen, wobei die weiter innen definierten Pattern auf die Äußeren zugreifen können, andersrum jedoch nicht. C/C++ Programmierer werden dies z.B. von Variablendeklarationen innerhalb von Blöcken her kennen.
Ein Pattern erbt von einem anderen, wenn dies vor dem Deskriptor genannt wird, an der Stelle von <prefix>. Ich habe bewußt gesagt, daß Pattern und nicht Klassen erben können, d.h. alle Formen von Pattern können erben. Dies sieht jedoch bei Prozeduren oder allgemeiner gesagt bei Pattern mit einer "do"-Anweisung etwas anders aus als die normale Vererbung. Ein Beispiel:

	X:  	(#
		do 'begin' -> puttext; inner; 'end' -> puttext;
		#);
	Y:    X	(#
		do 'Hello World' -> puttext;
		#);

'begin'-> puttext gibt auf dem Bildschirm "begin" aus, ebenso die anderen Anweisungen, die dementsprechend "end" und "Hello World" ausgeben. Die "inner"-Anweisung wird bei einer Vererbung durch den gesamten "do"-Abschnitt des Erben ersetzt. Somit wäre eine Alternative für Y:

	Y:	(#
		do 'begin' -> puttext; 'Hello World' -> puttext; 'end'-> puttext;
		#);

Wenn X weitere Pattern, wie z.B. Variablen, Prozeduren oder Klassen beinhalten würde, dann wären diese an Y vererbt worden. Der Unterschied zu Sprachen wie z.B. C++ besteht in dem "inner"-Konstrukt (gibt es in C++ nämlich nicht), nicht aber in der "normalen" Vererbung.

 

Virtuelle Patterns

In anderen Sprachen gibt es nur virtuelle Funktionen, in BETA jedoch, gibt es virtuelle Klassen, und Funktionen. Ein virtuelles Pattern wird mit :< anstelle von : definiert. Wenn ein virtuelles Pattern erweitert werden soll, so muß man ::< benutzen, und :: um es endgültig abzuschließen.

 

Standard-Typen

In BETA gibt es vier fest eingebaute Typen, integer, real, char und boolean. Diese werden nicht als Instanzen von Klassen (wie etwa in SmallTalk) gesehen, sondern wie in C++ oder Pascal, der Laufzeit zuliebe, direkt als Speicherstellen (z.B. integer = 4 Bytes) abgelegt. Diese vier Typen können nicht als Referenzen (Zeiger) erzeugt werden, sondern nur als statische Variablen (@). Um auch von diesen Typen Referenzen (angedeutet durch das Symbol ^ statt @) zu deklarieren, sind in BETA vier Cover-Klassen definiert: IntegerObject, RealObject, CharObject und BooleanObject. Die Literale dieser Typen sind ähnlich Pascal:

	(#
		i,j:	@integer;
		r: 	@real;
		ch:	@char;
		b:	@boolean;
	
	do
		1234 -> i; i -> j;	(* 1234 wird i zugewiesen, i wird j zugewiesen *)
		123.6 -> r;
		'g' -> c;
		true -> b; false -> b;
	#);

Der Unterschied zu Pascal ist hier wohl der Zuweisungsoperator ->, der die linke Seite der Rechten zuweist. (* und *) begrenzen ein Kommentar, welches auch über mehrere Zeilen gehen kann. Integer können auch in einer bestimmten Basis definiert werden:

	<basis>x<digits>
	2x1110011	(* Binär *)
	16xFFFF		(* Hexadezimal *)
	8x76543		(* Oktal *)
	0xF334E		(* Hexadezimal *)
	usw.

 

Generelles

 

Der Zuweisungsoperator

Anders als z.B.in Pascal oder C++ wird von links nach rechts zugewiesen. Dies mag am Anfang einem vielleicht etwas seltsam vorkommen, aber nach längerem Gebrauch hilft es beim Lesen des Quellcodes ungemein. Ein Beispiel:

	C++:	a = b = c;
	BETA:	c -> b -> a;

In C++ muß man rechts beginnen zu Lesen, da der Zuweisungsoperator in C++ rechtsassoziativ ist. So wird zuerst "b" der Wert von "c" zugewiesen, dann wird "a" der Wert von "b" zugewiesen. In BETA ist die Syntax intuitiv, man kann von links nach rechts lesen und der Pfeil deutet schon an, wem was zugewiesen wird.

Der Zuweisungsoperator hat noch eine ganz andere Anwendung, und zwar wird er benutzt um Pattern mit einer "enter"-Liste etwas zuzuweisen oder von einem Pattern mit "exit"-Liste etwas entgegenzunehmen. Dabei wird immer der "do"-Teil aufgerufen (wenn vorhanden). Ein Pattern mit "enter"-Liste kann auf der rechten Seite stehen, eines mit "exit"-Liste kann auf der linken Seite stehen. Pattern mit Ein- und Ausgabelisten ("enter" und "exit") können auf beiden Seiten stehen, müssen aber nicht.

	add:	(#	a,b : @integer
		enter	(a,b)
		do 	'in add,' -> puttext;
		exit	a+b
		#);
	
	i,j: 	@integer;
	
	do 	(3,4) -> add -> i;	(* in i befindet sich nun 7 *)
		add -> j;		(* in j befindet sich auch 7 *)
		(7,4) -> add;		(* add.a = 7   add.b = 4 *)
		add -> i;		(* i ist nun 11 *)
		add;

Die Paramterwerte bleiben auch nach dem Aufruf erhalten. Oder Sie können die Funktion, auch ohne zuvor Parameter übergeben zu haben, aufrufen. Bei jedem Aufruf jedoch wird der "do"-Teil ausgeführt, folgendes wäre die Bildschirmausgabe:

	in add,in add,in add,in add,in add,

 

Blöcke

Blöcke kennen Sie vielleicht aus Pascal, wo man sie z.B. bei "if"-Anweisungen benötigt, wenn man mehrere Anweisungen zu einer einzigen Anweisung zusammenfassen und ausführen will. Ein Block in BETA ist ein Pattern und wird als eine einzige Anweisung betrachtet. Im "do"-Abschnitt des Patterns werden alle Anweisungen geschrieben die ausgeführt werden sollen.Da ein Block in BETA ein Pattern ist, kann man lokale Pattern (lokale Variablen, Klassen usw...) definieren, die nur innerhalb des Patterns sichtbar sind. Die Definition von Patterns im <declarations> -Abschnitt kennen Sie schon. Es können aber auch Blöcke direkt im "do"-Abschnitt deklariert werden und einen Namen besitzen, der eine besondere Bedeutung hat, die ich bei den Schleifen erläutern werde. Der Name ist nur innerhalb des Blockes sichtbar. Ein Beispiel:

	Ein geschachtelter Block:
	(#
	do 	(#
		do 'Hello World' -> putline
		#)
	#)
	Ein geschachtelter Block mit Name:
	(#
	do 	blockname: (#
		do 'Hello World' -> putline
		#)
	#)
	Ein geschachtelter Block mit Name und lokaler Variable:
	(#
	do 	blockname: (# i: @integer;
		do 'Hello World' -> putline; 1 -> i -> putint
		#)
	#)

 

Kontrollkonstrukte

Wir werden nun sehen, wie in BETA Abfragen und Schleifen programmiert werden. Es gibt zwei "if"-Konstrukte:

	1.)
	(if	<expr>
	then	<imperatives1>
	else	<imperatives2>
	if)
	2.)
	(if 	<expr>
	//	<expr1> then <imperatives1>
	//	<expr2> then <imperatives2>
	...
	...
	else	<imperatives3>
	if)

Das erste ist vergleichbar mit dem in Pascal oder C++, d.h. wenn <expr> wahr ist, dann wird <imperatives1> ausgeführt, ansonsten <imperatives2>. Das zweite Konstrukt funktioniert anders: Wenn <expr> gleich <expr1> dann wird <imperatives1> ausgeführt, wenn <expr> gleich <expr2> dann wird <imperatives2> ausgeführt und so weiter. "Else" muß natürlich nicht angegeben werden. Nun zu den Schleifen:

	1.)
	(for	<evaluation>
	repeat	<imperatives>
	for)
	2.)
	(for	<var>: <evaluation>
 	repeat	<imperatives>
	for)

Beide Konstrukte unterscheiden sich nur dadurch, daß 2.) vor <evaluation> eine Variable definiert. Die Anweisungen nach "repeat" werden so oft wie in <evaluation> angegeben ausgeführt. Bei 2.) wird die Variable <var> von anfangs 1 auf <evaluation> hochgezählt. Die Variable ist lokal innerhalb der "for"-Anweisung.
Schleifen können auch durch "leave" und "restart" programmiert werden. Dazu muß eine Anweisung oder ein Block mit einem Namen versehen werden (siehe Abschnitt "Blöcke"):

	(#
		i: @integer;
	do
		1 -> i;
		lp: (if i<10 then i -> putint; i+1 -> i; restart lp; if);
	#)

Diese Programm gibt die Zahlen eins bis neun aus. Die "if"-Anweisung wird mit "lp"benannt. Der Name ist nur innerhalb der "if"-Anweisung vorhanden. "restart" springt an den Anfang der Anweisung bzw. führt die Anweisung/Block neu aus. "leave" bewirkt das Gegenteil und verläßt die Anweisung/Block Bei dem Beispiel wird die "if"-Anweisung solange wiederholt, bis i>=10, wobei bei jeder Ausführung "i" ausgegeben und um eins erhöht wird.

 

Rekursion

Hierbei ist eine Besonderheit der Pattern zu beachten. Pattern, egal welcher Form, sind eigentlich nichts anderes wie Klassen in z.B. C++ oder Pascal, d.h. bevor sie benutzt werden können, muß eine Instanz gebildet werden (eine Abbildung im Speicher). Lokale Varibalen oder lokale Pattern in BETA werden also nur einmal erzeugt und sind bis ans Lebensende des umschließenden Pattern-Objektes verfügbar. In anderen Sprachen (C++, Pascal) werden lokale Variablen von Prozeduren dagegen bei jedem Aufruf neu erzeugt, und beim Verlassen der Prozedure wieder freigegeben. In BETA weisen Sie dem Pattern-Objekt vor jedem Aufruf Werte zu. Diese Werte sind auch noch nach dem Aufruf verhanden, da sie erst zerstört werden, wenn das ganze Pattern-Objekt freigebenen wird (automatisch durch Garbage Collector). Wenn Sie also rekursiv programmieren möchten, müßen Sie ein neues Pattern-Objekt erzeugen bevor Sie dieses aufrufen:

	(#
		fakultaet:	(#
					n: @integer;
				enter	n
				do	(if	n <= 1
					then	1 -> n
					else	n*((n-1) -> &fakultaet) -> n
					if);
				exit	n
				#);
	
	do	6 -> fakultaet -> putint (* ergibt 720 *)
	#)

Das neue daran ist der &-Operator. Er erzeugt ein neues Objekt vom Typ "fakultaet" und ist vergleichbar mit "new" in Pascal und C++. Das neu erzeugte Objekt wird dann mit "n-1" aufgerufen welches die Fakultät von "n-1" liefert. Noch ein Beispiel:

	(#
		(*Fibonacci-Folge*)
		fib:	(#
				n: @integer;
			enter	n
			do	(if	n > 1
				then	(n-1 -> &fib) + (n-2 -> &fib) -> n
				if);
			exit	n
			#);
		
		do	8 -> fib -> putint (* ergibt 13 *)
	#)

 

Mehr folgt später....


eMail: neumann@s-direktnet.de

letzte Änderung: 3.1.1999 von Michael Neumann

http://www.s-direktnet.de/homepages/neumann/beta/beta.htm