88
Wojciech Gańcza Języki programowania Skrypt dla studentów IV roku Fizyki Komputerowej

Aby język giętki

  • Upload
    voque

  • View
    239

  • Download
    0

Embed Size (px)

Citation preview

Page 1: Aby język giętki

Wojciech Gańcza

Języki programowaniaSkrypt dla studentów IV roku Fizyki Komputerowej

Page 2: Aby język giętki

WstępJęzyki programowania jest jednym z kursów przygotowujących studentów do nieco

szerszego spojrzenia na projektowanie programów i programowanie. Podobnie jak język, którym mówimy, determinuje nasz sposób myślenia – język programowania determinuje sposób, w jaki projektujemy program. Dlatego nie powinniśmy się ograniczać do jednego języka programowania, nawet jeśli jest to język uniwersalny i powszechnie uznawany za najlepszy.

Niniejszy skrypt przeznaczony jest dla studentów, którzy w podstawowym kursie programowania uczą się języka C++. Celem tego skryptu nie jest ani przekonanie czytelników o wyższości czy niższości poszczególnych języków, ani nauczenie wszystkich prezentowanych języków. Chciałbym tu jedynie zaprezentować inne sposoby programowania i inne, typowe dla innych języków, style. Jeśli czytelnik nie ma zamiaru zmieniać języka czy środowiska, w jakim programuje – nie będę go do tego przekonywał. Moim zamiarem jest przede wszystkim pokazanie innego sposobu rozwiązywania problemów, w sytuacji, w której język programowania nie pozwala na dowolność lub wymusza pewien konkretny styl czy metodę.

Książka ta może także być wstępem do nauki różnych języków programowania. Można ją czytać dla rozrywki lub w celu pogłębienia swojej wiedzy i umiejętności. Wprawdzie współczesne języki programowania pozwalają na dużą dowolność w stylu czy podejściu do programowania, jednak programiści uczący się takich języków zazwyczaj idą po najmniejszej linii oporu – ucząc się konkretnego stylu.

Języki, które chciałbym zaprezentować, nie pozwalają na pisanie w ten sam sposób. Każdy z nich powinien nauczyć czegoś innego, tak by po powrocie do języka, w którym czytelnik programował, poznane metody pozwoliły na pełniejsze wykorzystanie jego możliwości.

Page 3: Aby język giętki

Aby język giętki...Na jednej ze stron internetowych, można znaleźć prosty program napisany w ponad

dwustu różnych językach programowania. Można by zadać sobie pytanie: po co istnieje tyle języków programowania zamiast jednego – najlepszego i uniwersalnego – na przykład C++ – albo innego.

Tak samo można zadać sobie pytanie: dlaczego ludzie mówią różnymi językami? Dlaczego powstają hermetyczne języki typowe dla pewnych grup zawodowych czy zastosowań? Podobnie jest z językami programowania.

Początkowo programowanie odbywało się w języku procesora. Programista panował całkowicie nad tym, co się dzieje z informacją, na której komputer operuje. W miarę komplikowania się programów i komputerów coraz bardziej skomplikowane stało się pisanie i konserwowanie programów. Musiały się więc pojawić sposoby pisania programów, które pozwalały na zapis programu w sposób zrozumiały dla programisty.

Pierwsze języki pozwalały na pisanie programu w postaci tekstu – ciągu instrukcji. Instrukcje są skrótami, które są tłumaczone na pojedyncze instrukcje procesora. Nie musimy się jednak martwić o to, w jakich komórkach pamięci umieścimy program, a w jakim dane – o to zadba program tłumaczący.

Kolejne języki programowania coraz bardziej przypominają język naturalny. Pojawiają się całe słowa opisujące czynności, jakie ma wykonać komputer. Nieistotne informacje są przed nami ukrywane, a obliczenia możemy zapisywać w postaci naturalnych dla nas wyrażeń.

Pojawiają się języki specjalizowane – języki zorientowane na konkretne zastosowania: FORTRAN – do prowadzenia obliczeń, SIMULA – jedne z pierwszych języków obiektowych zorientowany na budowę symulacji, COBOL do operacji na danych zapisanych w postaci rekordów o znanej strukturze, PROLOG – do operowania na bazach wiedzy, LISP – do operacji na danych nie posiadających ustalonej struktury, czy wreszcie C – język zastępujący ASSEMBLER w programowaniu systemowym.

Dalsza ewolucja komputerów prowadzi do powstania złożonych języków uniwersalnych, pozwalających pisanie w sposób typowy dla innych języków programowania. Jednym z takich języków jest C++ czy ostatnio C#, pozwalający na obiektowe podejście do problemów – bardzo przydatne przy modelowaniu. Pozwalają one na pisanie funkcji i procedur – podobnie jak proste języki systemowe oraz na operowanie na strukturach dynamicznych, podobnie jak LISP.

Języki uniwersalne są naprawdę bardzo dobrym narzędziem dla doświadczonego programisty, jednak nauka programowania jest na nich nieco utrudniona. Nauka – nie programowanie, bo programować można na wiele najrozmaitszych sposobów, i nawet najbardziej absurdalne rozwiązanie zawsze daje się zaprogramować.

Stare, dobre języki programowania, takie jak PASCAL czy ADA, wymuszały pewien porządek w kodzie programu. W C++ można napisać program, który będzie działał, ale którego nie będzie się dawało w żaden sposób zrozumieć. I nie chodzi mi tu o dziwne nazwy zmiennych, czy też wcięcia robione dość dowolnie, ale o strukturę kodu, który jest po prostu nieczytelny.

Studenci często piszą kod po to, żeby działał, i po to by dostać zaliczenie lub ocenę. Rzadko przejmują się tym, że ktoś kiedyś będzie musiał go przeczytać, zrozumieć, a nawet poprawić. Tymczasem po ukończeniu studiów trafią oni do pracy, w której po pierwsze,

Page 4: Aby język giętki

będą pracować w zespole, pisząc kod wraz z innymi programistami, najczęściej zaczynając od robienia poprawek w tym, co napisał ktoś inny. Poza tym ten sam kawałek kodu zazwyczaj żyje w programie dość długo i z wersji na wersję trzeba go przebudowywać i poprawiać, zwiększać funkcjonalność i dostosowywać do nowych środowisk i systemów. I niestety trzeba go poprawiać, a nie pisać od początku, gdyż pisząc od początku – możemy popełnić nowe błędy, a poza tym ciągłe przepisywanie kodu jest czasochłonne, a co za tym idzie kosztowne.

Dlatego na zajęciach z języków programowania postawiłem sobie założenie: nie uczymy się innych języków po to, by w nich programować, ale by poznać metody programowania, typowe dla pewnych klas programów i ich zastosowań. Dlatego w trakcie tego kursu zajmiemy się najprostszymi dialektami wybranych języków. Nie będziemy korzystali z ułatwień jakie wprowadzano w trakcie ewolucji tych języków. Języki zostały tak dobrane, by uczestnicy kursu mogli poznać konkretne techniki – typowe dla tych języków.

Istnieje dowód twierdzenia, że jeśli problem jest rozwiązywalny w jednym z języków – to jest także rozwiązywalny w innym, pod warunkiem, że oba języki są zupełne. Istnieje też wiele dowodów praktycznych, że prowadzi to do komplikacji kodu i nerwicy programisty.

Podobnie jest z językami naturalnymi. Można zaryzykować twierdzenie, że jeśli jakieś pojęcie daje się wyrazić w jednym z języków, to można je opisać w innym. Czasem jest to jednak bardzo skomplikowane, jak w przypadku chińskiego słowa tao. W zasadzie dosłowne tłumaczenie nie jest możliwe, ale pojęcie, jakie to słowo wyraża, daje się opisać – w sposób o tyle wierny, o ile skomplikowany.

Dlatego jeśli chcemy zrozumieć innych ludzi i rozszerzyć swoje rozumienie i świadomość, uczmy się języków. Podobnie w nauce programowania – poznajemy nowe języki nie po to, by w nich pisać, ale by poznawać nowe metody i konstrukcje.

Dla programistów znających C++ lub uczących się tego języka, chciałbym polecić 5 języków programowania których znajomość bardzo wzbogaci nasze programowanie:1. FORTRAN – język bardzo prosty, zmuszający programistę do optymalizowania

programu już na etapie jego projektu. Jednocześnie język, który pozwala na pisanie programu w postaci procedur i funkcji, a nie pozwalający jedynie na operowanie na dynamicznych strukturach danych.

2. LISP – w tym języku nie możemy w ogóle mówić o ciągu instrukcji. W najbardziej fundamentalistycznej wersji w ogóle nie możemy mówić o iteracji. Jedyne czym dysponujemy – to superpozycja funkcji.

3. RedCode – w zasadzie język napisany do zabawy: język do pisania zawodników w grze CORE WARS. Język będący kwintesencją języków wewnętrznych procesora.

4. JAVA – język obiektowy: tu wszystko jest obiektem. Język o tyle ciekawy, że pozwala na łączenie modułów w memencie wykonywania kodu, a nie w momencie kompilacji. Ponadto zarzucający pewne ograniczenia na strukturę dziedziczenia klas.

5. JavaScript – Język skryptowy. W zasadzie obiektowy, ale bez ścisłej kontroli typów. Do czego to prowadzi – warto zobaczyć,

Myślę, że nawet po pobieżnym zapoznaniu się z tymi językami, czytelnik będzie w stanie lepiej wykorzystać potencjał jaki drzemie w uniwersalnym języku jakiego używa – bez względu na to, jaki to język.

Page 5: Aby język giętki

FORTRANJęzyk FORTRAN należy do języków w których programista musi męczyć się tak długo –

żeby procesor już nie musiał – wymuszając styl programowania łatwy do automatycznej optymalizacji nawet przez najprostsze kompilatory. Rozpoczynając pracę w tym języku – trudno oprzeć się wrażeniu, że gramy w koszykówkę chodząc po boisku na czworaka. Z drugiej jednak strony – możemy bardzo ułatwić kompilatorowi życie i pozwolić na wyprodukowanie na prawdę efektywnego kodu.

Język ten dostarcza nam jedynie statycznie alokowaną pamięć, tablice o dobrze zdefiniowanych rozmiarach i taką obsługę plików, abyśmy się dobrze zastanowili – zanim użyjemy ich jako struktur dynamicznych. Panuje powszechna opinia, że programy pisane w języku FORTRAN – są szybkie. Jest to prawda, o ile celowo nie skomplikujemy kodu, aby uniemożliwić kompilatorowi przekazywania zmiennych przez referencje1.

Język FORTRAN pochodzi z czasów kiedy programy były pisanie na arkuszach papieru, następnie przepisywane na karty dziurkowane lub i w takiej formie analizowane przez komputer. Kompilator wyrzucał kompilat w postaci taśmy dziurkowanej, którą można było już ‘wpuścić’ w maszynę i po chwili otrzymać wyniki na przypominającej młockarnię – drukarce wierszowej.

Ponieważ objętość arkuszy programu a szczególnie kart perforowanych – była ograniczona –zapisu programu musiał spełniać ustalone reguły.

Po pierwsze program składa się z linii w których zapisane są – jedna deklaracja lub instrukcja w jednej linii – ewentualnie kontynuowanej w kolejnych liniach – ale bez możliwości umieszczania kilku instrukcji obok siebie w jednej linii.

W języku FORTRAN program jest wykonywany instrukcja po instrukcji – tak jak są one zapisane w tekście programu. Możliwe są tu skoki – i co może się wydać dziwne dzisiejszym programistom przyzwyczajonym do programowania strukturalnego – nie są one niczym wstydliwym. Skoki i końce pętli musimy oznaczyć w programie wskazując w jakiej linii kończy się pętla, czy też do jakiej linii kodu powinien program przeskoczyć. Linie takie oznaczamy etykietą – liczbą całkowitą z przedziału od 1 do 99999. Liczby te nie muszą mieć nic wspólnego z kolejnością linii i nie muszą występować na początku linii (jak w starych wersjach języka BASIC). Wstawiamy je tam gdzie są potrzebne, i nadajemy im takie wartości jakie przyjdą nam do głowy.

Każda linia ma określony format – miejsca zajmowane przez kolejne znaki mają różne przeznaczenie:

Znaki 1-5 służą do zapisania etykiety instrukcji Znak 6 jest używany do określenia , czy linia jest początkiem, czy kontynuacją

instrukcji (jeśli znak ten jest różny od * i spacji) - linia zawiera kontynuację poprzedniej linii (jeśli linia kodu nie zmieściła się w jednej linii może być kontynuowana przez 19 kolejnych linii.

Znaki 7-72 zawierają treść instrukcji Znaki powyżej 73 są ignorowane przez kompilator.

1 Referencje rozumiane są tu inaczej. Ponieważ pamięć alokowana jest statycznie – często przekazując parametr – tak na prawdę przekazujemy samą zmienną – w kompilacie użyty jest adres przekazywanej zmiennej a nie – jak to ma miejsce w C – wskaźnik do niej.

Page 6: Aby język giętki

Etykieta może wystąpić jedynie w linii która jest początkiem instrukcji (nie może wystąpić w linii będącej kontynuacją poprzedniej linii). Jeśli w pierwszej linii znajduje się litera 'C' lub '*' - linia ta zawiera komentarz. Komentarze nie mogą być kontynuowane w następnej linii - można natomiast umieścić w następnej linii kolejny komentarz. Komentarze są pomijane przez kompilator podobnie jak spacje które nie mają żadnego znaczenia jeśli nie są częścią stałych tekstowych.

Program w języku FORTRAN podzielony jest na bloki. Kolejność występowania bloków nie jest istotna. Możemy w blokach korzystać z bloków które są zadeklarowane później – a nie tak jak w C – tylko tych które już zadeklarowano.

Bloki mają podobną konstrukcję2:

DEKLARATOR_BLOKU nazwa(lista parametrów) deklaracje zmiennych zawartość bloku (wiele linii kodu)END

Do dyspozycji mamy 4 rodzaje bloków: PROGRAM – blok od którego rozpoczyna się wykonywanie programu. Gdy

instrukcje zapisane w tym bloku zakończą swoje działanie – program się kończy. W programie może być tylko jeden blok PROGRAM.

SUBROTINE – podprogram – coś w rodzaju procedury. Fragment programu który wykonuje jakieś czynności.

FUNCTION – funkcja – podobnie jak podprogram – jednak zwraca wartość – jak to funkcja.

BLOCK DATA – blok danych – blok pozwalający na zainicjowanie wspólnych zmiennych w programie.

Bloki nie mogą być zagnieżdżone – podobnie jak funkcje w języku C. Bloki mogą się nawzajem wywoływać – i nie ma limitu na ilość wywołań – poza oczywiście rozmiarem stosu maszynowego naszego komputera. Funkcje wywołujemy podając ich nazwy i w nawiasach – parametry natomiast procedury wywołuje się pisząc instrukcję CALL, nazwę procedury i w nawiasach – parametry.

Blok PROGRAMBlok program jest blokiem od którego rozpoczyna się wykonywanie programu. W bloku

tym możemy podać nazwę programu, ale ma ona znaczenie komentarza. Nie ma sensu podawanie parametrów. Mogą być one używane w nowszych mutacjach tego języka. Dla nas parametry bloku PROGRAM nie mają znaczenia.

Blok FUNCTIONBlok definicji funkcji musi posiadać nazwę. Nazwa bloku jest jednocześnie nazwą funkcji.

Jeśli funkcja posiada parametry – ją one wymienione w nawiasach po nazwie bloku. 2 Wcięcia w programach pisanych w języku FORTRAN nie są zwyczajowe – ale bardzo

ułatwiają zrozumienie kodu – dlatego w przykładach będziemy ich używać. W przykładach dostępnych w literaturze – wcięć zazwyczaj się nie stosuje.

Page 7: Aby język giętki

Parametry oddzielany przecinkami. Funkcję wywołujemy podając po prostu jej nazwę oraz wartości parametrów w nawiasach. Na liście parametrów podajemy jedynie nazwy poszczególnych parametrów. Ich typu – podobnie jak typ zwracanego wyniku – deklarujemy w części bloku przeznaczonej na deklaracje. Przypomina to trochę stary język C. Liczba i typy parametrów muszą się zgadzać z tymi jakie są zadeklarowane w funkcji.

Typ zwracanego wyniku określmy deklarując typ zmiennej o nazwie takiej samej jak nazwa funkcji. Trochę to skomplikowane, ale można się przyzwyczaić. Na przykład funkcję fi2r która przyjmuje jako parametr liczbę całkowitą i dwie liczby rzeczywiste a zwraca – liczbę zespoloną możemy zadeklarować jako:

FUNCTION fi2r(a,b,c) INTEGER a REAL b,c COMPLEX fi2r ... END

Jeśli funkcja nie wymaga podania parametrów (jest bezargumentowa) - listę parametrów oraz nawiasy można pominąć

Wartość zwracaną przypisujemy do zmiennej o takiej samej nazwie jak nazwa funkcji. Nazwa ta może pojawić się jedynie po lewej stronie znaku równości.

Jeśli jako parametr przy wywoływaniu funkcji podamy nazwę zmiennej - to zostanie ona przekazana przez referencję i będzie można zmienić jej wartość z wnętrza funkcji. Jeśli przy wywoływaniu funkcji, w miejscu parametry, wpiszemy wyrażenie - zostanie ono przekazane przez wartość. Może to być trochę mylące, ale pozwala na znaczne przyspieszenie wykonywania programu. Ciekawe jest to że do tej samej funkcji te same parametry mogą trafiać raz przez wartość a raz przez referencje.

Blok SUBROUTINEBlok definicji podprogramu wygląda podobnie jak blok definicji funkcji, nie ma tu jednak

sensu definiowanie typu ani przypisywanie na nazwę procedury. Podobnie ja w funkcji - parametry przekazywane są przez referencję - o ile to jest możliwe. Jeśli nie – przekazywane są przez wartość.

Zmienne prosteZmienne identyfikowane są przez ich nazwy. Nazwa zmiennej składa się z ciągu znaków

(liter i cyfr) z których pierwszy jest literą. Długość nazwy zmiennej nie może przekraczać 6 znaków. W FORTRANIE występują następujące typy zmiennych:

Całkowite (INTEGER) Rzeczywiste (REAL) Rzeczywiste o zwiększonej precyzji (DOUBLE PRECISION) Zespolone (COMPLEX)

Page 8: Aby język giętki

Logiczne (LOGICAL) - przyjmujące jedną z dwu wartości: .TRUE. i .FALSE. Znakowe (CHARACTER) - pozwalające na przechowywanie tekstu o zadanej

długości. Długość tekstu podaje się w nawiasach, lub pisząc znak '*' oraz liczbę znaków po deklarowanej zmiennej.

Zmienne zajmują zawsze określoną ilość miejsca w pamięci komputera. Zmienne typu INTEGER, REAL oraz LOGICAL zajmują jedną jednostkę pamięci, Zmienne typu DOUBLE PRECISION oraz COMPLEX - dwie takie jednostki. Zmienne typu CHARACTER – liczone są w innych jednostkach – nie należy więc ich mieszać z typami liczbowymi i nie jest bezpiecznie (czytaj – mądrze – używać instrukcji EQUIVALENCE do mieszania typów znakowych i liczbowych.

Zmienne proste nie muszą być deklarowane. Jeśli zmienna nie jest zadeklarowana - jej typ jest określony przez pierwszą literę nazwy tej zmiennej. Jeśli zmienne jest zadeklarowana - jej typ określa jej deklaracja.

TabliceTablice muszą być zadeklarowane przed ich użyciem. Deklaracja może być określeniem

typu tablicy. Wygląda to wtedy jak deklarowanie typu zmiennej prostej, jednak po nazwie zmiennej podaje się w nawiasach okrągłych rozmiar (rozmiary - dla tablicy wielowymiarowej) tablicy. Rozmiar możemy podać pojedynczą liczbą - indeks tablicy przebiega wtedy wartości od 1 to zadanej wartości włącznie. Można także określić jawnie zakres zmiany indeksu podając dwie liczby oddzielone dwukropkiem - min:max.

Tablicę można również definiować używając deklamatora DIMENSION, lub wymieniając go na liście instrukcji COMMON. W obu tych przypadkach typ jest zależny od pierwszej litery nazwy tablicy.

Tablice mogą mieć maksymalnie 6 wymiarów.

Inne typyW programie napisanym w języku FORTRAN nie ma tu możliwości definiowania struktur

ani tablic których elementami są tablice. Nie ma także możliwości zadeklarowania tablic o rozmiarze dynamicznie wyznaczanym przez program.

PrzypisaniaInstrukcje przypisania mają postać zmienna=wyrażenie. Jest to naturalna i prosta

forma zapisu. Najpierw wartościowana jest prawa strona wyrażenia, następnie następuje przypisanie wartości do zmiennej. Prawidłowe są wiec konstrukcje I=I+1.

Przypisania wykonywane na wycinek tekstu (fragment zmiennej tekstowej) dotyczą zawsze tylu znaków ile zawiera wycinek. W razie potrzeby zawartość wycinka uzupełniana jest spacjami lub przycinana.

Inną formą przypisania jest instrukcja ASSIGN o formacie ASSIGN etykieta TO zmienna, gdzie zmienna jest zmienną całkowitą, natomiast etykieta jest etykietą z bieżącego segmentu. Użycie tej instrukcji pozwana na wykonywanie skoków do etykiet zapamiętywanych w zmiennych. Nie jest to przypisanie wartości liczbowej – wartość takiej zmiennej nie jest równa liczbie z etykiety.

Page 9: Aby język giętki

Operatory arytmetyczneW języku FORTRAN dostępne są podstawowe dwuargumentowe operatory arytmetyczne:

+ - dodawanie - - odejmowanie - mnożenie / - dzielenie - wszystkie cztery - lewostronnie łączne) ** - potęgowanie - działanie prawostronnie łączne

Typ wyniku zależy od typu argumentów1. Jeśli argumenty są takiego samego typu - to wynik jest tego samego typu co

argumenty (UWAGA: dzielenie wartości całkowitych jest dzieleniem całkowitym).

2. Jeśli argumenty mają różny typ - wykonuje się konwersję jednego z nich do typu drugiego zgodnie z zasadami:

o Mieszanie typów zespolonego i podwójnej precyzji jest zabronione o Jeśli jeden z argumentów jest typu zespolonego lub podwójnej precyzji

to drugi argument otrzymuje ten sam typ. o Jeśli jeden argument jest typu rzeczywistego a drugi całkowitego - to

przekształca się wartość całkowitą. Konstruując wyrażenia można używać nawiasów w celu zmiany priorytetów działań w

wyrażeniu. Jeśli nie użyto nawiasów, to priorytety są następujące: 1. wywołania funkcji, 2. potęgowanie, 3. mnożenie i dzielenie, 4. dodawanie i odejmowanie

Operatory logiczneWyrażenia logiczne można konstruować używając operatorów logicznych oraz operatorów

relacji. Operatory logiczne operują na argumentach logicznych, Operatory relacji pozwalają na wartościowanie logiczne warunków nakładanych na wartości wyrażeń arytmetycznych i znakowych. Operatory logiczne oraz operatory relacji wyglądają nieco dziwnie ale używa się ich w taki sam sposób jak operatorów arytmetycznych. Do dyspozycji mamy:

.NOT. - operator negacji (jednoargumentowy) .AND. - iloczyn logiczny .OR. - suma logiczna .EQV. - równoważność .NEQV. - nierównoważność .GT. - relacja większości (ang. Greater Than, '>' w C) .GE. - relacja nie mniejszości (ang Greater or Equal, '>=' w C) .LT. - relacja mniejszości (ang Less Than, '<' w C)

Page 10: Aby język giętki

.LE. - relacja nie większości (ang Less or Equal, '<=' w C) .EQ. - relacja równości (ang EQual, '==' w C) .NE. - relacja nierówności (ang Not Equal, '!=' w C)

Operatory znakoweW Języku FORTRAN jest tylko jeden operator znakowy - operator łączenia (konkatenacji)

napisów - // . Inną operacją która nie jest określona żadnym operatorem jest operacja brania wycinka tekstu. Operację tą zapisuje się jako zmienna(od:do), gdzie zmienna jest nazwą zmiennej znakowej, a wartości od i do są wyrażeniami całkowitymi spełniającymi warunek: 1<=od<=do<=dlugość tekstu

Wywołania funkcjiWywołanie funkcji ma postać nazwafunkcji(argumenty), gdzie nazwa funkcji jest

nazwą funkcji wbudowanej lub zadeklarowanej przez programistę, natomiast argumenty funkcji są listą wyrażeń oddzielonych przecinkami, których liczba oraz typy musza być zgodne z ilością i typami parametrów jakie są zadeklarowane w definicji tej funkcji.

Instrukcje sterująceInstrukcje sterujące pozwalają na kontrolowanie przebiegu programu. W Języku

FORTRAN – mamy do dyspozycji bardzo okrojony, ale wystarczający zestaw instrukcji zawierający instrukcję skoku, instrukcje wykonania warunkowego, pętli, wywołania i powrotu z podprogramu oraz instrukcję zatrzymania programu.

Instrukcja skoku: GOTOInstrukcja GOTO zmienia kolejność wykonywania instrukcji w programie - kolejną

instrukcją które będzie wykonana, będzie instrukcja umieszczona w linii opatrzonej wskazaną etykietą. Instrukcja GOTO występuje w trzech postaciach:

bezwarunkowe GOTO powoduje natychmiastowy skok do wskazanej instrukcji: GOTO n

wyliczane GOTO. Instrukcja o formacie: GOTO (n1,n2,...,nn),w , gdzie n1..nn są etykietami natomiast w jest wyrażeniem całkowitym. W zależności od wartości wyrażenia - wykonywany jest skok do etykiety zajmującej w-tą pozycję na liście etykiet. Jeśli wartość wyrażenia w jest mniejsza od 0 lub większa od liczby etykiet na liście - skok nie jest wykonywany.

przypisane GOTO: GOTO v lub GOTO v,(n1,n2,...,nn) - powoduje skok do instrukcji której etykieta znajduje się w zmiennej v. Nie jest to jednak numer odpowiadający etykiecie, ale specjalna wartość jaką można nadać zmiennej całkowitej używając instrukcji ASSIGN, wyliczającej adres (w pamięci) pod jakim znajduje się instrukcja opatrzona wybraną etykietą. W drugiej wersji - na liście umieszczamy dozwolone etykiety pod które może być wykonany skok.

Wbrew temu czego uczymy się poznając języki w których programujemy strukturalnie – instrukcję GOTO można i należy używać wszędzie tam gdzie jest potrzebna.

Page 11: Aby język giętki

Instrukcja warunkowa IFInstrukcja IF pozwala na wykonywanie pewnych fragmentów programu w zależności od

prawdziwości pewnych wyrażeń logicznych. W języku FORTRAN, instrukcja warunkowa występuje w trzech mutacjach:

Logiczne IF: Instrukcja ma format IF (warunek) instrukcja, gdzie warunek jest jakimkolwiek wyrażeniem typu logicznego, natomiast instrukcja jest dowolną instrukcją języka FORTRAN za wyjątkiem instrukcji pętli DO, i innych instrukcji warunkowych

Arytmetyczne IF o formacie: IF (wyrażenie) etykieta1, etukieta2, etykieta3. Wykonanie tej instrukcji polega na wykonaniu skoku do instrukcji opatrzonej etykietą etykieta1, jeśli wartość wyrażenia jest mniejsza od zera, etykieta2, jeśli wartość wyrażenia jest równa 0, lub etykieta3, jeśli wartość wyrażenia jest dodatnia. Wyrażenie może być dowolnym wyrażeniem całkowitym, rzeczywistym, lub rzeczywistym podwójnej precyzji.

Blokowe IF - podobne do instrukcji IF znanej z języków PASCAL, C czy C++. Blok instrukcji wykonywanych warunkowo rozpoczyna się instrukcją IF (warunek) THEN , zaś kończy instrukcją END IF. Wewnątrz bloku może znajdować się dowolnie dużo instrukcji. Dodatkowo, można zaznaczyć blok instrukcji wykonywanych gdy warunek nie jest spełniony (pomiędzy IF ... THEN a END IF umieszczamy instrukcję ELSE, lub ELSE IF (warunek) THEN - jeśli potrzebujemy bardziej skomplikowanych konstrukcji warunkowych). Wewnątrz dowolnego bloku może wystąpić blokowa instrukcja warunkowa (zagnieżdżona). Pętle muszą zawierać się całkowicie w bloku warunkowym. Nie można skoczyć do wnętrza bloku warunkowego.

Instrukcja pętli: DOInstrukcja DO służy do zorganizowania pętli w programie. Instrukcja ma postać DO

etykieta zm=w1,w2,w3, gdzie etykieta, jest etykietą ostatniej instrukcji wykonywanej w pętli i musi znajdować się poniżej instrukcji DO. Zmienna zm, jest zmienna sterującą pętli. Na początku pętli nadawana jest jej wartość równa wartości wyrażenia w1, i po każdym obiegu pętli dodawana jest wartość wyrażenia w3. Instrukcje w pętli powtarzane są do chwili, kiedy zmienna zm będzie miała wartość większą lib równa wyrażeniu w2. Wyrażenie w3 może być pominięte, i wtedy krok pętli wynosi 1.

Wartości wyrażeń w2 i w3 są wyliczane tylko raz na początku pętli i na ich podstawie wyznaczana jest ilość obiegów pętli DO. Zmiana składników wyrażeń wewnątrz pętli nie wpłynie w żaden sposób na ilość iteracji pętli. Podobnie nie ma sensu zmieniać w pętli wartości zmiennej sterującej. Nie będzie to miało żadnego znaczenia (niektóre kompilatory wykażą w takiej sytuacji błąd, lub wypiszą ostrzeżenie).

Instrukcja pusta: CONTINUEInstrukcja CONTINUE nic nie robi. Efektem jej wykonania jest przejście do kolejnej linii

programu. Najczęściej jest używana jako ostatnia instrukcja pętli.

Page 12: Aby język giętki

Instrukcja wywołania procedury: CALLInstrukcja CALL służy do wywoływania procedury. Po instrukcji CALL powinna

występować nazwa procedury oraz, w nawiasach, lista jej argumentów. Liczba i rodzaj parametrów musi się oczywiście zgadzać z liczbą i rodzajem argumentów jakie zadeklarowano w definicji procedury. Wywołania funkcji nie wymagają użycia żadnych specjalnych dekoracji.

Instrukcja powrotu z podprogramu: RETURNWykonanie instrukcji RETURN powoduje powrót z podprogramu lub funkcji. Instrukcja

ta nie ma żadnych parametrów. Wartość jaką zwraca funkcja powinna być przypisana do zmiennej o takiej samej nazwie jak nazwa funkcji.

Instrukcja zatrzymania programu: STOPWykonanie instrukcji STOP powoduje natychmiastowe zakończenie wykonywania

programu.

Instrukcja ENDInstrukcja END kończy segment. Dodatkowo jest interpretowana jako instrukcja STOP lub

RETURN, w zależności od tego czy jest umieszczona na końcu segmentu głównego lub segmentu zawierającego procedurę lub funkcję.

Wejście / WyjściePliki oraz urządzenia wejścia / wyjścia traktowane są jak zbiory zawierające rekordy.

Pojedyncza instrukcja wprowadzająca lub wyprowadzająca dane dotyczy zawsze całego rekordu. Odpowiednikiem rekordu w pliku oraz na urządzeniu wejścia - wyjścia jest linia tekstu - ciąg znaków o stałej długości zakończany przejściem do nowej linii.. Wyprowadzania danych powoduje przejście do nowej linii - ponieważ zapisywany jest zawsze cały rekord..

Każda instrukcja wyprowadzania lub wprowadzania danych wymaga podania formatu w jakim będą wyprowadzone dane. Można określić plik jako nieformatowany, ale jego użycie jest wtedy znacznie utrudnione.

Otwarte pliki oraz urządzenia, przypisane są kanałom, z których każdy jest identyfikowany liczbą całkowitą. Domyślny kanał można wyprać pisząc w miejscu w którym wymagany jest numer - gwiazdkę. Operując na pliku – posługujemy się wyłącznie jego numerem.

Instrukcje wejścia / wyjściaW języku FORTRAN 77, dostępne są trzy instrukcje obsługujące wejście / wyjście: READ

- dla wejścia, oraz WRITE i PRINT dla wyjścia Instrukcja READ występuje w dwu wariantach:

READ(lista sterowania) lista-zmiennych-do-wczytania READ nr-kanału, lista-zmiennych-do-wczytania WRITE(lista sterowania) lista-zmiennych-do-wczytania

Page 13: Aby język giętki

PRINT nr-kanału, lista-zmiennych-do-wczytania Gdzie: lista zmiennych - jest ciągiem nazw zmiennych przeznaczonych do wczytania z

wejścia lub wyprowadzenia na wyjście. Lista sterowania zawiera listę specyfikatorów. Pierwszy specyfikator może nie mieć nazwy - jest wtedy traktowany jako numer kanału. Drugi parametr oznacza etykietę instrukcji FORMAT zawierającej wzorzec formatowania wyprowadzonego tekstu.

Na liście zmiennych, przy wprowadzaniu lub wyprowadzaniu tablic, można użyć pewnej ciekawej konstrukcji, tzw. DO implikowanego. Przypomina ona instrukcję DO:

READ *, (A(i), I=1,10).

W tej instrukcji, do tablicy A przeczytane będą elementy od 1 do 10. Proste?

FormatowaniePodczas większości operacji wejścia / wyjścia należy podać w jaki sposób mają być

sformatowane wyprowadzane informacje. Format wybiera się podając etykietę instrukcji FORMAT. Instrukcja ta może być umieszczona w dowolnym miejscu w bloku w którym jest używana. Instrukcja FORMAT ma postać:

FORMAT(wzorzec),

gdzie wzorzec jest ciągiem stałych tekstów oraz specyfikatorów wskazujących jakiego typu zmienne będą wyprowadzana i jaką precyzją.

W tabeli przedstawiono pełną listę deskryptorów oraz znaków sterujących wyprowadzaniem informacji

Deskryptor Znaczenie

rA Pole znakowe. Przy wyprowadzaniu - długość jest równa długości pola znakowego

rAw Pole znakowe - określona długość polaprDw.d pole podwójnej precyzjiprEw.d pole rzeczywiste w postaci wykładniczej

prEw.dEe pole rzeczywiste w postaci wykładniczej. Wartość e określa szerokość pola wykładnika

prFw.d pole rzeczywiste w postaci stałoprzecinkowejprGw.d pole rzeczywiste - jak prEw.d lub prFw.dprGw.dEe pole rzeczywiste - jak prEw.dEe lub prFw.drIw pole całkowiterIw.m pole całkowite. Wartość m wskazuje na ilość cyfr widocznych w polu.iLw pole logiczneBN Ignoruj spacje w polu numerycznymBZ Nie wiodące spacje w polu numeryczny traktuj jako 0

Page 14: Aby język giętki

kP Współczynnik skalowania

S Sterowanie wyprowadzaniem znaku liczby. Znaczenie jak SP lub SS w zależności od procesora

SP drukować znak plus przy wyprowadzaniu liczb dodatnichSS znak plus nie ma być drukowany przy wyprowadzaniu liczb dodatnichTLs Przesunięcie bieżącej pozycji o s znaków w lewoTRs Przesunięcie bieżącej pozycji o s znaków w prawo'text' wyprowadzenie stałego teksuwHtext wyprowadzenie stałego teksuX pomijanie znaków spacji przy wprowadzaniur(grupa) Powtórzenie grupy specyfikatorów r razy

Poszczególne znaki oznaczają:

znak Znaczenied liczba znaków po przecinkup współczynnik skalowania

r repetytor w postaci stała* (może być pominięty jeśli mielibyśmy napisać "1*")

w szerokość pola w znakach

Formatowania można ponadto dokonać pisząc zamiast etykiety instrukcji FORMAT, dowolne wyrażenie znakowe mające postać wzorca formatowania, lub zmienna znakową zawierającą sensowny wzorzec.

Użycie plikówAby używać plików jako wejścia, wyjścia lub przestrzeni do przechowywania danych -

należy otworzyć plik i przypisać go do określonego kanału. Plik po użyciu musi być zamknięty, aby był dostępny dla innych programów. Oczywiście używać (czytać / pisać) możemy jedynie plików które są otwarte. Pliki możemy otwierać jako pliki tymczasowe - są one kasowane podczas zamykania, lub normalne - nowe - plik jest tworzony w trakcie otwierania, lub stare - plik jest jedynie otwierany.

Poza tym otwierany plik możemy określić jako plik o dostępie sekwencyjnym - a więc taki który można czytać "jak leci", oraz plik o dostępie swobodnym - w którym możemy programowo zmieniać pozycję w której będziemy pisać / czytać.

Wszystkie instrukcje dotyczące użycia plików mają podobny format. Po słowie kluczowym, w nawiasach podajemy listę specyfikatorów w postaci: (specyfikator=wartosc, ...). Pełna lista dopuszczalnych specyfikatorów znajduje się poniżej.

W języku FORTRAN77 dostępne są operacje:

Page 15: Aby język giętki

OPEN - otwarcie pliku. Wymagane jest podanie numeru kanału któremu będzie przypisany plik oraz nazwa pliku o ile plik nie jest tymczasowy.

CLOSE - zamknięcie pliku. Tu podajemy numer kanału, i dodatkowo, jeśli chcemy - akcję jaką należy wykonać podczas zamykania - zachować plik na dysku czy też go skasować,

INQUIRE - pozwala na sprawdzenie stanu pliku, łącznie z informacją do którego kanału jest on przypisany oraz jaka jest jego nazwa

REWIND - przewinięcie pliku na jego początek ENDFILE - obcięcie pliku w miejscu w którym plik jest czytany / pisany BACKSPACE - przesunięcie znacznika odczytu / zapisu na początek

poprzedniego rekordu (przesunięcie na początek następnego możemy wykonać wywołując instrukcję READ.

Specyfikatory dla instrukcji wejścia / wyjściaWszystkie dostępne specyfikatory zestawione są w tabelce poniżej. W poszczególnych

kolumnach umieszczono: nazwę specyfikatora, znaczenie, oraz kontekst w jakim może być użyty. Poszczególne litery odpowiadają instrukcjom OPEN, CLOSE, INQUIRE i REWIND

Specyfikator Znaczenie KontekstUNIT Numer urządzenia. OCIR

IOSTAT Nazwa zmiennej całkowitej która po zakończeniu operacji zawierać będzie kod błędu.

OCIR

ERR Numer etykiety do której należy skoczyć jeśli podczas operacji nastąpił błąd.

OCIR

FILE Nazwa pliku O-I-

STATUS

Rodzaj pliku: 'NEW' - tworzy nowy plik, 'SCRATCH' - plik tymczasowy, 'OLD' - istniejący plik, 'UNKNOWN' - nie bardzo wiadomo jaki. Dla specyfikatora CLOSE - dopuszczalne są dwie wartości 'KEEP' lub 'DELETE' oznaczające odpowiednio - zachowanie pliku i usunięcie go przy zamykaniu. Domyślną wartością jest 'UNKNOWN'.

OC--

ACCESSMetoda dostępu: 'SEQUENTIAL' - plik sekwencyjny, 'DIRECT' - plik o dostępie swobodnym. Domyślną wartością jest 'SEQUENTIAL'

O-I-

FORM

Tryb redagowania: 'FORMATTED' - plik formatowany, 'UNFORMATTED' - plik nieformatowany. Wartość domyślna zależy od typu pliku FOR gdy SEQ, UNF gdy DIR

O-I-

RECL Długość rekordu - tylko dla plików o dostępie swobodnym.

O-I-

BLANK Znaczenie spacji: Pola numeryczne są dopełniania znakiem spacji - 'NULL' lub zerem - 'ZERO'. Wartość

O-I-

Page 16: Aby język giętki

domyślna: 'NULL', Dla instrukcji INQUIRE - podajemy nazwę zmiennej znakowej w której zostanie umieszczony napis 'ZERO' lub 'NULL'

EXISTWskazanej zmiennej logicznej przypisuje wartość FALSE - jeśli plik związany z urządzeniem nie istnieje, lub TRUE - jeśli istnieje

--I-

OPENEDWskazanej zmiennej logicznej przypisuje wartość TRUE - jeśli plik związany z urządzeniem jest otwarty - w przeciwnym wypadku - FALSE

--I-

NUMBER

Jeśli plik związany jest z urządzeniem (kanałem), to zmiennej całkowitej przypisuje się numer tego urządzenia. Użycie tego specyfikatora ma sens jedynie w przypadku gdy zamiast specyfikatora UNIT użyto specyfikatora FILE w instrukcji INQUIRE

--I-

NAMEDWskazanej zmiennej logicznej przypisuje wartość TRUE - jeśli plik związany z urządzeniem ma nazwę- w przeciwnym wypadku - FALSE

--I-

NAME Wskazanej zmiennej tekstowej przypisuje wartość będącą nazwą pliku o ile plik posiada nazwę.

--I-

SEQUENTIAL

Wskazanej zmiennej znakowej przypisuje wartość 'YES' - jeśli do urządzenia / pliku można pisać / czytać sekwencyjnie, 'NO' - jeśli nie można i 'UNKNOWN' - jeśli nie wiadomo czy można czy nie

--I-

DIRECT

Wskazanej zmiennej znakowej przypisuje wartość 'YES' - jeśli do urządzenia / pliku można uzyskać dostęp bezpośredni, 'NO' - jeśli nie można i 'UNKNOWN' - jeśli nie wiadomo czy można czy nie

--I-

FORMATTED

Wskazanej zmiennej znakowej przypisuje wartość 'YES' - jeśli do urządzenia / pliku można pisać lub czytać w sposób formatowany (o stałej długości rekordów), 'NO' - jeśli nie można i 'UNKNOWN' - jeśli nie wiadomo czy można czy nie

--I-

UNFORMATTED

Wskazanej zmiennej znakowej przypisuje wartość 'YES' - jeśli do urządzenia / pliku można pisać lub czytać w sposób nieformatowany (o zmiennej długości rekordów), 'NO' - jeśli nie można i 'UNKNOWN' - jeśli nie wiadomo czy można czy nie

--I-

NEXTREC Numer następnego rekordu o ile plik jest otwarty z dostępem swobodnym

--I-

Definicje stałychStałe w programie definiuje się poleceniem PARAMERTER, podając w nawiasie listę par

nazwa=wartość. Typy stałych określamy tak samo jak typy innych symboli w programie --> patrz następny paragraf.

Page 17: Aby język giętki

Deklaracja zmiennychZmienne deklarujemy jedynie na początku bloku. Zmienne proste możemy deklarować

podając nazwę typu oraz listę zmiennych które będą miały ten typ. Elementy na liście oddzielamy przecinkami Jeśli na liście występują tablice, to można od razu określić ich wymiar lub wymiary pisząc w nawiasach rozmiar tablicy. W przypadku zmiennych tekstowych definiuje się ich długość podając ją po gwiazdce (znaku mnożenia): CHARACTER*8 IDENT.

Jeśli nie zadeklarujemy typu zmiennej. określi go pierwsza litera jej nazwy. Domyślnie zmienne zaczynające się na:

I, J, K, L, M, N - są zmiennymi całkowitymi, pozostałe - rzeczywiste.

Interpretację pierwsze litery nazwy zmiennej można zmienić używając instrukcji IMPLICIT w formacie IMPLICIT lista typów i znaków, gdzie lista typów i znaków jest listą elementów (oddzielonych przecinkami) o formacie typ(zakres liter). Zakres liter to litery oddzielone przecinkami, lub konstrukcja od - do zapisana jako litera-litera. Przykładowo po zadeklarowaniu:

IMPLICIT COMPLEX(C,Z),DOUBLE PRECISION(U-X)

Wszystkie zmienne których nazwy rozpoczynają się od liter C lub Z będą zmiennymi zespolonymi, a zmienne o nazwach rozpoczynających się literami U, V, W, X – liczbami rzeczywistymi o podwójnej precyzji. Oczywiście tylko w takim wypadku – jeśli nie zadeklarowano ich inaczej.

Tablice muszą mieć określony rozmiar. Jeśli nie deklarujemy ich określając ich typ, musimy określić ich rozmiar za pomocą polecenia DIMENSION. Używa się go podobnie jak deklaratorów typu, z tą różnicą, że typ elementów tablicy określa pierwszy znak jej nazwy.

Podobnie jak nazwy zmiennych, w ciele funkcji definiujemy typy jej parametrów oraz typ zwracany przez funkcję (w procedurze typu parametrów). W FORTRANIE nie ma możliwości podania typów parametrów na liście parametrów. Typy definiujemy poprzez nazwę zmiennej (pierwszą literę), lub poprzez zdefiniowanie typu w taki sam sposób jak zmiennych lokalnych bloku - w jego wnętrzu.

Nadawanie wartości początkowychZmiennym i tablicom można nadawać wartości początkowe używając instrukcji DATA o

formacie DATA lista zmiennych / lista stałych, gdzie lista zmiennych jest taką samą listą jak lista w instrukcji READ (łącznie z możliwością użycia DO implikowanego), z zastrzeżeniem, że wszystkie indeksy tablic muszą być stałymi. Lista stałych zawiera stałe odpowiednich typów. Jeśli trzeba zainicjować wiele kolejnych zmiennych tą samą wartością - możemy użyć repetytora w postaci liczba* poprzedzając nim wartość. Przykład użycia:

DATA X,I,(A(K),K=1,20) / 1.0,5,10*0.0,4.0,5.0,8*1.0

Wartościom umieszczonym w obszarach wspólnych. inicjuje się w segmentach typu BLOCK DATA Segmenty te mają postać:

Page 18: Aby język giętki

BLOCK DATA nazwa_bloku_jak_w_common COMMON /nazwa_bloku_jak_w_common/ A,B,I(10) DATA A, B, I / 0.0, 5.92, 4*3, 6*0END

Wewnątrz bloku BLOCK DATA mogą występować jedynie instrukcje IMPLICIT, PARAMETER, DIMENSION, COMMON, EQUIVALENCE i DATA.

Obszary wspólne czyli jak dzielić zmienne pomiędzy blokami

W zasadzie wszystkie dane powinny być przekazywane jako parametry funkcji i procedur (przekazywane zawsze kiedy to możliwe poprzez referencje), ale czasami prowadzi to do niepotrzebnej komplikacji kodu programu. Dzieje się tak wtedy, gdy pewne zmienne powinny być dzielone pomiędzy kilkoma modułami (np. ustawienia, pewne stałe symulacji zadawane przez użytkownik itp.). W takiej sytuacji powinniśmy zadeklarować wspólny blok danych, w którym będziemy umieszczali zmienne widoczne (dostępne) wewnątrz poszczególnych modułów.

Bloki wspólne mogą być nazwane lub nienazwane. W programie może być tylko jeden nienazwany blok wspólny, oraz dowolna liczba bloków z nazwą. Bloki wspólna są rozróżnianie poprzez ich nazwę (nienazwany - to tak jakby nazwa była pustym tekstem).

Dostęp do wspólnego bloku uzyskujemy poprzez zmienne zadeklarowane lokalnie. Komenda COMMON definicja bloku, gdzie definicja bloku zawiera nazwę bloku pomiędzy znakami '/' oraz listę zmiennych które będą umieszczone w tym bloku. Instrukcja COMMON nie definiuje nowych nazw ani ich zasięgu. Określa tylko jakie zmienne i w jakiej kolejności umieszczone są w bloku wspólnym. Poszczególne zmienne umieszczone w bloku wspólnym, mogą mieć różne nazwy w różnych blokach programu. Typy poszczególnych zmiennych (widocznych w różnych modułach) nie muszą być zgodne - chociaż niezgodność nie ma tu sensu. Nie powinno się jednak umieszczać w tym samym miejscu zmiennych liczbowych (zajmujących jedną lub dwie jednostki pamięci - tzw. jednostki numeryczne) oraz znakowych - których długość mierzona jest w jednostkach znakowych.

Przykładowo: Jeśli w jednym bloku napiszemy

COMMON A,B,I4,M(30)

a w drugim

COMMON X, I(32)

to zmienna X w drugim bloku będzie miała taką samą wartość jak zmienna A w pierwszym. Podobnie, tożsame będą zmienne i elementy tablicy B = I(1), I4=I(2), M(1)=I(3), M(2)=I(4) itd. Podobnie dzieje się w przypadku tablic wielowymiarowych, które są umieszczane w pamięci w kolejności indeksów: np. tablica M(3,3) zawiera kolejno elementy: M(1,1), M(2,1), M(3,1), M(1,2), M(2,2), ...

Page 19: Aby język giętki

Jedna instrukcja COMMON może zawierać definicję wielu bloków - są one oddzielone przecinkami.

Equivalence - coś jakby uniaJeśli zmienne lub tablice mają zajmować ten sam obszar pamięci, możemy tego zażądać

stosując instrukcję EQUIVALENCE, po której podajemy listę typu (lista identyfikatorów), (...), ... .Lista identyfikatorów - jest listą nazw zmiennych które będą umieszczone w tym samym miejscu w pamięci. Jeśli wskażemy na liście komórki tablic, to również kolejne i poprzednie komórki będą na siebie nachodziły ponieważ elementy tablic zawsze zajmują ciągły obszar pamięci. W odróżnieniu od instrukcji COMMON, EQUIVALENCE definiuje równoważność lokalną - w jednym bloku programu.

Alternatywne wejście do procedury / funkcjiDo każdej funkcji lub procedury można zdefiniować dodatkowe punkty wejścia używając

instrukcję ENTRY o podobnej składni jak instrukcja FUNCTION lub SYBROUTINE. Rodzaj i liczba parametrów nie musi być zgodna z listą parametrów funkcji / procedury w której definiujemy dodatkowe wejście. Nazwami wejść możemy posługiwać się tak samo jak nazwami funkcji i procedur.

Dodatkowo w procedurach można używać parametru formalnego w postaci '*' Parametr taki oznacza możliwość skoku do wybranej etykiety wewnątrz bloku procedury, jeśli wywołamy procedurę podając w jego miejscu wartość w postaci *etykieta.

Funkcje wbudowaneFunkcja OpisINT konwersja liczby na liczbę całkowitąREAL konwersja liczby do typu rzeczywistegoDBLE konwersja liczby do typu rzeczywistego podwójnej precyzjiCMLPX konwersja liczby na typ zespolony.CHAR konwersja z liczby całkowitej na znakCHAR konwersja znaku do liczby całkowitej AINT obcięcie liczby do liczby całkowitej - wynik jest typu rzeczywistegoANINT Najbliższa liczba całkowita - wynik jest typu rzeczywistegoNINT Najbliższa liczba całkowita - wynik jest typu całkowitegoABS wartość bezwzględna (także moduł liczby zespolonej)MOD reszta z dzielenia SIGN znak liczbyDIM różnica dodatnia - wartość bezwzględna z różnicy argumentów)DPROD iloczyn dwóch liczb rzeczywistych - wynik podwójnej precyzji

MAX maksimum - wartość maksymalna z wartości znajdujących się na liście argumentów

MIN minimum - wartość minimalna z wartości znajdujących się na liście

Page 20: Aby język giętki

argumentówLEN zwraca długość tekstu w znakachINDEX zwraca pozycję podtekstu w tekścieLGE, LGT, LLE, LLT

funkcje porównujące teksty. Wynikiem jest wartość logiczna a działają podobnie jak operatory .GE., .GT., .LE., .LT.,

AIMAG część urojona liczby zespolonejCONJG liczba zespolona sprzężona z liczbą podaną jako argumentSQRT pierwiastekEXP eksponentaLOG logarytm naturalnyLOG10 logarytm dziesiętnySIN sinus kąta, argument wyrażony w radianachCOS cosinus kąta, argument wyrażony w radianachTAN tangens kąta, argument wyrażony w radianachASIN arkus sinus liczbyACOS arkus cosinus liczbyATAN arkus tangens liczbyATAN2 arkus tangens ilorazu dwóch liczbSINH sinus hiperbolicznyCOSH cosinus hiperbolicznyTANH tangens hiperboliczny

Przykład programuJak przykład programu proponuję program który szuka liczb pierwszych –

zaprzyjaźnionych – czyli takich par liczb pierwszych które różnią się o 2. Wykorzystamy algorytm zwany sitem Eratostenesa. Algorytm jest bardzo prosty – pracujemy na tablicy liczb, z której wykreślamy wszystkie liczby złożone Cały algorytm można zapisać jako:

1. Tworzymy tablicę kolejnych liczb, Zawierającą N liczb (N jest największą liczbą którą będziemy sprawdzać

2. Skreślamy w niej jedynkę3. Szukamy pierwszej nie wykreślonej liczby – wykreślamy wszystkie jej

wielokrotności4. Jeśli nie sprawdzaliśmy ostatniej liczby – idziemy do kroku 3

Gotowy program zapisany jest poniżej. Tablicę liczb tworzymy tu w pliku tymczasowym, operując na nim bezpośrednio w bloku program. Jedynym działaniem które jest nam potrzebne, a którego instrukcje języka nie zawierają – jest ustawienie pozycji do zapisu / odczytu – które jest wykonywane w podprogramie.

C Program poszukujacy liczb pierwszych zaprzyjaznionych metoda C sita Eratostenesa. Program jest ilustracja uzycia plikow

Page 21: Aby język giętki

C tymczasowych PROGRAM LICZBYPIERWSZEC N oznacza ile liczb bedziemy przeszukiwac PARAMETER(N=10000) INTEGER I,J,K,LC otwieramy plik tymczasowy OPEN(UNIT=9,STATUS='SCRATCH',ACCESS='DIRECT', - FORM='FORMATTED',RECL=3,ERR=900)C i wypelniamy go od razy pomijajac liczby parzyste oprocz 2 WRITE(9,700) 1 WRITE(9,700) 1 DO 100 J=1,N WRITE(9,700) 1 WRITE(9,700) 0100 CONTINUE LIMIT=INT(SQRT(REAL(N)))+1C ustawiamy poczatkowy numer liczby pierwszej I=3C szukamy kolejnej liczby pierwszej101 CALL POSITION(I)102 IF (I.GT.LIMIT) GOTO 120 READ(9,700) L IF (L.EQ.0) THEN I = I+1 GOTO 102 ELSEC wpisujemy 0 na wszystkie pozycje bedace wielokrotnosciami C znalezionej DO 110 K=2*I,N,I CALL POSITION(K) WRITE(9,700) 0110 CONTINUE I = I+1 GOTO 101 END IFC wypisujemy liczby zaprzyjaznione120 CALL POSITION(1) I=1 DO 130 J=1,N READ(9,700) L IF (L.EQ.1) THEN IF (J-I.EQ.2) THEN WRITE(*,703) I,J END IF I=J END IF130 CONTINUE CLOSE(UNIT=9) STOP

Page 22: Aby język giętki

C formaty uzywane w programie700 FORMAT(I1)701 FORMAT(I12)702 FORMAT('Blad programu')703 FORMAT(I10,I10)C wypisanie komunikatu o bledzie900 WRITE(*,702) STOP END C PODPROGRAM USTAWIAJACY WSKAZNIK PLIKU NA WYBRANEJ POZYCJI SUBROUTINE POSITION(I) INTEGER I,J INQUIRE(UNIT=9,NEXTREC=J) IF (J-I) 600,610,620C PRZESUN SIE DO PRZODU 600 DO 601 K=J,I-1 READ(9,700) L601 CONTINUEC POZYCJA JEST DOBRA610 RETURNC PRZESUN SIE DO TYLU620 REWIND(9) DO 622 K=1,I-1 READ(9,700) L622 CONTINUE700 FORMAT(I1) END

Zadania1. Proszę zainstalować kompilator języka FORTRAN – g77 oraz przygotować sobie

3 pliki wsadowe służące do edycji, kompilacji i uruchamiania bieżącego programu.

2. Proszę skompilować i uruchomić program „99 bottles of beer” w języku FORTRAN IV.

3. Przerabiając program z poprzedniego zadania, proszę napisać najprostszy program w języku fortran – typu ‘hello world’, skompilować go i uruchomić.

4. Proszę napisać program który policzy miejsca zerowe równania kwadratowego. Na wejściu programu określamy współczynniki A, B, C równania Ax2+Bx+C=0

5. Proszę napisać program znajdujący miejsce zerowe wielomianu czwartego stopnia w przedziale 0..1, metodą bisekcji.

6. Proszę napisać program wyliczający kolejne liczby pierwsze metodą sita Eratostenesa. Co zrobić jeśli tablica nie mieści się w pamięci?

a. pracujemy na pliku tymczasowym – jedynej dostępnej strukturze dynamicznej

Page 23: Aby język giętki

b. pracujemy na fragmencie tablicy – zapisując znalezione liczby do pliku wyjściowego

Proszę przemyśleć metody optymalizacji.

Page 24: Aby język giętki

LispLisp (List procesor) jest językiem operującym na listach. Jest językiem w którym nie

istnieją inne typy danych niż lista, oraz atom – liczba lub tekst, używany najczęściej jako element listy. W pierwotnej wersji – LISP nie oferuje sekwencyjnego wykowywana instrukcji – a jedynie superpozycję – czyli składanie funkcji. Czy w ten sposób daje się programować? Czy można budować promy opisując jedynie co zwraca funkcja?

Podstawowymi pojęciami w programowaniu w Lispie są lista asocjacji i wartościowanie form. Jeśli to zrozumiemy – to będziemy już blisko.

Lista asocjacji – jest zbiorem symboli i ich wartości. Każdy element tej listy posiada nazwę – która jest atomem, oraz wartość – która jest dowolną daną Lispa. Jeśli do tego dodamy jeszcze informację, że w Lispie wszystko jest daną – nawet funkcja – to możemy zaryzykować twierdzenie, że lista asocjacji jest zbiorem wszystkiego co w programie zdefiniowano – nadając temu nazwę czyli zbiór wszystkich funkcji i zmiennych.

W Lispie trudno oddzielić dane od funkcji. Podobnie jak możemy zmienić zawartość zmiennych – tak możemy przedefiniować treść funkcji – zmieniając jej działanie. Program może tu operować nie tylko na zmiennych – ale także na swojej treści.

Wartościowanie – to obliczanie wartości symboli. W przypadku zmiennych – nie ma problemu – wartością zmiennej jest po prostu przypisana do niej dana. Jeśli jednak wartościujemy symbol który jest funkcją – obliczane są wartości jej parametrów, a ich wartości skojarzane z lokalnymi nazwami tych parametrów a następnie obliczana jest treść funkcji.

Celowo piszę to o obliczaniu a nie wykonywaniu, gdyż ciało funkcji nie jest listą instrukcji ale pojedynczą formą – czyli czymś czego wartość można policzyć. Wartością funkcji jest więc forma stanowiąca jej treść po podstawieniu wartości pod symbole odpowiadające parametrom funkcji.

Jeśli do tego wszystkiego dodamy jeszcze, że Lisp jest językiem interpretowanym – mamy całkowite zaprzeczenie wszystkiego czego uczyliśmy się do tej pory.

Ale po kolei...

Atomy, listy i funkcjePodstawową formą danej jest atom. Atomem jest dowolna dana będąca tekstem lub liczbą

(w dowolnym formacie) - jednym słowem wartość prosta, nie będąca obiektem, strukturą, listą itp. W Lispie atomem jest pojedyncze słowo nie zawierające spacji, lub dowolny tekst zapisany w cudzysłowach3.

Atom może być użyty jako wartość lub jako nazwa danej. Dana może być również atomem – lub czymś bardziej skomplikowanym: listą lub funkcją – także zapisaną w postaci listy.

Jeśli w tekście programu umieścimy atom – jest on oznaczeniem zmiennej o takiej nazwie. Wyznaczanie wartości atomów jest podstawowym sposobem pracy interpretera Lispa. W zasadzie jedyne co potrafi interpreter to wyznaczać wartości atomów.

Niektóre atomy – są swoimi wartościami. Do takich atomów należą: Atom nil – reprezentujący pusty element, fałsz, pustą listę, nieprawdę

3 W takim przypadku cudzysłów również należy do zapisu atomu.

Page 25: Aby język giętki

Atom t – symbolizujący prawdę logiczną Wszystkie liczby – reprezentujące swoje wartości.

Strukturą z której tworzone są wszystkie bardziej skomplikowane struktury danych jest para. Elementami pary mogą być dowolne dane – atomy, pary i struktury składające się z wielu par. Parę zapisujemy jako dwie oddzielone kropką zapisane w nawiasie:

( A . B ) ; jest parą atomów A i B( A . ( B . C) ) ; jest parą której pierwszym

elementem jest para a drugim atom.

Para jest elementem z którego tworzona jest lista – najczęściej używany typ danych. Listę zapisujemy jako ciąg elementów, oddzielonych odstępami (spacjami), ujęty w nawiasy. Lista jest parą złożoną z głowy listy i jej ogona. Lista pusta – to po prostu atom nil. Lista jednoelementowa – para w której drugi element jest atomem nil. Tak więc

(A) ( A . nil ) () nil( A B C D E ) ( A . ( B C D E )

( A . ( B . ( C . ( D . ( E . nil ) ) ) ) )

Zapis listowy w stosunku do kropkowego jest szczególnie wygodnym uproszczeniem w przypadku struktur bardziej skomplikowanych – takich jak lista list:

( (a b) (c d) (e f) ) ( (a. (b . nil)) . ( (c . (d . nil) . ( (e . (f . nil)) . nil)))

W przypadku gdy dana nie jest listą, ale różni się od niej jedynie końcowym elementem, można skrócić zapis pisząc kropkę oddzielającą ostatnie elementy:

(a.(b.(c.(d.e)))) (a b c d.e )Listy prezentowane były często w postaci tzw. Diagramów pudełkowych w których para (a

. b) była reprezentowana przez pudełko:

A para której elementem jest para (a . (b . c)) – w postaci dwu pudełek połączonych strzałką.

W takim zapicie lista ( a b c d e ) ma zapis:

Gdzie symbol przekreślonego prostokąta odpowiada atomowi nil. Diagramy pudełkowe wskazują na sposób implementacji list – para jest strukturą zawierającą, albo daną atomową, albo wskaźnik na następny element listy.

Struktury w wyglądające jak listy, ale nie zakończone atomem nil – nie są listami.Wywołanie funkcji lub policzenia wartości jest w Lispie dość dziwne. Pisząc nazwę

wartości zmiennej – otrzymujemy jej wartość. Wywołanie funkcji zapisujemy podobnie jak w innych językach, jednak w nawiasie zapisujemy nie tylko parametry – ale także nazwę

Page 26: Aby język giętki

funkcji. Ponadto nie mamy tu operatorów zapisywanych w taki sposób jak w wyrażeniach algebraicznych – a jedynie funkcje których nazwami są symbole operatorów.

C++ Lispsin(x) (sin x)a + b (+ a b)a + b + c + d (+ a b c d)f(g(x)) (f (g x))a * b + c * d (+ (* a b) (* c d) )a * ( b + c) (* a (+ b c) )

Na początku, szczególnie w skomplikowanych wyrażeniach, może to być mylące, ale odrobina praktyki pozwoli nam na bezbłędne operowanie tym sposobem zapisu. Jednak co bardziej skomplikowane operacje zapisuje się dość prosto, a raczej w sposób równie skomplikowany co prostsze.

Lista może zawierać elementy będące listami. Nie ma żadnego ograniczenia na skomplikowanie i ilość zagnieżdżeń list. Jedynym ograniczeniem jest to – by lista nie zawierała samej siebie. Lista nie może zawierać struktur rekurencyjnych. Co nie znaczy, że struktury danych nie mogą być podobne, ale o to by nie występowały w liście pętle.

Funkcja jest także listą zawierającą 3 elementy:1. Nazwę funkcji2. Listę parametrów3. formę (wyrażenie) które funkcja zwraca.

Funkcja może jedynie zawierać jedną formę, i wynik jej obliczania jest jednocześnie wynikiem działania funkcji. Przy wywoływaniu funkcji – wartości przekazywane jako parametry do funkcji, są skojarzane z nazwami parametrów na liście, a następnie obliczana jest wartość formy. Funkcje tworzymy funkcją defun przyjmującą jako argumenty: nazwę funkcji, listę parametrów i formę służącą do obliczania jaj wartości.

Lista asocjacjiWszystkie symbole dostępne w programie znajdują się na liście asocjacji4. Lista ta zawiera

listę par: nazwa-wartość, gdzie nazwa jest dowolnym atomem, natomiast wartość – dowolną wartością: atomem, listą, daną nie listową, lub funkcją.

Wszystkie skojarzenia nazwy z wartością są dopisywane na początku tej listy. Wartości parametrów funkcji są również wartościowane, i dopisywane na początku tej listy, a przy opuszczaniu funkcji, są usuwane. W ten sposób nie ma problemu z wartościami lokalnymi wewnątrz funkcji i nie ma problemu z implementacją interpretera Lispu – w samym Lispie.

Przykładowa lista asocjacji zawierająca dwie zmienne: a=5 i b=(a b (x . y) c) i funkcję silnia wygląda tak:

4 W początkowych implementacjach była to rzeczywiście lista. W bardziej współczesnych – jest to tablica symboli, zachowująca się w przypadku powtórzeń – jak stos.

Page 27: Aby język giętki

( (silnia (lambda ( (x) (if (< x 2) 1 (* x (silnia (- x 1))))))) ( a 5) (b (a b (x . y) c) )

Odpowiednik w zapisie pudełkowym może być, przy odrobinie inwencji, nieco bardziej czytelny:

Słowo lambda jest (przynajmniej w starszych implementacjach Lispu) określeniem funkcji.

Jak widać, nawet najprostsze postacie listy asocjacji są na tyle skomplikowane, że trudno nimi operować. W Lispie, będziemy musieli się przyzwyczaić, do pracy na fragmentach list i do fragmentacji zadań w naszym programie. Z jednej strony będzie to ciekawe ćwiczenie, z drugiej – nauczymy się dbać o porządek w kodzie programu i nie pisać zbyt skomplikowanych funkcji.

Programy pisane w języku Lisp może nie są tak nieczytelne jak te w języku FORTH, ale niewiele im do nich brakuje.

Funkcje i makraW języku Lisp, możemy definiować zarówno funkcje jak i makra. Różnice między nimi są

niewielkie ale istotne:1. W przypadku wywołania funkcji – wszystkie wartości wszystkich jej argumentów

są obliczane przed wywołaniem funkcji. Wartości argumentów są liczone (wartościowane) tylko raz.

Page 28: Aby język giętki

2. W przypadku makra – wartości argumentów są obliczane w trakcie ich użycia – a więc dopiero wtedy gdy staramy się coś z nimi zrobić. Wartości przy nieumiejętnie i nie efektywnie pisanym kodzie – mogą być liczone wielokrotnie.

Przykładami funkcji są funkcje matematyczne, operatory arytmetyczne – czyli wszystkie takie funkcje które do policzenia wartości potrzebują znać wszystkie wartości swoich parametrów. Makra są użyteczne wszędzie tam, gdzie nie trzeba liczyć wszystkich argumentów, żeby otrzymać wartość – na przykład przy liczeniu wartości funkcji and – wystarczy znaleźć pierwszy argument o wartości nil by wynikiem było nil. Podobnie, dla funkcji or, wystarczy znaleźć pierwszą wartość różną od nil by być pewnym, że wynik jest t.

Funkcje podstawoweW Lispie funkcje tworzone są przez superpozycję innych funkcji. Muszą jednak istnieć

funkcje pierwotne, z których możemy tworzyć inne funkcje, aż do momentu kiedy całą funkcjonalność programu umieścimy w jednej funkcji. W języku Lisp – kilka bardzo prostych funkcji tworzy zestaw pozwalający na pisanie programów. W różnych nutacjach tego języka podawane są różne zestawy tych funkcji. W niniejszym skrypcie, chciałbym wybrać takie, które stanowią podstawowy zestaw, ale zostały zaimplementowane w języku CLisp:

car Funkcja zwracająca pierwszy element pary która została podana jako argument. Dla

argumentu który nie jest parą, funkcja nie jest określona, czyli program po prostu zatrzymuje się wyświetlając informację o napotkanym błędzie. Dla listy funkcja ta zwraca głowę listy – czyli jej pierwszy element.

a := (a b c d)(car a) -> a

cdr Funkcja zwraca drugi element pary podanej jako argument. Podobnie jak car, dla

argumentu który nie jest parą, funkcja kończy swoje działanie – błędem. Dla argumentu listowego, funkcja zwraca ogon listy, czyli listę która została przekazana jako argument, po obcięciu z niej pierwszego elementu.

a := (a b c d)(car a) -> (b c d)

Wielokrotne wywołanie funkcji car i cdr może być zastąpione funkcją której pierwsza litera to ‘c’, ostatnia ‘r’, a w środku znajduje się ciąg liter ‘a’ i ‘d’. Funkcja taka jest wielokrotnym złożeniem funkcji car i cdr.

(caadr x) = (car (car (cdr x)))Taki rodzaj zapisu ułatwia dostęp do elementów listy, jednocześnie nie komplikując zapisu

consFunkcja tworząca parę z podanych argumentów. Funkcja ta pozwala na generowanie

dowolnie skomplikowanych struktur danych.a := (a b)

Page 29: Aby język giętki

b := x(cons a b) -> ( (a b) . x)(cons b nil) -> (x)

Dla przykładu możemy z przy użyci funkcji cons budować listę jednoelementową z podanego argumentu:

(defun list1 (x) (cons x nil) )(list1 b) -> (x)(list1 a) -> ((a b))

atomFunkcja atom sprawdza czy jej argument jest daną atomową czy też daną o nieco bardziej

skomplikowanej strukturze. Jeśli argument jest atomem, funkcja zwraca atom t, jeśli argument nie jest atomem, funkcja zwraca atom nil.

a := (a b)b := x(atom a) -> nil(atom b) -> t

eqfunkcja przyjmująca dwa parametry. Wartości obu parametrów muszą być atomami. Jeśli

są one równe – to funkcja zwraca atom t, jeśli są różne – atom nil. Forma ta służy do porównywania wartości. Korzystając z tej formy, możemy napisać funkcję porównującą dowolnie złożoną daną języka Lisp.

a := (a b)b := b(eq a b) -> nil(eq a (quote (a b)) -> błąd(eq b (quote b) ) -> t(eq b (cdr (car b))) -> t

ifForma warunkowa. Forma ta przyjmuje trzy parametry. Jej konstrukcja jest podobna do

operatora ? z języka C++ - przyjmuje ona trzy parametry. Jeśli wartość pierwszego parametru jest różna od nil – wartościowana i zwracana jest forma będąca drugim parametrem. Jeśli wartość pierwszego parametru jest nil – to wartościowana i zwracana jest trzecia forma.

W niektórych implementacjach Lispu podstawową funkcją jest nie if ale cond, która przyjmuje jako parametr listę par. Jeśli pierwszy element pary jest różny od nil to zwracana jest wartość drugiego elementu pary Jeśli nie – to powtarzamy operację dla następnej pary.

Funkcję if daje się zapisać poprzez funkcję cond i odwrotnie.a := (a b)b := b(if t a b) -> (a b)(if nil a b) -> b

Page 30: Aby język giętki

defunFunkcja ta pozwala na definiowanie nowych funkcji. Funkcja przyjmuje trzy parametry:

1. Nazwę funkcji – nazwa musi być atomem. Nazwa nie jest wartościowana, nie może więc być umieszczona w zmiennej.

2. Listę parametrów – listę nazw parametrów lokalnych, czyli nazw z jakimi zostaną skojarzone wartości parametrów funkcji w chwili jej wywołania (wartościowania). Również tu, nazwy te nie są wartościowane.

3. Formę będącą wynikiem funkcji – formę której wartość będzie zwracana jako wynik dziania funkcji.

Wynikiem działania tej jest dopisanie do listy asocjacji nowej funkcji. W starszych wersjach Lispa funkcję definiujemy przy użyciu słowa kluczowego lambda, które pozwala na zdefiniowanie nienazwanej funkcji, poprzez podanie jedynie listy parametrów i formy która pozwala na obliczenie wartości funkcji. Aby takiej funkcji nadać wartość – trzeba buło użyć funkcji define, której pierwszym parametrem jest nazwa funkcji, a drugim – funkcja zdefiniowana funkcją lambda.

(defun porownaj (a b) (if (atom a) (if (atom b) (eq a b) nil ) (if (atom b) nil (if (porownaj (car a) (car b)) (porownaj (cdr a) (cdr b)) nil ) ) ))

Przedstawiona tu funkcja porównuje czy wartości podane jako parametry są równe, zachowując się tak jak funkcja eq, ale zwracająca poprawne wartości dla dowolnych wartości parametrów.

WartościowanieKażdy atom, czy lista jaką przekażemy interpreterowi jest wartościowana. Jeśli wywołamy

funkcję sinus pisząc:(sin a)

To symbol sin zostanie potraktowany jak nazwa funkcji i interpreter odszuka tą funkcję i znajdzie sposób wyznaczania jej wartości, następnie spróbuje odszukać wartość o nazwie a. W przypadku funkcji których argumenty muszą być liczbami – nie ma z tym problemu. Nazwy traktowane jak nazwy zmiennych nie są niczym zaskakującym. Ale jeśli parametrem ma być tekst? Jeśli chcemy przekazać jako argument po prostu literę „a” ?

Page 31: Aby język giętki

Jeśli napiszemy ją w cudzysłowach – to będzie ona tekstem, i jej wartością będzie litera „a” – ale również w cudzysłowach. Po prostu będzie to wtedy tekst, który podobnie jak liczby oraz wartości t i nil przedstawiają zawsze same siebie. To tak jakby istniała zmienna o nazwie t mająca wartość t.

Jeśli chcemy przekazać nie wartość – ale symbol – to musimy użyć słowa kluczowego quote:

(quote a) -> A

Można również użyć postaci skróconej: - pojedynczego apostrofu:

‘a -> A

Zarówno quote jak i pojedynczy apostrof – mogą spowodować nie wartościowanie bardziej złożonych struktur, takich jak listy. O ile ujęcie takiej listy w cudzysłowy – zrobi z niej pojedynczy tekst – to użycie apostrofu lub quote – zachowa strukturę listy, chroniąc nas przed jej wartościowaniem.

(quote (a b c) ) -> (A B C)‘(a b c) -> (A B C)

Przy cytowaniu małe litery zamieniane są na ich wielkie odpowiedniki. W języku LISP małe i wielkie litery nie są rozróżniane, a zwyczajowo małymi literami oznacza się nazwy wartości, natomiast wielkimi – same wartości.

evalWartościowanie jest wykonywane funkcją eval – która zachowuje się dokładnie tak jak

interpreter języka Lisp – wartościuje formę która jest przekazana jako argument. Przykład prostej funkcji eval znajduje się w jednym przedostatnim paragrafie części poświęconej językowi Lisp.

Definiowanie funkcji i makrFunkcje jak już wspominaliśmy, definiujemy makra defun. Przyjmuje ona 3 argumenty –

nazwę funkcji, listę argumentów oraz ciało funkcji. Lista argumentów może zawierać, oprócz nazw parametrów lokalnych, słowa kluczowe, które pozwalają na dodatkową interpretację argumentów. Obowiązują one od miejsca w którym wystąpią – do końca listy argumentów. Słowa te pozwalają na:

&rest Przypisanie do symbolu całej reszty argumentów w postaci listy. Jeśli zadeklarujemy

funkcję (defun and (a &rest r) ( ... ) )

Page 32: Aby język giętki

To funkcja ta może zostać zawołana z jednym lub wieloma argumentami. Przy wywołaniu tak zadeklarowanej funkcji and z czterema argumentami – pierwszy będzie dostępny jako symbol a, zaś kolejne trzy argumenty będą dostępne jako lista – nazwana r.

Używając &rest – bardzo łatwo napisać funkcję tworzącą listę:(defun list (&rest l) l)

&optionalOznacza, że następujące po nim argumenty są opcjonalne. Dla argumentów opcjonalnych

możemy określić – jakie wartości będą im przypisane gdy argument nie zostanie przekazany. Dla wartości domyślnych – zamiast nazwy parametru – podajemy listę zawierającą 2 elementy: nazwę parametru oraz wartość która zostanie nadana argumentowi, wtedy gdy nie zostanie przekazany przy wywołaniu funkcji.

&keyPozwala na określenie nazwanych argumentów. Argumenty nazwane nie muszą w

wywołaniu funkcji być podawane w takiej kolejności jak w definicji funkcji. Ale oczywiście mogą. Pozwala to na budowanie funkcji z argumentami domyślnymi, które mogą być opuszczane, przy określaniu wartości argumentów które stoją za nimi. Trochę to skomplikowane, ale pozwala na, na prawdę niezłe kombinowanie z argumentami funkcji.

Przy wywoływaniu funkcji – przed argumentem możemy określić nazwę argumentu który określamy:

(defun f (&key x y (z 12)) (list x y z))(f 1 2 3) -> (1 2 3)(f :y 5) -> (nil 5 12)(f :z 1 :y 2 :x 3) -> (3 2 1)

Podobnie jak w przypadku parametrów &optional – można tu podać zamiast nazwy parametru listę zawierającą dwa elementy – nazwę argumentu oraz wartość domyślną która zostanie użyta jeśli wartość parametru nie jest określona.

&auxW zasadzie nie odnosi się do parametrów funkcji, ale pozwala na budowę

pseudoparametrów, których wartości są określone podobnie jak w przypadku &key i &optional. Parametrów określonych po słowie &aux nie można podawać przy wywołaniu funkcji, jednak w ciele funkcji są one dostępne – tak jak inne argumenty funkcji.

MakraMakra definiujemy podobnie jak funkcje. Możemy u definiować argumenty podobnie jak

to mam miejsce w funkcjach – stosując takie same specyfikatory dla argumentów jak omówione powyżej, oraz dodatkowo &body, &whole i &enviroment. Oznaczające:

&body – podobnie jak &rest, ale bez wartościowania poszczególnych elementów listy. Na liście nie może wystąpić wspólnie z &rest.

&whole – do zaznaczenia, że wszystkie parametry mają być dostępne jako list o określonej nazwie. &whole musi być pierwszym elementem listy parametrów i może po nim wystąpić tylko jedna nazwa parametru.

Page 33: Aby język giętki

&enviroment – pozwala na dostęp do środowiska (listy asocjacji) w której makro zostało zawołane. Można tu podać tylko jedną nazwę parametru.

OperatoryW języku Lisp, operacje arytmetyczne i relacje są takimi samymi funkcjami jak funkcje

mające nazwy złożone z symboli operacji arytmetycznych lub logicznych. Większość operatorów potrafi działać na liście argumentów – inaczej niż to jest w tradycyjnych językach programowania. Pisząc symbol dodawania – możemy od razy zsumować całą listę wartości – wystarczy napisać:

(+ 10 4 2 1 6 3 1) -> 27A nie jak w C++:

10+4+2+6+3+1 /*co de facto oznacza*/ ((((10+4)+2)+6)+3)+1

operatory arytmetyczneOperatory arytmetyczne są w zasadzie funkcjami, zachowującymi się jak normalne funkcje

języka Lisp. W tym języku symbole mogą być elementami nazw podobnie jak litery i cyfry.Podstawowe działania arytmetyczne są dostępne jako operatory – ale przyjmują one

dowolną ilość argumentów. O ile w przypadku dodawania i mnożenia interpretacja listy argumentów jest oczywista – o tyle w przypadku odejmowania i dzielenia – dopiero po chwili zastanowienia zauważamy, że:

(/ 1 2 3 4 ) = 1 / 2 / 3 / 4(- 1 2 3 4) = 1 – 2 – 3 – 4

Mamy więc dostępne operatory dodawania, odejmowania, mnożenia i dzielenia:(+ 1 2 3) => 1 + 2 + 3(* 1 2 3) => 1 * 2 * 3(– 1 2 3) => (1 – 2 ) – 3 (/ 1 2 3) => (1 / 2) / 3

operatory logiczneOperatory logiczne działają na wartościach logicznych. W języku Lisp, nie ma specjalnego

typu logicznego – zamiast tego dowolna wartość poza atomem nil jest traktowana jako wartość prawdziwa, natomiast atom nil – jako fałsz. Jeśli operator ma zwrócić wartość logiczną – to zwróci dla prawdy – atom t., dla fałszu – oczywiście atom nil.

W Lispie zaimplementowano podstawowe trzy operatory logiczne. Operator jednoargumentowy not oraz dwa makra wieloargumentowe and i or. Operator not jest funkcją – jego argument jest wartościowany przy obliczaniu wartości operatora. Operatory and i or wartościowane są do momentu kiedy wynik operacji jest znany. Dla operatora and – do pierwszego argumentu którego wartością jest nil – wiadomo wtedy, że wynikiem jest nil.

Podobnie dla operatora or – argumenty wartościowane są do momentu gdy pojawi się jakakolwiek różna od nil. Wtedy od razy wiadomo, że wynik jest t.

(not ‘x) ->nil(not ()) -> t

Page 34: Aby język giętki

(and t t) -> t(and ‘a 5 12.665 pi) -> t(and ‘a nil (f x)) -> nil ; (f x) nie będzie

wartościowane(or nil nil ‘a f(x)) -> t ; (f x) nie będzie

wartościowane

operatory relacjiOperatory relacji znane z innych języków programowania, w języku Lisp mają trochę

więcej zastosowań niż tylko porównywanie wartości. Każdy z operatorów relacji może przyjmować całą listę argumentów. Podobnie jak w innych językach mamy tu sześć operatorów relacji:

< - sprawdzający czy wartości przekazane jako argumenty są uporządkowane rosnąco

<= - sprawdzający czy wartości są uporządkowane nie malejąco = - sprawdzający czy wszystkie wartości znajdujące się na liście argumentów są

równe. /= - trochę dziwny zapis operatora nierówności – wszystkie argumenty muszą

mieć różne wartości >= - wartości muszą być uszeregowane nie rosnąco > - lista argumentów jest malejąca

Zauważmy jakie silne warunki są sprawdzane tymi operatorami relacji. Nie są potrzebne dodatkowe operacje na zapisanie warunku 0<= x <=1 który w C++ napisalibyśmy jako:

(0<=x) && (x<=1)W języku lis wystarczy napisać:

(<= 0 x 1)I wszystko jasne.Ponadto zauważmy, że operatory relacji nie są już tak mocno ze sobą powiązane. W

przypadku operatorów dwuargumentowych – jeśli argumenty nie były równe – to na pewno były różne. W przypadku większej liczby argumentów – zarówno = jaki i /= mogą być nieprawdziwe:

(= 1 1 0) -> nil ; bo ostatnia wartość jest różna od dwu pierwszych

(/= 1 1 0) -> nil ; bo pierwsze dwie wartości są równe

Można nawet napisać taką listę argumentów dla której wszystkie relacje będą fałszywe.

RekurencjaDotychczas mówiliśmy jedynie o superpozycji funkcji. W takiej sytuacji nie ma

możliwości wykonywania ciągu operacji – jedna po drugiej. Oczywiście można sobie coś takiego samodzielnie przygotować i napisać korzystając z wcześniej poznanych funkcji i makr. Dla Lispu, skonstruowanego jako narzędzie dla matematyków prowadzących

Page 35: Aby język giętki

obliczenia symboliczne, naturalnym sposobem pisania definicji funkcji jest definiowanie rekurencyjne. Jest to w pewnych sytuacjach znacznie prostsze niż pisanie programu który będzie coś liczył krok po kroku. Oczywiście rekurencja nie jest sposobem na wszystkie problemy i można przy jej pomocy zrobić na prawdę ciekawą głupotę, jednak rozwiązania iteracyjne też nie są doskonałe.

Typowym przypadkiem rekurencyjnego obliczania – jest liczenia wartości funkcji silnia. Można ją policzyć korzystając ze wzoru pozwalającego dość sprawnie liczyć funkcję silnia:

O ile silnia liczy się rekurencyjnie – dość dobrze, o tyle obliczanie wartości elementów ciągu Fibonacciego jest najmniej efektywnym sposobem na ich liczenie:

Tu z każdym krokiem rekurencji – musimy podwajać liczbę wywołanych rekurencyjnie funkcji, co nawet dla niewielkich wartości indeksu n prowadzi do bardzo złożonych obliczeń.

Jednak rekurencja może być silnym narzędziem we wprawnych rękach. Typowym problemem rekurencyjnym jest problem rozwiązania zagadnienia tak zwanej „Wieży Hanoi”, zabawki składająca się z kilku krążków o różnej średnicy i trzech miejsc na których krążki te można ustawiać.

Zadanie polega na przeniesieniu wszystkich krążków z pierwszego miejsca na ostatnie, przy czym nie wolno o kłaść krążka większego na mniejszy i nie wolno odkładać krążków w inne miejsca niż na stosy stojące w jednym z trzech wyznaczonych miejsc.

Iteracyjne rozwiązanie tego zagadnienia jest trudne. Ale jeśli spróbujemy zastosować indukcję matematyczną – problem staje się trywialny:

1. Załóżmy, że mamy n krążków przenieś ć z miejsca A do C, korzystając jedynie z miejsca B jako pomocniczego

2. Złóżmy, ze umiemy przenieść n-1 krążków z miejsca A na B korzystając z C. Wtedy przenosimy n-1 z A na B, następnie n-ty – największy krążek z A na C, a następnie przenosimy n-1 z B na C

3. Ale jak przenieść n-1 krążków? To proste: przenosimy n-2 z A na C, następnie n-1-szy z A na B a potem n-1 z C na B. Zauważmy, że za każdym razem – największy krążek zawsze trafia na spód i nie przeszkadza w manipulacjach.

4. Operacje przeprowadzamy tak długo, aż będziemy musieli przenieść jedne krążek – a to potrafimy zrobić.

Każdy problem daje się rozwiązać zarówno w sposób iteracyjny jak i rekurencyjny, ale zazwyczaj jedna z tych metod jest dla rozwiązania naturalna a inna – nie. Spróbujmy napisać rekurencyjną funkcję która zwróci ilość elementów znajdujących się na podanej liście:

(defun liczlen (a) (if (equal a nil) 0 (+ 1 (liczlen (cdr a) ) )

Page 36: Aby język giętki

))

Ciekawym ćwiczeniem byłoby napisanie funkcji która zwraca listę zawierającą stany stosów krążków w poszczególnych krokach.

Funkcje sterująceFunkcje sterujące warunkowym wykonaniem procesu wartościowania pozwalają na

wartościowanie pewnych form, w zależności od wartości innych. Na początku nauki programowania w Lispie – zapomnijmy o innych ciekawych sposobach na zmianę metody wykonywania programu poza formami warunkowymi.

Jedną formę warunkową jaką poznaliśmy jest forma if. Innymi formami pozwalającymi na wartościowanie warunkowe są formy and oraz or. Zauważmy, że formy te wartościują swoje argumenty: and – tak długo jak długo formy są różne od nil. W przypadku or – dopóki są równe nil.

Inne formy dają się wyrazić przez formę if, ale ich użycie może znacznie uprościć program.

whenForma when ma postać:

(when warunek forma1 forma2 ...)

Pozwala ona na wartościowanie ciągu form jeśli spełniony jest warunek. Funkcja wymaga podania co najmniej dwóch parametrów – warunku który jest sprawdzany, oraz formy której wartość będzie zwrócona jeśli warunek jest spełniony. Jeśli nie jest spełniony – zwracana jest wartość nil. Jeśli podano więcej niż dwa parametry, kolejne są wartościowane sekwencyjnie a wynikiem jest wartość ostatniej formy. Funkcja ta nie powinna być używana gdy zwracana wartość jest potrzebna. W takim wypadku bezpieczniej jest używać funkcji if.

Funkcja when może być zapisana przy użyciu form if oraz prog:

(defmacro when (a &rest c)(if a (prog c)

nil)

)

unlessFunkcja unless ma postać:

(unless warunek forma1 forma2 ...)

Forma ta wartościuje formy będące argumentami, jeśli warunek nie jest spełniony. W zasadzie jest to po prostu zaprzeczenie funkcji when.

Page 37: Aby język giętki

condForma cond pozwala na implementowanie nawet bardzo skomplikowanych warunków.

Forma powala na sprawdzenie wielu warunków, przy czym spełnienie pierwszego z nich – kończy wartościowanie formy. Konstrukcja taka znana jest z C++ jako if ... else if ... else if ... .

Cond ma postać:

(cond (warunek1 forma11 forma12 ...) (warunek2 forma21 forma22 ...) ... )

Forma wartościuje warunek i jeśli jego wartość jest różna od nil – wartościowane są formy następujące po tym warunku i wartościowanie formy się kończy. Jeśli warunek nie jest spełniony – sprawdzany jest kolejny warunek. Cond można zapisać przez funkcję if:

(defmacro cond (l &rest r) (if (car l)

(prog (cdr l)(cond (car r) (cdr r)

))

Natomiast funkcję if można zapisać przez cond:

(defmacro if (w then else)(cond (w then)

(t else))

)

UWAGA: Forma cond nie jest określona (jej działanie kończy się błędem) jeśli żaden warunek nie będzie spełniony. Dlatego dobrze kończyć listę list warunków listą (t nil), zwracającą nil – gdy żaden warunek nie jest spełniony.

caseNajbardziej skomplikowaną instrukcją warunkową jest forma case. Przypomina ona

instrukcję case z języka C++, czy też innych języków w których programujemy proceduralnie. Case ma formę:

(case wyrazenie(listaWartości1 forma11 forma12 ...)(listaWartości2 forma21 forma22 ...)...

)

Page 38: Aby język giętki

Przy wartościowaniu tej formy, wartościowane jest wyrażenie, którego wartość jest porównywana z wartościami znajdującymi się na liście wartości. Elementy tej listy nie są wartościowane – ale traktowane tka jakby były umieszczone w formie quote. Jeśli lista wartości zawiera jeden element – nie musi być on zapisany jako lista jednoargumentowa, ale jako pojedyncza dana. Jeśli porównanie wypadnie pozytywnie – to wartościowane są wszystkie formy na liście które jest stowarzyszona z porównywaną wartością.

a := 2(case a ((1 2 3) (* 2 a)) (4 12)) -> 4

doForma do pozwala na wykonywanie pętli. Funkcjonalnie odpowiada ona instrukcji for z

języka C++, ale pozwala na użycie wielu zmiennych kontrolnych. Forma ta ma postać:

(do ( (zmienna1 wartoscPoczątkowa1 krok1)(zmienna2 wartoscPoczątkowa2 krok2)... )

(warunek) forma1 forma2

...)

Formy forma1, forma2, ... wykonywane są w pętli, przy zmiennych zadeklarowanych na początku pętli zmieniających się od zadanych wartości początkowych, tak długo jak długo warunek jest spełniony. Po każdym obiegu pętli, wartość zmiennej jest przeliczana przez wyrażenie określone tu jako krok.

Jeśli zadeklarowano więcej niż jedną zmienną – wszystkie zmienne są przeliczane w pojedynczym kroku pętli. Nie jest to więc pętla wielokrotna czy zagnieżdżona.

Sekwencyjne wykonywanie instrukcjiLisp to listy, oraz superpozycje funkcji. Ale niestety programiści przyzwyczajają się do

sekwencyjnego wykonywania instrukcji i bardzo trudno im to wybić z głowy. Matematyczna konstrukcja wyrażeń w języku Lisp, jest bardzo elegancka, ale przydałaby się możliwość pisania programów w sposób tradycyjny – jako ciągu instrukcji wykonywanych jedna po drugiej.

prog1, prog2, prognNajprostszymi formami które pozawalają na sekwencyjne wartościowanie form są formy

prog1, prog2, i progn. W konstrukcji:

(progn forma1 forma2 forma3 ... formai)

Formy wartościowane są jedna po drugiej a ich wartości są zapominane, gdy tylko kolejna forma będzie wartościowana. Formę taką łatwo napisać jako makrodefinicję:

Page 39: Aby język giętki

(defmacro progn (&rest l) (prognx nil l ) )(defun prognx (result rest)

(if rest(prognx (eval (car rest)) (cdr rest))result

))

Konstrukcja wydaje się oczywista. Do napisanie funkcji, posłużyliśmy się jedna funkcją pośrednią – która wylicza wartościuje poszczególne formy, przygotowując wynik do zwrócenia – przekazując go do kolejnej rekurencji. Proste i po Lispowemu.

Funkcja prog1 zachowuje się tak samo jak progn, ale zwraca nie wynik ostatniego, ale pierwszego wartościowania. Wszystkie formy są wartościowane, ale zwracana jest wartość pierwszej z nich. Podobnie makro prog2 zwraca wartość drugiej formy z listy argumentów. Obie makrodefinicje łatwo napisać używając zmiennych tymczasowych, ale jak to zrobić korzystając wyłącznie z superpozycji funkcji? Podobnie jak w poprzednim przypadku użyjemy funkcji pośredniej dla której przygotowujemy parametry we właściwej funkcji. Konstrukcja jest to trochę zakręcona, ale myślę, że nie czytelnik nie powinien mieć dużych problemów z jej rozszyfrowaniem:

(defmacro prog1 (optional& (a nil) &rest l) (prog1x a nil l) )(defun prog1x (wynik x rest)

(if rest (prog1x wynik (eval (car rest)) (cdr

rest))wynik

))

(defmacro prog2 (&optional (a nil) (b nil) &rest l)(progn a (prog1x b nil l))

)

letForma let pozwala na określanie zmiennych lokalnych dla ciągu form wartościowanych

sekwencyjnie. Powoli Lisp zaczyna wyglądać jak C++ pozwalając na definiowanie form wyglądających jak typowy zakres „{ ... }”. Forma let ma postać:

(let ( (zmienna1 wartość1)(zmienna2 wartość2)...)forma1forma2...

)

Page 40: Aby język giętki

Forma ta zachowuje się tak samo jak forma progn jeśli chodzi o wartościowanie form składających się na jej ciało. Przedtem jednak wartościowane są wszystkie formy określające wartości, a następnie są przypisywane do zmiennych o zadeklarowanych nazwach. Zmienne te są lokalne w formie let – to znaczy, że po wyjściu z tej formy – wszystkie te zmienne są niszczone a pamięć 0 zwalniana.

O ile wartościowanie form tworzących ciało formy let – są wartościowane po kolei – o tyle wartości zmiennych – nie musza być wartościowane kolejno a ponadto, wartości same zmienne nie są dostępne zanim nie zakończy się definiowanie wszystkich zmiennych lokalnych.

Aby móc obliczać wartości definiowane lokalnie – należy użyć funkcji let*. Ma ona taką samą składnię, ale wszystkie zmienne są deklarowane sekwencyjnie – jedno po drugim.

Jeśli pominiemy wartość przy deklarowaniu zmiennej – to zmienna jest inicjowana z wartością nil. Pamiętajmy: w języku Lisp nie ma takiego pojęcia jak zmienne która nie ma wartości. Albo zmienna nie jest zadeklarowana, czyli nie może być wartościowana, albo ma wartość.

Jak zmieniać wartości zmiennych – dowiemy się przy okazji omawiania innych ciekawych funkcji.

tagbodyForma tagbody pozwala na sekwencyjne przetwarzanie jej form składowych, podobnie jak

formy prog1, prog2 i progn, ale zawsze zwraca wartość nil. Ponadto wewnątrz formy tagbody mogą występować, poza formami do wartościowania – także etykiety określające miejsca do których można skoczyć instrukcją (formą) go. Etykiety nie są w żaden konkretny sposób oznaczone nie ma tu deklaracji etykiet czy wyróżniania ich jakimś specjalnym znakiem (jak np. w C++ - etykiety zawsze kończą się dwukropkiem).

Ty zasada jest prosta – jeśli element formy tagbody jest listą – to jest wartościowany. Jeśli jest atomem – to jest traktowany jak etykieta.

Instrukcja skoku ma format:

(go etykieta)

Forma tagbody ma postać:

(tagbodyforma1forma2...etykieta1...formai...etykietan...

)

Page 41: Aby język giętki

progForma prog jest połączeniem form let i tagbody. Jej składnie jest taka sama jak formy let

jednak w ciele formy – można yzywać etykiet oraz instrukcji skoku. Podobnie jak w przypadku formy let zmienne lokalne nie są wartościowane i definiowane sekwencyjnie. Aby wymusić sekwencyjne definiowanie zmiennych lokalnych – należy użyć formy prog*.

Forma prog ma postać:

(porg ( (zmienna1 wartość1)(zmienna2 wartość2)...)forma1forma2...etykieta1...formai...etykietan...

)

Inne ciekawe funkcje implementacji LispLiczba funkcji konkretnej implementacji języka Lisp może być bardzo duża. Funkcje te

mają często po kilkanaście wariantów, lub pozwalają na dość dziwne specyfikacje argumentów jako funkcji, słów kluczowych itp. Poniżej chciałbym pokazać kilka-kilkanaście użytecznych funkcji jakie możemy znaleźć w większości implementacji.

W przykładach założymy, że:a := (1 2 3)b := ‘Bc := ()d := ‘(x y z)e := (30 . (1 . 1) )f := (1 2 3 4 5 6 7 8 9 0)

Posłużymy się tu także z nieco bardziej złożonym przekazywanie argumentów. Oprócz argumentów które są wartościowane oraz takich które są traktowane dosłownie (występuje przed nimi pojedynczy apostrof, lub są argumentem formy quote) – poznamy jeszcze jedną – wartościowanie nazwy funkcji. Normalnie jeśli podamy nazwę punkcji – interpreter będzie próbował ją obliczyć. A co zrobić jeśli chcemy przekazać, jako argument, nie wartość funkcji – ale samą funkcję? W takim wypadku użyjemy znaku # - by określić, że chodzi nam o wartość która jest ciałem funkcji o podanej nazwie.

Konstrukcja #’funkcja – oznacza po prostu ciało funkcji której nazwą jest ‘funkcja’.Po tych wyjaśnieniach zobaczmy kilka przykładów funkcji:

Page 42: Aby język giętki

Funkcje testująceFunkcje testujące mają nazwy kończące się literą „p”. Początek nazwy funkcji – określa co

jest sprawdzane. (endp dana) – funkcja zwraca wartość t jeśli dana przekazana jako argument jest pustą

listą, oraz nil jeśli jest jakąkolwiek daną różną od pustej listy. Funkcja bardzo przydatna gdy chcemy sprawdzić, czy zakończyliśmy już przeglądanie listy dochodząc do jej końca. Można w tym celu używać także funkcji (if ( and (atom dana) (eq dana nil)) t nil), ale (endp dana) wygląda nieco prościej.

(endp a) -> nil(endp b) -> nil(endp c) -> t

(consp dana) – funkcja sprawdzająca czy wartość podana jako dana jest parą. Jeśli nie wprowadzamy do naszych programów innych typów danych niż atomy i pary5 - to consp jest równoważne (not (atom ... )).

(consp a) -> t(consp b) -> nil(consp c) -> nil

(listp dana) – funkcja sprawdzająca czy dana jest listą. To znaczy – czy jest ciągiem par w którym ostatnim elementem jest para której drugim elementem jest nil. Ponadto funkcja ta zwraca t przy przekazaniu atomu nil jako argumentu. Pamiętajmy że atom nil odpowiada liście pustej.

(listp a) -> t(listp b) -> nil(listp c) -> t(listp e) -> nil

(zerop dana) – funkcja sprawdzająca, czy podany argument ma wartość zero – bez względu na jego typ. Argument może być liczbą całkowitą, zmiennopozycyjną, ułamkową a nawet – zespoloną. Jeśli liczba to ma wartość zero – to wynikiem działania funkcji jest t, jeśli jest niezerowa – nil.

(zerop 0/12) -> t(zerop 0.00001) -> nil

(numberp dana) (integerp dana) (rationalp dana) (floatp dana) (realp dana) – funkcje sprawdzające typ podanego argumentu. Funkcje te zwracają wartość t jeśli argument jest odpowiednio typu – liczbowego (dowolnego podtypu typu liczbowego), całkowitego, ułamkowego, zmiennopozycyjnego i rzeczywistego.

(numberp ‘a) -> nil(integerp 123) -> t(rationalp 10) -> nil

5 Pamiętajmy, że lista jest również parą. Para wartości nie musi koniecznie oznaczać pary atomów.

Page 43: Aby język giętki

(plusp dana) (minusp dana) (oddp dana) (evenp dana) – funkcje sprawdzające czy przekazana dana jest odpowiednio dodatnia, ujemna, nieparzysta lub parzysta. Dwie ostatnie funkcje wymagają podania argumentu całkowitego.

(oddp 12) -> nil(evenp 12356) -> t(plusp 23) -> t

Funkcje operujące na listachJęzyk Lisp oferuje bardzo dużo funkcji pracujących na listach. Tu przedstawimy kilka

najbardziej popularnych: (list-length lista) – funkcja zwraca długość listy – ilość jej

elementów.(list-length ‘(a b c (d e) f)) -> 5

(nth numer lista) – funkcja zwracająca n-ty element podanej listy. Funkcja może służyć do swobodnego dostępu do listy, pozwalając na traktowanie jej jak normalnej tablicy. Funkcję taką bardzo łatwo zaimplementować samodzielnie, jednak jej wbudowana wersja jest znacznie bardziej efektywna.

(nth 2 b) -> Y(nth 1 b) -> X

(first lista) – funkcja zwraca pierwszy element listy – działa tak samo jak car

(rest lista) – funkcja zwraca ogon listy – działając tak samo jak cdr, od której różni się nieco więcej znaczącą, i łatwiejszą do zapamiętania nazwą.

(nthcdr numer lista) – funkcja jest wielokrotnością funkcji cdr. (nthcdr 1 f) -> (2 3 4 5 6 7 8 9 0)(nthcdr 5 f) -> (6 7 8 9 0)

(last lista &optional (numer 1)) – funkcja zwracająca ostatni element lub elementy podanej listy. Jeśli określimy wyłącznie listę – to zwrócony zostanie ostatni element listy. Jeśli dodatkowo podamy liczbę – funkcja zwróci zadaną ilość elementów z końca listy.

(last f) -> 0(last f 3) -> (8 9 0)

(list &rest elementy) - funkcja zwraca listę złożoną z wszystkich argumentów przekazanych do funkcji

(list a b c) -> ( (1 2 3) B () ) (concatenate &rest elementy) - funkcja zwraca listę która jest połączeniem wszystkich

list i atomów podanych jako argumenty tej funkcji, sklejając je w jedną listę.(concatenate a b c) -> (1 2 3 B)

Page 44: Aby język giętki

(butlast lista &optional numer) – funkcja zwraca początek listy – listę nie zawierającą ostatniego, lub ostatnich elementów listy.

(butlast f) -> (1 2 3 4 5 6 7 8 9)(butlast f 4) -> (1 2 3 4 5 6)

Modyfikacja wartości (setq nazwa wartosc) – funkcja ustawiająca wartość zmiennej na

liście asocjacji. Funkcja ta modyfikuje lub tworzy wartość globalną, dostępną z każdego miejsca programu. Nazwa zmiennej nie jest wartościowana, zachowuje się ona jak zapisana wewnątrz formy quote. Nazwa określa nazwę zmiennej, wartość – jest dowolną formą o dowolnej wartości dowolnego typu. Jeśli wartość jest wyrażeniem – to jest ono obliczane w momencie przypisywania. Jeśli nie chcemy wartościowania – musimy zamknąć wartość wewnątrz formy quote.

(setq x 12) x -> 12(setq x ‘(a b c)) x -> (A B C)(setq x f) x -> (1 2 3 4 5 6 7 8 9 0)

(rplaca x y) – funkcja ta, podobnie jak funkcja replacd, może być użyta do modyfikacji istniejących struktur listowych. Funkcja ta wymienia w istniejącej liście głowę listy na swój drugi argument.

(setq x ‘(a b c)) x -> (A B C)(rplaca x 12) x -> (12 B C)

(rpalcd x y) – funkcja pozwalająca na wymianę ogona podanej listy na wartość podaną jako drugi argument.

(rplacd x ‘V) x -> (12 V)

Operacje matematyczne (floor num) (ceiling num) (truncate num) (round num) – funkcje konwertujące liczby

do liczb całkowitych zwracające odpowiednio: największą liczbę całkowitą, nie większą nie niż podana; najmniejszą nie mniejszą, liczbę po obcięciu części ułamkowej oraz zaokrągloną do najbliższej liczby całkowitej.

num Floor ceiling truncate

round

2.6 2 3 2 32.5 2 3 2 22.4 2 3 2 20.7 0 1 0 10.3 0 1 0 0-0.3 -1 0 0 0

Page 45: Aby język giętki

-0.7 -1 0 0 -1-2.4 -3 -2 -2 -2-2.5 -3 -2 -2 -2-2.6 -3 -2 -2 -3

(abs x) – wartość bezwzględna liczby. Działa także dla argumentów zespolonych.

(phase z) – faza liczby zespolonej. Dla liczb rzeczywistych - 0

(signum x) – znak liczby. Ma sens tylko dla liczb rzeczywistych.

(sin phi) – sinus kąta (cos phi) – kosinus kąta (tan phi) – tangens kąta (cis phi) – cosinus i sinus tego samego kąta zapisany jako

liczba zespolona której część rzeczywista ma wartość kosinusa, a urojona - sinusa

(asin x) – arkus sinus – wartość kąta którego sinus wynosi x. Funkcja odwrotna do funkcji sinus.

(acos x) – arkus kosinus – funkcja odwrotna do funkcji sinus.

(atan y &optional x) – arkus tangens. Funkcja odwrotna do tangensa. Jeśli podano jeden argument – to wynik jest wartością funkcji arkus tangens tej wartości. Jeśli podano dwa – to ich ilorazu.

(sinh n) (cosh n) (tanh n) – sinus, kosinus i tangens hiperboliczny

(asinh x) (acosh x) (atanh x) – funkcje odwrotne do funkcji hiperbolicznych

(exp x) – funkcja ekspotencjalna ex. (expt x y) – funkcja ekspotencjalna xy. (log num &optional podstawa) – logarytm o zadanej

podstawie. Jeśli nie określono podstawy – logarytm jest logarytmem naturalnym.

(sqrt x) – pierwiastek kwadratowy z zadanej liczby. Wynik może być zespolony o ile to konieczne.

(isqrt x) – całkowity pierwiastek z zadanej liczby. Można oczywiście policzyć (floor (sqr x)), ale isqr jest znacznie szybsze.

Inne operacje na listach (subst wstaw znajdz lista &key test test-not) – funkcja zamieniająca

wszystkie wystąpienia wskazanego elementu znajdz na element

Page 46: Aby język giętki

wstaw w zadanej liście. Lista jest przeszukiwana jak drzewo – jeśli zawiera podlisty – to są one również przeszukiwane. Dodatkowe parametry kluczowe test oraz test-not pozwalają na zadanie funkcji użytej do porównywania.

(subst 1 A ‘(A B (1 2 A C) A)) -> (1 B (1 2 1 C) 1) (adjoin element lista &key test test-not) – funkcja dodająca element do

listy pod warunkiem że element nie występuje na liście. Parametry kluczowe pozwalają na określenie funkcji służącej do porównywania.

(adjoin 1 ‘(1 2 3)) -> (1 2 3)(adjoin 1 ‘(5 6 7)) -> (5 6 7 1)

(member element lista &key test test-not) – funkcja sprawdzająca czy element występuje na podanej liście

(member 1 ‘(1 2 3)) -> t(member 1 ‘(5 6 7)) -> nil

(ldif lista podlista) – funkcja zwracająca różnicę list, jednak podlista musi być rzeczywistą podlistą a nie jedynie wyrażeniem z nią identycznym – podlista musi być którymś z cdr-ów listy z której będziemy ją odcinać.

g := (cdddr f)h := (4 5 6 7 8 9 0) ; identyczna wartość jak g(ldif f g) -> (1 2 3)(ldif f h) -> (12 3 4 5 6 7 8 9 0)

(union lista1 lista2) – funkcja zwracająca wszystkie elementy znajdujące się na liście1 i liście2. Elementy powtarzające się występują na liście wynikowej tylko raz. Funkcja ta zachowuje się jak wielokrotne wywołanie funkcji adjoin. Również i tu można określić parametry kluczowe test i test-not.

(union ‘(1 2 3 4) ‘(3 6 7 2)) -> (1 2 3 4 6 7) (intersection lista1 lista2) – funkcja zwracająca elementy

występujące na obu listach podanych jako argumenty:(intersection f ‘(4 6 8 12 443 2)) -> (2 4 6 8)

(fill lista wartosc &key start end) – funkcja pozwalająca na wypełnienie listy lub jej części zadaną wartością. Jeśli nie podano wartości start i end – wypełniana jest cała lista Jeśli określono początek i koniec – wypełniane są tylko elementy o indeksach większych lub równych wartości początku i mniejszych od wartości końcowej. Funkcja ta zmienia listę przekazaną jej jako parametr. Elementy są numerowane od zera. Funkcja może działać na listach i tablicach.

(fill f ‘x)x -> (X X X X X X X X X X)(fill f ‘a :start 2 : end 3)x -> (X X A X X X X X X X)

Page 47: Aby język giętki

(replace zmienna wyrazenie &key start1 end1 start2 end2) – funkcja modyfikująca zmienną poprzez wstawienie w nią elementów podanego wyrażenia. Jeśli nie określono wartości początkowych i końcowych – to działa podobnie jak setq – podstawiając do zmiennej podane wyrażenie. Jeśli określimy w jakie miejsce należy kopiować – to wymieniony zostanie podany ciąg elementów. Jeśli podano jaki fragment wyrażenia należy przepisać – funkcja pozwoli na podstawienia pod wycinek listy – wycinka innej listy. Podobnie jak w funkcji fill – wartości początku i końca określają element początkowy oraz element za końcowym. Elementy listy numerowane są od zera.

(replace f ‘(A B C D))f -> (A B C D)(replace f ‘(1 2 3) :start1 0 :end1 2)f -> (1 2 3 C D)

(remove element lista &key from-end, test test-not start end count) – funkcja pozwalająca na usuwanie elementów z listy. Przy wywołaniu funkcji możemy określić elementy jakiej wartości będą usuwane z listy. Poza określeniem wartości elementu o listy z której będzie wycinany, możemy określić dodatkowe opcje pozwalające na budowanie naprawdę złożonych poleceń. count – pozwala na określenie ile elementów może zostać

usuniętych. from-end – działa wyłącznie z count pozwalając na wycięcie określonej liczby

wystąpień elementu – ale poszukiwanie rozpoczyna się od końca listy, jeśli wartość tego parametru jest różna od nil.

test oraz test-not – pozwalają na zadanie warunku jaki mają spełniać elementy do usunięcia. Jeśli podamy przy wywołaniu funkcji remove wartość :test #’> - wszystkie elementy większe od zadanego – zostaną usunięte. Oczywiście z uwzględnieniem innych parametrów kluczowych. W przypadku test-not – elementy nie spełniające warunku będą usuwane. Jako warunek można podać nazwę dowolnej funkcji, ważne jest by przyjmowała dwa argumenty.

start, end – pozwalają na określenie w jakiej części listy elementy będą usuwane.(remove 2 f) -> (1 3 4 5 6 7 8 9 0)(remove 3 f :test #’<) -> (3 4 5 6 7 8 9 0)

Inne funkcje (equal a b) – funkcja sprawdzająca czy podane parametry są

równe. W odróżnieniu od funkcji eq oraz = powala ona na sprawdzanie dowolnie rozbudowanych struktur danych.

(equal e (cons 30 ‘(1 . 1) )) -> t (find element lista &key from-end test test-not start end) – Funkcja

znajdująca element na podanej liście i zwracająca go jako wartość którą możemy modyfikować. Zwracane jest pierwsze

Page 48: Aby język giętki

wystąpienie (lub ostatnie jeśli from-end na wartość różną od nil) elementu na liście. Parametry kluczowe mają takie same znaczenie jak w funkcji remove. Jeśli nie znaleziono elementu – zwracana jest wartość nil.

(find 3 f) -> 3 (mapcar funkcja lista &rest listy) – funkcja pozwalająca na

obliczenie podanej funkcji dla listy argumentów. Wartością funkcji jest lista wartości funkcji. Jako parametry podajemy funkcję której wartości mają być liczone oraz tyle list, ile funkcja przyjmuje parametrów. Argumenty funkcji brane są jako kolejne elementy list:

(mapcar #’+ ‘(1 2 3 4 5) (3 3 2 1 6)) -> (4 5 5 5 11) (apply funkcja &rest argumenty) – funkcja która liczy

wartość funkcji której nazwa jest podana jako pierwszy parametr funkcji apply, na argumentach podanych jako reszta argumentów. Działa podobnie jak funkcja eval, jednak eval nie wartościuje nazwy funkcji. W funkcji apply nazwa funkcji może być zapisana w zmiennej.

x := ‘>=(apply x 90 10 12) -> nil

(sort lista funkcja &key key) – funkcja sortująca listę. Ponieważ lista może mieć dowolną strukturę i funkcje porównujące dla różnych list mogą różnie wyglądać – jako drugi parametr podano funkcję porównującą elementy, która powinna zwracać wartość nil jeśli dwa elementy, przekazywane jej jako parametry nie są ułożone w dobrej kolejności. Ostatni parametr określa nazwę funkcji która pozwala na wyciągnięcie kluczy do porównywania z elementów listy. Jeśli nie jest ona podana – to jako elementy listy traktowane są jak klucze.

(sort ‘(4 5 6 2 9) #’<) -> (2 4 5 6 9) (string<= a b) (string= a b) (string< a b) (string/= a b)

(string>= a b) (string> a b) – operacje porównywania tekstów. Działają podobnie jak relacje <, <=, =, /=, >=. >, ale pozwalają na porównywanie napisów. Przyjmują jednak tylko dwa argumenty.

(string/= ”asd” ”xx”) -> t(string< ”asd” ”xx”) -> t(string> ”asd” ”xx”) -> nil

Inne typy danych implementacji LispW czystym Lispie mamy do dyspozycji jedynie dane atomowe oraz pary. W

nowoczesnych implementacjach Lispa – dodatkowe typy danych pozwalają na optymalizację obliczeń a nawet na włączenia typizacji do tego języka. Dowolną zmienną możemy zadeklarować zastępując ją listą złożoną z nazwy typu oraz nazwy zmiennej.

Poza typami podstawowymi możemy korzystać z

Page 49: Aby język giętki

Liczby – Lisp oferuje cztery różne typy liczbowe: Liczby całkowite – zapisane jako ciąg cyfr rozpoczynającymi się ewentualnie

znakiem plus lub minus Ułamki – dwie liczby całkowite rozdzielone znakiem łamania. Druga z nich

powinna być bez znaku. Lisp potrafi traktować takie liczby jak dokładne wartości. Wartości zmiennopozycyjne – zapisane z kropką i ewentualnym wykładnikiem po

literze e. Liczby zespolone – zapisane jako #C(r,i), gdzie r, i są liczbami

zmiennopozycyjnymi Tablice wartości. Zapisywane są podobnie jak listy – ale ich użycie jest znacznie

bardziej efektywne jeśli chodzi o dostęp swobodny. Aby określić daną jako tablicę – należy nawiasy poprzedzić znakiem #: #(1 2 3)

Napisy – zachowują się podobnie jak dane atomowe, ale trudno z napisu zrobić nazwę zmiennej. Napisem jest dowolny ciąg znaków ujęty w cudzysłowy. Np. ”To jest napis”.

Tablice bitowe. Jeśli poprzedzimy ciąg zer i jedynej znakami #* - to mamy wektor bitów.

Tablice mieszające – pozwalają na budowę szybkich i efektywnych tablic symboli. StrukturyStruktury są budowane przez odpowiednie makra które tworzą funkcje dostępu do

poszczególnych elementów oraz cos w rodzaju konstruktora. Ale w głębi kodu – nadal jest to lista...

Ewaluator – czyli Lisp w LispieJako jedno z ćwiczeń proponowałbym napisanie interpretera języka Lisp w Lispie. Może

sprawiać to wrażenie masła maślanego, ale czym innym jest wykonywanie prostych operacji na listach, a czym innym wartościowanie form zawierających zmienne oraz obliczanie funkcji zdefiniowanych przez użytkownika, z przekazywaniem parametrów, parametrami lokalnymi itp.

Funkcja wartościująca musi działać w pewnym środowisku, w którym zdefiniowano wartości zmiennych. Wartością zmiennej może być także funkcja. Tu założymy, że pierwszy element listy będącej poleceniem jest funkcją. A jeśli nie jest funkcją wbudowaną – to jest funkcją zdefiniowaną przez użytkownika, a więc jej definicja znajduje się na liście asocjacji.

Na początek jednak – powinniśmy rozróżnić wartościowanie zmiennej od obliczania wyrażenia. Wyrażenie jest listą, zmienna – atomem. Napiszmy więc funkcję wartościującą, która wywoła wartościowanie zmiennej lub wyrażenia. Zauważmy również, że atomy t i nil są swoją własną wartością:

; --------------------------------------------; funkcja wartościująca podane wyrażenie ; w.g. wartości zapisanych w słoniku; --------------------------------------------(defun rozwiaz (forma slownik) (if (atom forma)

Page 50: Aby język giętki

(cond ((eq forma 't) t) ((eq forma 'nil) nil) ( t (zmienna forma slownik) )) (wyrazenie (car forma) (cdr forma) slownik) ))

Funkcja ta dla argumentu będącego atomem – sprawdza czy nie jest to atom t lub nil, jeśli tak – od razu zwraca wartość. Jeśli jest innym atomem – woła funkcję poszukującą wartości zmiennej w słowniku. Jeśli podana forma jest listą – wołamy wartościowanie wyrażenia, ale od razu odcinamy głowę listy – która jest traktowana jako symbol operacji.

Wartościowanie zmiennych jest bardzo proste. Sprawdzamy po prostu czy głowa listy asocjacji (słownika) odpowiada poszukiwanej zmiennej. Jeśli tak – zwracamy jej wartość. Jeśli nie – wołamy funkcję tą samą funkcję dla słownika pozbawionego pierwszego elementu:

; --------------------------------------------; funkcja poszukująca w słowniku ; wartości zadanej zmiennej; --------------------------------------------(defun zmienna (nazwa slownik) (if (eq nazwa (caar slownik)) (cadar slownik) (zmienna nazwa (cdr slownik)) ))

Teraz musimy napisać interpretację wyrażeń. Dla prostych funkcji wbudowanych – nie ma nic prostszego – wartościujemy odpowiednie6 elementy listy argumentów. Problemem są jedynie funkcja cons oraz funkcje definiowane przez użytkownika.

; --------------------------------------------; funkcja wartościująca podane wyrażenie; wszystkie funkcje wbudowane są tu liczone; explicite; --------------------------------------------(defun wyrazenie (operacja argumenty slownik) (cond ( (eq operacja 'quote) (car argumenty) ) ( (eq operacja 'car) (car (rozwiaz (car argumenty) slownik) ) ) ( (eq operacja 'cdr) (cdr (rozwiaz (car argumenty) slownik) ) ) ( (eq operacja 'atom)

6 To znaczy takie których te funkcje się spodziewają. Nie będziemy się tu bawić w kontrolę poprawności danych.

Page 51: Aby język giętki

(atom (rozwiaz (car argumenty) slownik) ) ) ( (eq operacja 'cons) (cons (rozwiaz (car argumenty) slownik) (rozwiaz (cadr argumenty) slownik) ) ) ( (eq operacja 'eq) (eq (rozwiaz (car argumenty) slownik) (rozwiaz (cadr argumenty) slownik) ) ) ( (eq operacja 'cond) (warunkowe argumenty slownik) ) ( t (wolajfunkcje (zmienna operacja slownik) argumenty slownik) ) ))

Zacznijmy od wartościowania wyrażenia warunkowego. Również tu łatwo poradzimy sobie korzystając z prostej rekurencji. Jeśli pierwszy element głowy listy argumentów ma wartość inna niż nil, zwracamy drugą wartość. Jeśli pierwszy element ma wartość nil – wartościujemy formę dla reszty elementów listy warunkowej:

; --------------------------------------------; pomocnicza funkcja pozwalająca na warunkowe; wartościowanie wyrażeń; --------------------------------------------(defun warunkowe (argumenty slownik) (cond (rozwiaz (caar argumenty) slownik) (rozwiaz (cadar argumenty) slownik) ( t (warunkowe (cdr argumenty) slownik) ) ))

Pozostaje najtrudniejsza część: wartościowanie funkcji zdefiniowanych na liście asocjacji. Dla uproszczenia nie będziemy się zajmowali makrodefinicjami, zakładając, że wystarczą nam funkcje. Myślę, że po chwili zastanowienia, uważny czytelnik będzie w stanie przerobić nasze funkcje tak – by działały wywoływania makrodefinicji.

Jeśli funkcję zapiszemy jako listę zawierającą dwa elementy:1. Listę nazw argumentów2. Formę która policzy wartość funkcjiWystarczyłoby więc policzyć formę będącą ciałem funkcji, ale przy nieco zmienionej

liście asocjacji – takiej do której doklejono asocjacje nazw argumentów i ich wartości przekazane do funkcji. Jeśli argumenty te dodamy na początek listy asocjacji – to przy okazji zaimplementujemy coś takiego jak przykrywanie nazw.

Wystarczy więc policzyć formę funkcji:

; --------------------------------------------

Page 52: Aby język giętki

; funkcja pomocnicza wartościująca funkcje ; wartościuje ciało funkcji przy użyciu słownika; rozszerzonego o parametry funkcji; --------------------------------------------(defun wolajfunkcje (funkcja argumenty slownik) (rozwiaz (cadr funkcja) (liczargumenty (car funkcja) argumenty slownik slownik) ))

Pozostaje nam tylko napisanie, wspomnianej w powyższej funkcji, funkcji liczargumenty która rozbuduje słownik. Funkcję tą bardzo łatwo napisać: wystarczy dodawać do słownika kolejno pary złożone z odcinanych głów list – argumentów i ich wartości. Gdybyśmy jednak operowali na rozbudowywanym słowniku – moglibyśmy przykryć wartości na podstawie których wartości te są liczone. Dlatego będziemy przekazywali słownik rozbudowywany – ten który na końcu zwrócimy, oraz oryginalny, używany do wartościowania argumentów:

; --------------------------------------------; funkcja rozbudowująca słownik o nazwy z listy; 'argumenty' o wartościach z listy 'wartości'; --------------------------------------------(defun liczargumenty (argumenty wartosci slownik orgslownik) (if (atom argumenty) slownik (liczargumenty (cdr argumenty) (cdr wartosci) (dodajwartosc (car argumenty) (rozwiaz (car wartosci) orgslownik) slownik ) orgslownik ) ))

Jeszcze tylko malutka funkcja rozszerzająca słownik o podaną wartość i całość powinna działać:

; --------------------------------------------; funkcja rozbudowująca listę asocjacji o; podaną asocjację nazwy z wartością; --------------------------------------------(defun dodajwartosc (nazwa wartosc slownik)

Page 53: Aby język giętki

(cons (cons nazwa (cons wartosc nil) ) slownik))

Przetestujmy teraz nasze funkcje na prostej liście asocjacji zawierającej tylko dwie wartości oraz jedną funkcję. Jedna z wartości jest wartością prostą, druga – listą. Jako funkcję – pozwoliłem sobie wybrać funkcję if – pozwalającą na wybór wartości w zależności od wartości traktowanej jako warunek.

a := 10 b := (x 12 ((quote a) b c))) if := ( (a b c) (cond (a b) (t c) ) ) )

Na takiej liście asocjacji – policzymy wartości form, otrzymując:

(rozwiaz 'b asocjacje) -> (X 12 ((QUOTE A) B C))(rozwiaz '(quote x) asocjacje) -> X(rozwiaz '(cdr b) asocjacje) -> (12 ((QUOTE A) B C))(rozwiaz '(car b) asocjacje) -> X(rozwiaz '(atom b) asocjacje) -> NIL(rozwiaz '(atom a) asocjacje) -> T(rozwiaz '(cons a b) asocjacje) -> (10 X 12 ((QUOTE

A) B C))(rozwiaz '(cons b a) asocjacje) -> ((X 12 ((QUOTE A)

B C)) . 10)(rozwiaz '(eq a (quote 10)) asocjacje) -> T(rozwiaz '(eq a (quote 11)) asocjacje) -> NIL(rozwiaz '(cond (nil (quote www)) (t (quote xyz)) (t 1123456)) asocjacje) -> XYZ(rozwiaz 'if asocjacje) -> ( (a b c) (cond (a b) (t

c) ) ) )(rozwiaz '(if nil (quote a) (quote b)) asocjacje) ->

B(rozwiaz '(if t (quote a) (quote b)) asocjacje) -> A

Czyli nasza funkcja dział tak jak powinna.

Przykład programuJako przykład proponuję bardzo prosty problem – rozwiązanie równania kwadratowego

Ax2+Bx+C=0. Program napiszemy zgodnie z zaleceniami które mogą nas czegoś nauczyć:1. Nie piszemy programu jako sekwencji instrukcji – nie będziemy używali formy

prog – której należy unikać jak goto w C++.2. Nie będziemy używali formy var pozwalającej na tworzenie zmiennych

tymczasowych3. Nie będziemy tworzyli żadnych zmiennych globalnych.

Page 54: Aby język giętki

Czy daje się w ten sposób programować optymalny kod? Czy w ogóle daje się w ten sposób programować? Jeśli będziemy myśleć w C++ - będzie to trudne, ale jeśli się chwilę zastanowimy – okaże się, ze zamiast sekwencji instrukcji – można to samo zrobić poprzez superpozycję funkcji do których będziemy przekazywali parametry w postaci przetrawionej.

Po pierwsze – musimy sprawdzić czy nasze równanie jest na prawdę równaniem kwadratowym Jeśli nie – to może jest liniowe – może jest tożsamością a może jest sprzeczne... Przydatna okaże się możliwość zwracania przez funkcję różnych typów wartości. Jeśli równanie nie ma rozwiązań – zwracamy pustą listę, jeśli ma jedno rozwiązanie – zwracamy je, jeśli ma dwa rozwiązanie – zwracamy listę rozwiązań, a jeśli jest tożsamością – literę R – jako symbol zbioru liczb rzeczywistych.

W językach z kontrolą typu – nie byłoby to możliwe. Dla przypadku liniowego – rozwiązanie jest proste. Jeśli jednak jest to prawdziwe równanie kwadratowe – to nie jest już tak dobrze. Musimy wliczyć wyróżnik tego równania, i w zależności od jego znaku – policzyć pierwiastek, pierwiastki lub zwrócić pustą listę.

Zauważmy, że w liczeniu pierwiastków – potrzebne są nam nie współczynniki A, B i C – lecz 2A, -B oraz Delta=B2-4AC:

(defun trojmian (a b c) (if (= a 0) (if (= b 0) (if (= c 0) (quote R)

nil ) (/ (- c) b) ) (wyniki (* 2 a) (- b) (delta a b c) ) ))

(defun delta (a b c) (- (* b b) (* 4 a c)))

Pozostaje napisać funkcję wyniki która podejmie decyzję co do sposobu rozwiązywania i zwróci wynik. Zauważmy jednak, że albo musimy zapamiętać pierwiastek z wyróżnika równania, albo liczyć go dwukrotnie – co jest niepotrzebnie nieefektywne. Zamiast tego zapiszmy rozwiązanie w dwóch funkcjach – przekazując drugiej – już liczącej dwa pierwiastki – pierwiastek z wyróżnika a nie sam wyróżnik:

(defun wyniki (aa mb d) (if (< d 0) nil (if (= d 0) (/ mb aa) (dwapierwiastki aa mb (sqrt d) ) )

Page 55: Aby język giętki

))

(defun dwapierwiastki (aa mb sd) (list (/ (- mb sd) aa) (/ (+ mb sd) aa) ))

Proste? Tak też można. Maść na szczury!

Zadania1. Proszę napisać funkcję liczącą silnię i przetestować dla jakiej wartości będzie ona

działała.2. Proszę napisać funkcję która odwróci listę przekazaną jako parametr.3. Proszę napisać funkcję zwracającą n-ty wyraz ciągu Fibonacciego. Uwaga –

rozwiązanie czysto rekurencyjne jest niemiłosiernie nieefektywne.4. Proszę napisać program obliczający wyrażenie które jest pochodną wyrażenia

podanego jako parametr. Dla uproszczenia – zakładamy, że wyrażenia są zapisane w takim formacie jak w LISP-ie

Page 56: Aby język giętki

RedCodeRedCode jest językiem stworzonym na potrzeby gry „Core Wars” – w której gracze piszą

programy – zawodników które walczą ze sobą w pamięci komputera. Programy umieszczane są w losowych miejscach pamięci i nie wiedzą w jakim miejscu znajduje się przeciwnik. Gra polega na psuciu kodu przeciwnika przez wpisywanie mu bzdur, lub przebudowę jego programu tak, by odwoływał się do innych obszarów pamięci niż chciał tego przeciwnik – programista.

Język jakiego używamy do pisania swoich zawodników przypomina wczesne asemblery. Mamy tu proste operacje na liczbach całkowitych, instrukcje skoku, przypisania, kilka trybów adresowania oraz – co może zdziwić – a co czyni grę na prawdę fascynującą – instrukcja rozdzielnia programu na wątki.

Pisząc program musimy cały czas pamiętać o architekturze komputera w którym program będzie działał, przydzielać pamięć i używać konkretnych liczb i adresów. Tu całkowicie panujemy nad końcowym programem. Możemy zrobić wszystko – ale nie możemy się dziwić jeśli nasz program nie będzie działał tak jak byśmy chcieli. Kompilator może sprawdzić, czy nie pomyliliśmy się przy wprowadzaniu etykiety, albo kodu operacji, ale resztę – kontrolę typów, użycie pamięci oraz kontrolę nad tym co i jak robi nasz program – pozostawia programiście.

Myślę, że po napisaniu kilku niewielkich programów w języku RedCode – czytelnik nie będzie miał problemów w używaniu wskaźników i trochę lepiej zrozumie zasady funkcjonowania komputera.

Budowa instrukcjiInstrukcja w języku RedCode składa się z trzech pól:

Kod polecenia Pierwszy argument Drugi argument

Instrukcję zapisujemy jakoKOD arg1, arg2

Gdzie KOD jest kodem instrukcji – trzyznakowym skrótem oznaczającym operacje, natomiast arg1 i arg2 – są argumentami instrukcji. W niektórych przypadkach arg2 jest niepotrzebny i piszemy tylko

KOD arg1

Instrukcja może być poprzedzona etykietą:

etykieta: KOD arg1, arg2

Jeśli instrukcja nie wymaga podania argumentów – nie trzeba ich pisać w programie, jednak pamięć dla nich jest zawsze dostępna i może być używana do przechowywania

Page 57: Aby język giętki

danych. Jeśli modyfikujemy jakąś komórkę pamięci, lub pobieramy wartość komórki pamięci – to zawsze jest to pamięć przeznaczona na pierwszy argument.

Program operuje na wartościach z przedziału od 0 do 9999 (lub – jeśli są traktowane jako liczby ze znakiem z zakresu –5000 do 4999). Programy pracują w pamięci która ma tyle komórek ile można zaadresować – 10000. Wszystkie operacje wykonywane są modulo 10000.

Jedna instrukcja zawsze zajmuje jedną komórkę pamięci i jest wykonywana w jednym cyklu pracy komputera.

W grze „Core Wars” programu pracują 2 programy których instrukcje są wykonywane na przemian. Wątki każdego z graczy wykowywane są po jednej instrukcji.

Tryby adresowaniaW językach typu asembler – których instrukcje odpowiadają bezpośrednio instrukcjom

jakie wykonuje procesor, nie mamy różnych typów zmiennych – jak to ma miejsce w językach wyższego rzędu. To jak interpretujemy dane w pamięci – zależy od programu i aktualnie wykonywanej instrukcji. Mamy tu natomiast choć – co w językach wyższego rzędu nie występuje – lub coś czym w językach wyższego rzędu się nie wspomina – różne sposoby adresowania pamięci – czyli różne sposoby obliczania adresu pod jakim znajduje się dana w pamięci.

W podstawowej definicji języka mamy do dyspozycji 4 sposoby adresowania pamięci.1. Natychmiastowe – argument na którym działamy jest umieszczony w samej

instrukcji. Nie trzeba więc obliczać adresu komórki pamięci, tylko działać na danej umieszczonej w kodzie instrukcji. W C++ odpowiada takiemu sposobowi adresowania – wzięcie stałej. Tu jednak, ponieważ możemy modyfikować kod programu – pojęcie stałej nie istnieje. Możemy przecież wstawić wartość bezpośrednio w instrukcję. W języku RedCode wartość adresowaną natychmiastowo – poprzedza znak ‘#’ – wskazujący, że mamy użyć wartości a nie zawartości komórki i tym adresie.

2. Bezpośrednie – operacja będzie wykonana na komórce o wskazanym numerze. Ponieważ program powinien być przenośny i jego działanie nie powinno zależeć od miejsca w jakim został umieszczony, wszystkie komórki pamięci adresowane są względem bieżącego adresu. Ten typ adresowania określony jest samą liczba (bez dodatkowych znaków określających tryb adresowania), można również używać etykiet pisząc po prostu nazwę etykiety. Kompilator programu obliczy adres względny i umieści w kompilacje odpowiednią wartość.

3. Pośrednie – w instrukcji umieszczamy adres komórki z której bierzemy adres komórki o którą nam chodzi. O ile adresowanie bezpośrednie przypomina użycie zmiennych – to adresowanie pośrednie – jest typowym użyciem zmiennej wskaźnikowej. Ten sposób adresowania pozwala na obliczanie adresu lub ogólniej zmianę adresu pod którym będziemy czytać lub pisać pamięć, beż konieczności modyfikacji kodu programu w czasie jego wykonywania. Adresowanie pośrednie oznaczamy poprzez dodanie znaku ‘@’ przed liczbą oznaczającą adres komórki. Można tu również używać etykiet.

4. Pośrednie predekrementowane – znane programistom mającym praktykę w języku C. Działa dokładnie tak samo jak pośrednie – jednak przed użyciem

Page 58: Aby język giętki

wskaźnika – jego zawartość jest zmniejszana o 1. Podobnie jak w innych przypadkach możemy używać etykiet – i podobnie jak w innych przypadkach ten typ adresowania jest oznaczony znakiem poprzedzającym adres komórki pamięci. W tym wypadku znakiem mniejszości ‘<’

Lista instrukcjiW podstawowej wersji języka RedCode – mamy do dyspozycji 11 instrukcji. Instrukcje

mogą posiadać jeden lub dwa parametry. W większości przypadków parametry mogą być określone przez wszystkie typy adresowania. W niektórych przypadkach pewne tryby adresowania nie są dozwolone.

Pełna lista instrukcji zawiera operacje o kodach: DAT, MOV, JMP, ADD, SUB, CMP, JMZ, JMN, DJN, SLT, SPL pozwalające na wykonywanie dodawania i odejmowania, skoków warunkowych i bezwarunkowych oraz rozdzielania programu na wątki.

DAT arg1W zasadzie nie jest to polecenie. Komputer próbujący wykonać tą instrukcję – kończy

aktualnie wykonywany wątek. Instrukcja ta jest używana do przechowywanie wartości w pamięci. Jej pierwszy argument jest wartością przechowywaną w komórce pamięci. Tryb adresowania tego parametru nie ma znaczenia. Wartości przechowywane w komórkach w których znajduje się instrukcja DAT – zazwyczaj służy jako licznik lub wskaźnik w adresowaniu pośrednim.

Wpisując do komórki pamięci wartość (adresowaną natychmiastowo) – powodujemy wstawienie instrukcji DAT do tej komórki.

MOV arg1, arg2Operacja kopiowania danej. Argument pierwszy jest źródłem danej – określa jaka wartość

(przy adresowaniu natychmiastowym) albo jaka komórka pamięci (przy adresowaniu bezpośrednim i pośrednim) będzie skopiowana. Argument drugi określa dokąd skopiować zawartość Argument 2 nie może być adresowany natychmiastowo – trzeba tu wskazać komórkę pamięci.

Jeśli argument 1 jest po prostu wartością (adresowaną natychmiastowo) – to w komórce określonej przez drugi argument – zostanie umieszczona instrukcja DAT z pierwszym parametrem równym kopiowanej wartości. Jeśli pierwszy argument adresuje komórkę pamięci (adresowanie bezpośrednie lub pośrednie) – to kopiowana jest cała komórka – razem z kodem operacji i drugim argumentem.

Jeśli użyto adresowanie pośredniego predekrementowanego – to dodatkowo wartości wskaźników ulegają zmniejszeniu przed pobraniem (użyciem) wskaźników.

JMP arg1Skok bezwarunkowy do miejsca w programie określonym argumentem. Nie możemy

używać tu adresowania natychmiastowego – jedynie pośrednie lub bezpośrednie – powodujące skok do określonej komórki pamięci, lub komórki której adres znajduje się we wskazanej komórce.

Page 59: Aby język giętki

Pamiętajmy, że każde adresowanie dotyczy adresowania względem bieżącej lokalizacji instrukcji w pamięci. Dlatego pętlę nieskończoną możemy zapisać jako JMP 0

ADD arg1, arg2Dodaj wartość pierwszego argumentu do komórki pamięci określonej drugim parametrem.

Drugi parametr nie może być adresowany natychmiastowo – musi to być komórka pamięci. W komórce w której umieszczany jest wynik – jedynie wartość pola pierwszego argumentu ulega zmianie. Pole operacji i drugiego parametru pozostają bez zmian.

Pamiętajmy że wszystkie operacje arytmetyczne wykonywane są modulo 10000. Tak wiec nie można się dziwić, że 6000+6000=2000, lub – co jeszcze bardziej zabawne – że suma dwu wartości ujemnych – może być dodatnia

SUB arg1, arg2Różnica argumentu drugiego I pierwszego Wynik umieszczany jest w komórce pamięci

określanej drugim argumentem. Działa podobnie jak dodawanie – z tą różnicą że wynik jest – różnicą.

CMP arg1, arg2Porównanie dwu parametrów. Jeśli argumenty są równe – to kolejna instrukcja jest

przeskakiwana. Instrukcja ta dopuszcza wszystkie tryby adresowania dla obu argumentów i jest zazwyczaj połączona z instrukcją JMP, która prowadzi do miejsca w programie do którego należy przeskoczyć, ale można też użyć innej instrukcji jako instrukcji wykonywanej warunkowo.

SLT arg1, arg2Działa podobnie jak CMP – z tą różnicą, że kolejna instrukcja jest przeskakiwana, jeśli

pierwszy argument jest mniejszy niż drugi. Podobnie jak w przypadku instrukcji CMP – wszystkie tryby adresowania są dostępne dla obu argumentów.

JMZ arg1, arg2Instrukcja skoku warunkowego – jeśli wartość pierwszego argumentu jest równy zero – to

wykonywany jest skok do miejsca określonego przez drugi argument. Jeśli wartość ta jest różna od zera – to program przechodzi do kolejnej instrukcji.

Pierwszy argument może stosować dowolne tryby adresowania, drugi – jedynie bezpośrednie i pośrednie.

JMN arg1, arg2Działa podobnie jak instrukcja JMZ – ale skok następuje wtedy gdy wartość pierwszego

argumentu jest różna od zera.

Page 60: Aby język giętki

DJN arg1, arg2Bardzo ciekawa instrukcje – Jej działanie to zmniejszenie zawartości komórki pamięci

adresowanej (pośrednio lub bezpośrednio przez pierwszy argument, oraz skok pod adres wskazany przez argument drugi (również adresowany pośrednio lub bezpośrednio) jeśli pierwszy argument – po zmniejszeniu – jest różny od zera.

Za tym skomplikowanym opisem kryje się – instrukcja pętli, którą łatwo zapisać jako:

Start: MOV 10, licznik ; inicjujemy licznik na 10 iteracjiPetla: ...

...

...DJN Licznik, Petla ; kolejna iteracja...

Licznik: DAT #0

SPL arg1Jedna z najciekawszych instrukcji – powodująca rozdzielenie programu poprzez

utworzenie nowego wątku. Początek nowego wątku jest wskazywany przez argument instrukcji. (adresowany bezpośrednio lub pośrednio). Instrukcja pozwalające na pisanie silnych wielowątkowych programów, ale także wolnych.

W grze, zawodnicy – a więc programu – otrzymują dokładnie połowę czasu procesora. Czas każdego zawodnika – jest dzielony pomiędzy wątki jego programu. Jeśli jeden zawodnik pracuje w jednym wątku, a drugi uruchamia ich kilka – to program zawodnika pierwszego będzie działał kilkakrotnie szybciej Pamiętajmy jednak, że program uważa się za zniszczony – jeśli wszystkie jego wątki zostaną zabite.

Przykładowy programJako przykład zaproponowałbym program który ostrzeliwuje pamięć wokół siebie – a

następnie ucieka – kopiując się w inne miejsce pamięci.

; Program: Copier; Author: WGan; Version: 1

; poczatek - inicjacja wskaznikowstart: mov #target, count

mov #117, targetmov 4, source

; ostrzal przed sobamov #0, -12mov #0, -5mov #0, -10

Page 61: Aby język giętki

; petla kopiujaca sie w inne miejsceloop: mov <source, <target

djn count, loop

; obliczenie adresu skoku - i skoksub #3, targetjmp @target

; obszar zmiennychtarget: dat #0source: dat #0count: dat #0

Program jest dość prosty i chyba nie wymaga komentarza. Zwróćmy uwagę, że adresy względne podawane są nie według pola w jakim się znajdują, lecz względem instrukcji w której są używane. Jako samodzielne ćwiczenie – proponuję dodanie kodu który uruchomi dwa lub trzy takie programy wędrujące z różnym krokiem – czyli przeskakujące różną ilość komórek pamięci.

Będzie to wymagało dodania nowej etykiety – w instrukcji przypisującej do zmiennej target – adres względny końca przepisywanego programu – tak byśmy mogli zmodyfikować daną stojącą na pierwszym miejscu – dodając do niej jakąś wartość – trzeba to niestety zrobić po pewnym opóźnieniu – tak by program zdążył się już przepisać.

Inne asembleryPodobną budowę mają inne asemblery. Poszczególne wersje różnią się liczbą instrukcji i

operacji logicznych i arytmetycznych, zestawem trybów adresowania, ilością rejestrów czy wspólnym lub rozdzielonym adresowaniem portów wejścia / wyjścia.

Ponadto różne instrukcje mają różny koszt – wykonując się w czasie od jednego – nawet do kilkudziesięciu taktów zegara. Mogą też zajmować od jednej to kilku komórek pamięci.

Bogactwo języka zależy tu ściśle od sprzętu – procesora na który są pisane programy. Większość procesorów pozwala na wywoływanie podprogramów, korzystanie z rejestrów

– specjalnej pamięci wewnątrz procesora, wykonywania operacji arytmetycznych i logicznych. Szczególnie bogate są niekiedy sposoby adresowania – których liczba dochodziła do dwunastu.

Zadania1. Proszę napisać program który nie będzie ‘strzelał’ instrukcjami DAT, ale zerował

pierwszy argument instrukcji w pamięci – nie naruszając kodu operacji.2. Proszę napisać program który będzie wędrował po pamięci wstecz. 3. Proszę napisać program który rozmnoży takie wędrowniczki – tworząc programy

wędrujące z różnym krokiem.4. Proszę napisać własnego zawodnika do gry w „Core Wars” – zachowanie może

być dowolne, ale powinno być nastawione na przeżycie

Page 62: Aby język giętki

Spis TreściWstęp.....................................................................................................................................2Aby język giętki.....................................................................................................................3FORTRAN.............................................................................................................................5

Blok PROGRAM...............................................................................................................6Blok FUNCTION...............................................................................................................7Blok SUBROUTINE.........................................................................................................7Zmienne proste...................................................................................................................7Tablice................................................................................................................................8Inne typy............................................................................................................................8Przypisania.........................................................................................................................8Operatory arytmetyczne.....................................................................................................9Operatory logiczne.............................................................................................................9Operatory znakowe..........................................................................................................10Wywołania funkcji...........................................................................................................10Instrukcje sterujące..........................................................................................................10

Instrukcja skoku: GOTO..............................................................................................10Instrukcja warunkowa IF.............................................................................................11Instrukcja pętli: DO......................................................................................................11Instrukcja pusta: CONTINUE......................................................................................12Instrukcja wywołania procedury: CALL.....................................................................12Instrukcja powrotu z podprogramu: RETURN............................................................12Instrukcja zatrzymania programu: STOP.....................................................................12Instrukcja END............................................................................................................12

Wejście / Wyjście.............................................................................................................12Instrukcje wejścia / wyjścia.............................................................................................13Formatowanie...................................................................................................................13Użycie plików..................................................................................................................14Specyfikatory dla instrukcji wejścia / wyjścia.................................................................15Definicje stałych..............................................................................................................17Deklaracja zmiennych......................................................................................................17Nadawanie wartości początkowych.................................................................................18Obszary wspólne czyli jak dzielić zmienne pomiędzy blokami.....................................18Equivalence - coś jakby unia...........................................................................................19Alternatywne wejście do procedury / funkcji..................................................................19Funkcje wbudowane........................................................................................................19Przykład programu...........................................................................................................20

Page 63: Aby język giętki

Zadania.............................................................................................................................22Lisp......................................................................................................................................24

Atomy, listy i funkcje.......................................................................................................24Lista asocjacji...................................................................................................................26Funkcje i makra................................................................................................................27Funkcje podstawowe........................................................................................................28

car.................................................................................................................................28cdr................................................................................................................................28cons..............................................................................................................................29atom..............................................................................................................................29eq..................................................................................................................................29if...................................................................................................................................29defun............................................................................................................................30

Wartościowanie................................................................................................................30eval...............................................................................................................................31

Definiowanie funkcji i makr............................................................................................31&rest.............................................................................................................................32&optional.....................................................................................................................32&key.............................................................................................................................32&aux.............................................................................................................................32Makra...........................................................................................................................32

Operatory.........................................................................................................................33operatory arytmetyczne................................................................................................33operatory logiczne........................................................................................................33operatory relacji...........................................................................................................34

Rekurencja.......................................................................................................................35Funkcje sterujące..............................................................................................................36

when.............................................................................................................................36unless............................................................................................................................37cond..............................................................................................................................37case...............................................................................................................................38do..................................................................................................................................38

Sekwencyjne wykonywanie instrukcji.............................................................................38prog1, prog2, progn......................................................................................................39let..................................................................................................................................40tagbody.........................................................................................................................40prog..............................................................................................................................41

Inne ciekawe funkcje implementacji Lisp.......................................................................41

Page 64: Aby język giętki

Funkcje testujące..........................................................................................................42Funkcje operujące na listach........................................................................................43Modyfikacja wartości...................................................................................................44Operacje matematyczne...............................................................................................44Inne operacje na listach................................................................................................46Inne funkcje..................................................................................................................48

Inne typy danych implementacji Lisp..............................................................................49Ewaluator – czyli Lisp w Lispie......................................................................................49Przykład programu...........................................................................................................54Zadania.............................................................................................................................55

RedCode...............................................................................................................................56Budowa instrukcji............................................................................................................56Tryby adresowania...........................................................................................................57Lista instrukcji..................................................................................................................58

DAT arg1.....................................................................................................................58MOV arg1, arg2...........................................................................................................58JMP arg1......................................................................................................................58ADD arg1, arg2............................................................................................................59SUB arg1, arg2.............................................................................................................59CMP arg1, arg2............................................................................................................59SLT arg1, arg2.............................................................................................................59JMZ arg1, arg2.............................................................................................................59JMN arg1, arg2............................................................................................................59DJN arg1, arg2.............................................................................................................59SPL arg1.......................................................................................................................60

Przykładowy program......................................................................................................60Inne asemblery.................................................................................................................61Zadania.............................................................................................................................61

Spis Treści............................................................................................................................62