Az Opal programozási nyelv

Példaprogramok

Hello world!

Legyen az első a népszerű "Hello World", a második egy kicsit bonyolultabb, némi interaktivitást biztosító I/O-val. Ezen második példa kerettörténete a nyulak szaporodásának bemutatása, azaz a Fibbonacci-számok előállítása.

A programozás világában a "Hello World" szöveg megjelenítése a terminálon abszolút imperatív. Az OPAL nyelven ezt a következő képen kell megvalósítani.

Ebben a példában tehát a program egy HelloWorld nevű struktúrát tartalmaz, amely fizikailag két állományban található (HelloWorld.sign és HelloWorld.impl).

HelloWorld.sign
SIGNATURE HelloWorld
IMPORT Void ONLY void
Com[void] ONLY com
FUN hello : com[void] -- top level command
HelloWorld.impl
IMPLEMENTATION HelloWorld
IMPORT DENOTATION ONLY denotation
Char ONLY char newline
Denotation ONLY ++ %
Stream ONLY output stdOut
 write : output ** denotation -> com[void]

-- FUN hello:com[void] (already declared in Signature-Part)
DEF hello ==
 write(stdOut, "Hello World" ++ (%(newline)) )

Az ú.n. 'signature' (ennek rövidítése a "sign") rész deklarálja az export interfészét a struktúrának. Program esetében tartalmaznia kell a com[void]-ot amelyhez a com-ot és a void-ot a nekik megfelelő Com és Void struktúrákból kell includolni.

Void ONLY void

A Void struktúrából csak a void FREETYPE típusú adattagját szeretnénk használni (nincs is más benne...).

Com[void] ONLY com

A Com[void] egy olyan struktúra, amelyik a Com sablonból készült, a void típussal (kis "v"!!!). DE itt a típus nem a struktúra nevét mondja meg, mivel abban több adattag is lehet, hanem a konkrét adattag nevét, amiből a mintaillesztés lesz. Az így kapott struktúrából pedig csak a com FREETYPE típusú adattagot szeretnénk használni.

FUN hello : com[void]

Ez a sor deklarálja a HelloWorld struktúrához tarozó nyilvános függvényeket. Itt azért kell megadni a com után megint a típust, mert lehet olyan is, hogy egy struktúrát több különböző típussal származtattunk.

A program fordításánál a program szövegéből még az sem derül ki, hogy melyik struktúra melyik függvénye lesz a főprogram. Ezért ezeket mind nekünk kell megadni a fordítónak. A programhoz adott Report és Tutorial azt írja, hogy a következő paranccsal lehet lefordítani:

ocs -top HelloWorld hello

Ez nekem így sosem működött. A példaprogramból azonban kitűnt, hogy van egy plusz file a source kódok között. Ezt SysDefs-nek hívják. Az ilyen bonyolultságú programoknál ez a következő két sort tartalmazza:

TOPSTRUCT=HelloWorld
TOPFUN=hello

Az első sor a fordítandó struktúra neve, a második sor pedig a főfüggvény neve (mint C/C++ -ban a main).Miután megírtuk ezt a filét, utána adjuk ki az előbbi parancsot, és a fordító (ha nincs hiba a kódban), a főfüggvény nevével megegyező programot készít. (Ez a HelloWorld esetében hello lesz.)

Az implementációs (ennek rövidítése "impl") részhez kellenek még további dolgok (denotation, output, char) és műveletek (stdOut, write, %, ++ és newline) amelyeket a megfelelő struktúrából kell inkludólni.

A nyolcadik sor kommentár amelyet a "--" jel vezet be.

A definíciója a hello konstansnak, amely a signature részben van deklarálva, és úgy van definiálva, hogy a szöveget kiírja az stdOut-ra (ami egy előre definiált konstans és a terminálnak felel meg).

A szöveg a "Hello World"-öt és a hozzá kapcsolt 'newline' karaktert, amelyet a % operator konvertál, és a ++ operátor össze a szöveggel, tartalmazza.

Rabbits

A második feladat tulajdonképpen a rabbits nevű függvény előállítása, amely úgy van definiálva, hogy:

 / 1 , ha gen = 0
rabbits(gen) = - 1 , ha gen = 1
 \ rabbits(gen-1) + rabbits(gen-2) , ha gen > 1
Rabbits.sign
SIGNATURE Rabbits

IMPORT Void ONLY void
Com[void] ONLY com

FUN main : com[void] -- top level command
Rabbits.impl
IMPLEMENTATION Rabbits

IMPORT Denotation ONLY ++
Nat ONLY nat ! 0 1 2 - + > =
NatConv ONLY `
String ONLY string
StringConv ONLY `
Com ONLY ans:SORT
ComCompose COMPLETELY
Stream ONLY input stdIn readLine
output stdOut writeLine
write:output**denotation->com[void]

-- FUN main : com[void] -- already declared in signature part
DEF main ==
write(stdOut,
"For which generation do you want to know the number of rabbits? ") &
(readLine(stdIn) & (\\ in.
processInput(in`)
))

FUN processInput: denotation -> com[void]
DEF processInput(ans) ==
LET generation == !(ans)
bunnys == rabbits(generation)
result == "In the "
++ (generation`)
++ ". generation there are "
++ (bunnys`)
++ " couples of rabbits."
IN writeLine(stdOut, result)
-- ----------------------------------------------------------

FUN rabbits : nat -? nat
DEF rabbits(generation) ==
IF generation = 0 THEN 1
IF generation = 1 THEN 1
IF generation > 1 THEN rabbits(generation - 1)
+ rabbits(generation - 2) FI

A példában a matematikai formula egy az egyben megtalálható rabbits funkció definíciójában a "rabbits.impl" 35-39 sorában.

Ez direkt transzformáció jellemző a funkcionális programozásban. Ellentétben a tradicionális deklaratív nyelvekkel, nem szükséges tudni a változókról és az azok aktuális értékeiről, illetve arról, hogy érték szerinti vagy referencia szerinti paraméterátadás történik. Ez valódi problémáknál is hasonlóan használható, nem csak az ilyen triviális példánál, mint a Fibbonacci-számok előállítása.

Ez a példa jól illusztrálja a lokális deklaráció lehetőségét. A 24-30 sorok 3 lokális deklarációt tartalmaznak:

  • generation : ez az a szám amit a felhasználó gépel be
  • bunnies : a számolt értéke a Fibbonacci-függvénynek
  • result : a program által adott válasz szövegét tartalmazza

Ezt a logikai struktúrát követve a fő akció igen egyszerűvé válik:

write(stdOut, result)

Egészek tárolása

MinMax.sign
SIGNATURE MinMax
IMPORT Void ONLY void
Com[void] ONLY com

FUN main : com[void]
MinMax.impl
IMPLEMENTATION MinMax
IMPORT Denotation ONLY ++ %
Int ONLY int min max
IntConv ONLY `
Char ONLY char newline
Stream ONLY stdOut output
 write : output ** denotation -> com[void]

DEF main ==
 write(stdOut, "Min : " ++ `(min) ++ " Max : " ++ `(max) ++ (%(newline)) )
SysDefs
TOPSTRUCT=MinMax
TOPFUN=main

Ezzel a kis programmal, csak azt szerettem volna megnézni, hogy hány biten tárolja az egész számokat. Meglepetésként: -1073741823 és 1073741822 eredményeket kaptam. Mivel N biten 2N-féle számot lehet tárolni ezért 1073741823 (negatívok) + 1073741822 (pozitívok) + 1 (Nulla) = 2147483646 = 231 . Azaz csak 31 biten ábrázolja az egészeket… Nem tudom miért.

Itt jól megfigyelhető, hogy minden függvényt, és adattípust importálni kell. A Denotation típusból használni szeretnénk a konkatenációt (++) és a Char –> Denotation konvertáló függvényt. A szám osztályokra jellemző, hogy van hozzájuk egy konvertáló osztály (IntConv), ami csak a különböző konverziós függvényeket tartalmazza, és adattagja sincs. (Ezért leginkább a függvényosztályokhoz hasonlítanak.)