[nem-pl] projekty

Lukasz Kaiser kaiser at tenet.pl
Tue Oct 7 16:41:24 CEST 2003


Oczywiscie pomylilem zalacznik.

- lk

-----------------------------------
-------------- next part --------------
Proponowane projekty (dokladniejszy opis)

Chcemy zrobic formalna semantyke Nemerle w stylu funkcjonalnym zeby:
 1) moc robic automatyczne testy,
 2) jak ktos chce mogl sie bawic w dowody formalne.
Dyskusje o tym, czy ludzie robiacy formalne dowody sa szaleni ani jak robic dowody 
automatycznie _nie_ zaliczaja sie do tych projektow. Zeby robic automatyczne testy
to trzeba miec pewne zalozenia - asercje wzgledem ktorych testujemy. Jezykiem do ich
wpisywania zajmie sie Grzesiek. Glownym problemem w tych projektach jest zakodowanie
imperatywnych konstrukcji Nemerle na prosty jezyk funkcjonalny (przepisywanie termow).
Trzeba to polaczyc z IDE i zrobic tak, zeby uzytkownik chcacy zrbic testy czy nawet ktos
chcacy robic dowod formalny nie musial sie uczyc tego kodowania.

1. Przepisywanie Termow

Trzeba sie nauczyc co to sa termy i jak to jest, ze reguly przepisywania termow to wlasciwie
programy funkcjonalne, np.
- append( nil, x ) -> list( x, nil )
- append( list( x, y ), z ) -> list( x, append( y, z ) )
definiuja funkcje dodajaca el. do listy.
Nie bede sie nad tym rozpisywal, mam nadzieje ze ToMasz to ladnie wytlumaczy i poda lepsze przyklady.

Trzeba jeszcze dodac kilka szczegolow technicznych. Termy beda przepisywane gorliwie, tzn. najpierw 
bedzie przepisywany ten redex w termie pod ktorym nie ma juz innych redexow. 
Jesli jest kilka takich redexow to moga byc  przepisywane rownolegle, np. w osobnych watkach.
Do tego jesli po lewej i prawej stronie wystepuje ta sama zmienna to nalezy kopiowac wskaznik a nie
cala zmienna jako term. Przy wykonywaniu rownoleglym trzeba sie przygotowac na skopiowanie kodu
programu ze wzgledu na sposob kodowania (nizej) i dlatego przy tlumaczeniu z jezyka wyzszego
poziomu powinna byc opcja - czy tlumaczyc na przepisanie po kolei czy rownolegle z kopiowaniem kodu.

2. Zakodowane Przepisywanie Termow

Jest jedna drobna wada prostego przepisywania termow - osobno sa reguly i osobno funkcje, wiec nie
jest zdefiniowane np. jak to zrobic zeby funkcja dodala nowe reguly przepisywania. 
Zeby moc robic takie rzeczy zakodujemy reguly przepisywania razem z termem ktory bedzie przepisywany
i bedziemy je podawac jako argument kazdej funkcji, tzn. zamiast
- append ( x, y ) 
bedzie
- append ( x, y, reguly przepisywania )
czyli zamiast
- append( list( x, y ), z ) -> list( x, append( y, z ) )
mamy
- append( list( x, y ), z, r ) -> list( x, append( y, z, r ) ).
Przy takim kodowaniu zarowno reguly przepisywania czyli kod programu jak i dane do przepisania
sa w jednym termie, ktory jest przepisywany gorliwie tak dlugo jak sie da.

Jest jeszcze jeden problem gdy dodajemy nowe funkcje maja one zwykle nowe nazwy, ktore sa etykietami
w termie i naleza do jakiegos ustalonego wczesniej zbioru mozliwych etykiet. Poniewaz my chcemy moc
dodawac nowe etykiety, to bedziemy kodowac tak, ze zamiast
- append( x, y, r )
bedzie
- fun( "append", x, y, r ), przy czym "append" to string zakodowany jako term.
Podobnie zrobimy z typami, tzn. zamiast
- list( 1, nil )
bedzie
- tycons( "list", tycons( "1" ), tycons( "nil" ) ).

Wazne jest, zeby to kodowanie bylo w miare niewidoczne, tzn. w skladni jezyka do wpisywania takich termow \
nie bedzie tego "tycons" ani "fun" ani tych dodatkowych zmiennych, bedzie jednak specjalna zmienna "system" 
pozwalajaca operowac na tej dodatkowej zmiennej i specjalne zmienne ktorych bedzie mozna uzywac w glowie 
termu, co bedzie tlumaczone do tycons( x, ... ).

Formalnie proponuje taka sygnature dla termow:
stale:  0, 1, nil, list, char
funkcyjne: fun o arnosci 4 oraz 10 symboli tyconsN dla N=0,1,2,...,8,32 do reprezentowania typow
o arnosci N+1 (wszystko mozna zakodowac binarnie, ale bardziej plaskie lepiej widac i mozna
cos wtedy zrozumiec i sie nie mylic co chwile).

3. Typy

Zwykle statyczne polimorficzne typowanie kazdy zna z OCamla czy MLa.
Do tego dochodza dwie rzeczy: interface'y i funkcje dynamiczne.
Nie planujemy zadnej rekonstrukcji typow na tym poziomie, dlatego musza byc one podawane
przy zmiennych i przy funkcjach.

Interface to prosta rzecz, po prostu lista funkcji. Potem mozna napisac, ze zbior zmiennych typowych
spelnia interface. Tak samo jak w calym Nemerle, mam nadzieje ze Michal to wytlumaczy.

Funkcje dynamiczne przy statycznym typowaniu moga sie wydawac problematyczne, ale wlasnie od tego jest dobre 
kodowanie. Wprowadzamy do jezyka symbol specjalny ?, ktory pozwala brac za zmienne etykiety w termach,
tzn. ?x( y ) dopasuje sie do np. list( nil ) i wtedy x = "list" a y = nil. Tlumaczenie jest jasne,
?x(y) = tycons( x, y ). Oczywiscie wyrazenie ?x(...) jest typu 'a. Jakis przyklad:

Zalozmy, ze chcemy funkcje eq : ( 'a, 'b ) -> bool ktora dla kazdych dwoch termow sprawdza,
czy sa rowne. Piszemy
- eq( 0, x ) -> if x = 0 then true else false //zapis skrotowy
- ... dla innych stalych, czyli w sumie 5 wersji
- eq( ?x( l ), ?y( m ) ) -> and2( eq( x, y ), eq( l, m ) )
- eq( ?x( l1, l2  ), ?y( m1, m2 ) ) -> and3( eq( x, y ), eq( l1, m1 ), eq( l2, m2 ) )
... dla roznych arnosci tycons, czyli 10 wersji.
Bez takiego rozszerzenia systemu typow takiej funkcji nie da sie napisac, a ona jest przydatna.

4. Skladnia

Jakas skladnie trzeba miec bo nie mozna pisac termow z palca jak tam sa takie rzeczy jak ten tycons
i do tego jeszcze reguly przepisywania razem z funkcjami.
Dlatego niech bedzie tak, ze piszemy dowolne termy, tylko z malej litery, bo z duzej sa zmienne, czyli
term := smallID "(" {term ","}+ ")" | largeID ":" type

Do tego potrzeba wpisywac reguly przepisywania, asercje, typy i interface'y, wiec podam prosta
gramatyke, ktora moze trzeba troche zmienic. {x}* znaczy 0 lub wiecej wystapien x, terminale sa
w cudzyslowiach, poza ID. Sa ID typow (tID), zmiennych (vID), funkcji (fID), konstruktorow (cID)
i interface'ow (iID).

vars := "(" {vID ","}* ")"
type := tID "(" { {vID | type} "," }* ")" | type "->" type

tydecl := "type" tID vars "=" { "|" cID "of" {type "*"}* }*
ifacedecl := "interface" iID vars "=" {fID ":" type }*
assertdecl := term "=" term
fundecl := "fun" fID ":" type {", where" vars "implements" iID }* "=" { term "->" term "," }*

expr := tydecl | ifacedecl | assertdecl | fundecl

Do tego potrzebne jest jeszcze naturalne kodowanie liczb, znakow, stringow, krotek i list, oraz termow bez 
tycons, tylko z dodatkowym znakiem ? o ktorym juz pisalem i dodatkowa mozliwoscia wywolania kazdej
funkcji z 3 dodatkowymi argumentami - nazwa funkcji, jej numerem i calym programem.

Jeszcze kilka uwag:
- przy uzyciu arnosci innej niz 0-8+32 powinien byc jakis warning,
- jesli chodzi o symbole stale, to zakladam, ze one nie sa tlumaczone, tzn. nil = nil a nie tycons( "nil" )
  a np. char( 'c' ) = tycons( char, 'c' ) a nie tycons( "char", 'c' ) co by prowadzilo do zakrecenia,
- mozna przeciazac nazwy jesli maja inna arnosc ale nie wystarczy inny typ, prawdziwe przeciazanie bedzie
  robione gdzies na wyzszym poziomie,
- zmienne w termach moga byc 3 rodzajow: zwykle (argumenty), wejscie (leniwie podstawiane, tylko
  jedna zmienna ale mozliwy rozny numer portu wejscia) i egzystencjalne w asercjach; proponuje tak
  to zakodowac: IN_n - wejscie z portu n, Ex_nazwa - zmienna egzystencjalna, reszta to zwykle
- przy kazdym typie moga byc jeszcze jakies atrybuty potrzebna np. przy kodowaniu pol prywatnych, zeby
  zachowywac takie informacje chociaz my z nich nie korzystamy, czyli raczej
type := tID "(" { {vID | type} "," }* ")" 
     | tID "attr" string "(" { {vID | type} "," }* ")" 
     | type "->" type.

5. Kodowanie

Kazdy umie sobie zakodowac liczby, stringi i w koncu typy i reguly przepisywania w termach, ale
zeby nie robil tego kazdy po swojemu to ja podam typy. Te typy sa podobne do napisanej powyzej
gramatyki bo wlasnie ja koduja, wiec wlasciwie sa jej przepisaniem.

Podstawowe:

type list a' = nil | list of 'a * list 'a // tzn. term list( 3, nil ) ma typ list int
type digit = 0 | 1
type int = list of digit
type sint = plus of int | minus of int 
type fp = fp of sint * sint // floating point = 0.x * 10^y
type char = char of digit^8 // nieformalnie, tzn. 8-bitowy char
type string = list of char
type desc = 0 | 1 | nil | char | list | string

Kodowanie zmiennych w termach wraz z typami:

type var = var of string * type )
type exvar = exvar of string * type
type invar = in of int // numer portu

Kodowanie typow i interface'ow:

dla czytelnosci:
type tyvar = tyvar of string
type iface = iface of string
type funname = funname of string

type type = tyvar | type of desc * list of type | funtype of type * type
type tagtype = tagtype of type * string * string // typ z atrybutem i nazwa
type tydecl = tdecl of string * list of ( string * list of tagtype )
type ifacedecl = ifacedecl of iface * list of tyvar * list of ( funname * type )


Kodowanie funkcji:

zwykle wywolania:
type data = tycons of desc * list of data
type funcall = fun of funname * data

lewa strona regul przepisywania:
type datapattern = var | invar |  tycons of  desc * list of datapattern
type funcallpattern = funpattern of funname * datapattern

prawa strona regul przepisywania:
type funpattern = var 
     | invar 
     | tycons of desc * list of funpattern 
     | fun of funname * funpattern
     | out of int * string // wypisz string na port numer int

regula przepisywania:
type rrule = rr of funcallpattern * funpattern

definicja funkcji:
type fundef = fundecl of funname * type * list of ( list of tyvar * iface ) * list of rrule

Asercje:

type funexpattern = var 
     | invar
     | exvar 
     | tycons of desc * list of funpattern 
     | fun of funname * funpattern
     | out of int * string // wypisz string na port numer int

type assert = eq of funexpattern * funexpattern

Tutaj kazda zmienna invar jest interpretowana jako zwykla zmienna a out nic nie znaczy, tzn. przepisuje
sie do pustego stringu.

Trzeba jeszcze zdefiniowac interface calego systemu, czyli zbioru funkcji, typow, interface'ow i  asercji
oraz byc moze jeszcze innych danych. Zrobimy to najpierw tak:

type system = sys of list of tydecl * list of ifacedecl * list of fundef * list of assert * list of data

Ale chcemy miec przynajmniej namiastke sensownej wydajnosci, wiec nie mozemy funkcji lub asercji trzymac
w listach. Dlatego zrobmy tak:

type array 'a = leaf of 'a | array of (array of 'a)^32

To sa tablice, ktore moga byc dosc plaskie (32^4 =~ milion). Teraz mozemy zamiast list zrobic tablice,
ale trzeba do kazdego wywolania funkcji dopisac numer tej funkcji w tablicy i dopisac to odpowiednio do 
typow napisanych wczesniej.

Jesli chodzi o typy, interface'y, asercje i dane, to mozna nie okreslac dokladnie w jaki sposob sa 
trzymane bo one sa dla innych programow, nie jest konieczne konkretne okreslenie typu w ktorym sa trzymane.
Wystarczy napisac interface i zaimplementowac jakikolwiek typ spelniajacy ten interface.
Przyklady interface'ow sa np. na stronie http://www.go-mono.org/docs/, np. IDictionary.

6. Co trzeba zrobic na poczatek.

Trzeba zrobic prosty programik w Nemerle, ktory:
- wczytuje kod programu i jeden term do wykonania zgodnie z podana w punkcie 4. gramatyka,
- zamienia ten kod na odpowiedni term S typu system dodajac np. numery funkcji w tablicy,
- z wczytanego termu i termu S tworzy jeden term zgodny z sygnatura opisana w 2. zamieniajac
  we wlasciwy sposob wszystkie ? i dodajac funkcjom nowe argumenty,
- robi zwykle gorliwe przepisywanie tego termu,
- wypisuje to co zostalo zgodnie z podana w punkcie 4. gramatyka (da sie o ile program byl poprawnie
  otypowany).

Potem trzeba napisac (juz w tym jezyku) program sprawdzajacy typy.
Do tego przydaloby sie zrobic ladna dokumentacje i ladnie to wszystko powyjasniac.

7. Testowanie i dowodzenie na poziomie termow

Na poziomie przepisywania termow wszystko co da sie udowodnic mozna udowodnic wykorzystujac tylko
dwie rzeczy:
- rozbicie przez indukcje pewnej zmiennej,
- udowodnienie innej rownosci jako lematu i jej wykorzystanie.
My mamy jeszcze zmienne egzystencjalne (zakladamy, ze konstruktywne), czyli kazda z tych zmiennych jest
jakims termem i mozna cos pod nia podstawic.
Trzeba to zaimplementowac tak, zeby te kroki byly robione na termach ale tylko gdy sa zgodne z typami
i zeby byly widoczne w jak wykonywane na jezyku termowym a nie na samych termach ze wszystkimi tycons itd.

Przy testowaniu mamy asercje rownosciowa bez zmiennych egzystencjalnych i mamy ja przetestowac, tzn.
sprobowac znalezc podstawienie pod zmienne ktore obala ta asercje. Widac, ze jest to dokladnie
odwrotny przypadek do dowodzenia. Trzeba generowac rozne losowe dane odpowiedniego typu i podstawiac.
Przy generowaniu tych danych trzeba brac pod uwage strukture asercji i funkcji w niej wystepujacych,
tzn. jesli mamy wstawic dane typu t to mozemy wypisac wszystkie mozliwe termy typu t do pewnej glebokosci
a potem pare wzorcow, sprawdzic asercje dla tych niskich termow a potem zrobic nowe asercje dla
wzorcow i sprawdzac je dalej razem z jakims occur checkiem. Mozna to tez ew. mieszac z dowodzeniem i
trzeba jeszcze dokladnie to ustalic.

Pewna idee jak to mogloby probowac dzialac mozna zobaczyc na takim trywialnym przykladzie:
asercja: x+256 < x+n
jak juz beda do tego jakies heurystyki, to mozna od razu zgadnac n = 256+m i dla x=m=1 koniec, ale mozna
rozbic po strukturze liczb (kodowanych binarnie), czyli
asercja 1: x+256 < x+0 // szybko sie dowodzi jakimis wiezami arytm., ze jest prawdziwe i dalej pomijamy
asercja 2: x+256 < x+1 // szybko sie dowodzi, ze jest prawdziwe i dalej pomijamy
asercja 3: x+256 < x+2n - rozbijamy dalej log. ilosc krokow i znajdujemy kontrprzyklad
asercja 4: x+256 < x+2n+1

Na poczatek jednak to nie musi byc zbyt zaawansowane, wazne zeby generowalo sensowne losowe dane
dowolnego typu przechodzace przez wszystkie rozgalezienia wywolania danej funkcji do zadanego
poziomu glebokosci i dawalo mozliwosc wygodnego zapisywania gdzies danych testowych od uzytkownika.


8. Podniesienie sie z abstrakcji

Trzeba zrobic jezyk abstract Nemerle z takimi samymi typami co nasz jezyk termow, ale dodatkowo z:
- wskaznikami,
- zmiennymi i wywolywaniem sekwencyjnym,
- wyjatkami.
To wszystko mozna dosc latwo zakodowac w termach, ale trzeba sprobowac na ile sie da zachowac czytelnosc
testowania i dowodzenia.
Potem trzeba pojsc jeszcze wyzej, czyli przetlumaczyc samo Nemerle na abstract Nemerle.

Oczywiscie potem pozostaje integracja z IDE i innymi projektami, dokumentacja, tutariale i testy.

- lk


More information about the devel-pl mailing list