W pewnym sensie ta część naszego kursu LISP może być najtrudniejsza, jest to rozdział dla początkujących. Chciałbym w nim omówić najbardziej podstawowe wyrażenia. dla osób niemających wcześniej styczności z programowaniem, opanowanie tej części jest konieczne dla zrozumienia sensu kolejnych części. Osoby, które poznały wcześniej jakieś języki programowania również znajdą tu coś dla siebie, czyli syntezę składni wyrażeń używanych w LISPie.

Podstawowe pojęcia LISPa:

  • ; (średnik) - oznacza komentarz, to co jest w linii za znakiem komentarza nie będzie interpretowane przez program. Jeśli chcemy skomentować więcej niż jedną linię, możemy posłużyć się znakami ;| aby rozpocząć komentarz. Znacznik ten musi być zakończony znakami |;
  • nil – oznacza nic, (dla znających C, to jest to to samo co null W lispie null znaczy coś innego, o tym później) . Można by zadać sobie pytanie po co komu nic czyli nil. A nil jest bardzo ważnym wyrażeniem, najważniejszą jego funkcją jest sprawdzenie warunków (o warunkach niżej). Nil jest wartością zmiennych, którym nie są przypisane wartości. Wiele funkcji zwraca nil jeśli nie zostało poprawnie wykonanych, część funkcji zwraca nil zawsze np. command. Jeśli natomiast analizujemy typy danych, to nil jest równoważne wartości false w zmiennej typu logicznego (boolean). Przeciwieństwem do nil jest t. t jest równoznaczne wartości true zmiennej typu logicznego.
  • listy – są podstawowym elementem używanym w programach lispowych. Można nawet zaryzykować stwierdzenie, że wszystko w LISPie oparte jest na listach. Listą jest każde wyrażenie, nawet znane nam już 

(commandLine” P1 P2 ””)

  • jest listą pięciu elementów, z których pierwszy oznacza co ma zostać wykonane (nazwa funkcji), a pozostałe oznaczają argumenty funkcji). Wniosek z tego prosty, że każda lista ma przynajmniej jeden element, ilość koniecznych elementów w liście zależy od pierwszego elementu. Wszystko co jest ograniczone nawiasami to lista. Listy można podzielić na wiele rodzajów, w zależności od kryterium podziału. Gdyby chcieć podzielić listy ze względu na ich sposób adresowania można podzielić listy na indeksowane po numerze lub po nazwie (listy asocjacyjne, skojarzeniowe)
    Listy indeksowane jest to najczęściej używany typ listy. Użyć go można np. gdy istotna jest kolejność elementów , a nie są one nazywane, w praktyce jest to ogromna większość przypadków.

    Listy skojarzeniowe (asocjacyjne) - sensem takich list jest połączenie jakiejś informacji z inną. Przykładem może być używana w LISPie definicja narysowanego elementu, przykładowa taka definicja wygląda następująco:

    ((-1 . Entity name: 9ad5b00) (0 . "LINE") (5 . "1B9") (67 . 0) (8 . "0") (410 . "Model") (62 . 256) (6 . "ByLayer") (370 . -1) (48 . 1.00000) (60 . 0) (39 . 0.000000) (10 -468.040 1313.60 0.000000) (11 1055.68 626.068 0.000000) (210 0.000000 0.000000 1.00000))

    Jeśli nie jest to dla Państwa jasne, to proszę się nie obawiać, już niedługo się to zmieni. W takiej liście zapisane są wszystkie informacje jednoznacznie definiujące linię. Co więcej można się w bardzo łatwy sposób odwołać do danego elementu danej listy czyli do określonej właściwości. Bez tego odczytywanie właściwości byłoby równie zabawne jak czytanie książki telefonicznej :-). Każda właściwość elementu ma swój własny numer, którego musimy się nauczyć. Później przedstawię klika podstawowych numerów i odpowiadających im właściwości. Dalej nazywał je będę kodami DXF dlatego, że w plikach DXF są wszystkie elementy zapisane właśnie z użyciem tych numerów.

Podstawowe funkcje LISPa

  • (setq nazwa wartość) Jest to zdecydowanie najczęściej używana funkcja. Jest to operator przypisania (znaczy tyle co w C znak =) Myślę że jego opisywanie szczegółowe nie ma sensu, okaże się to wystarczająco oczywiste po przejrzeniu kilku przykładów.
    Po wydaniu polecenia
    (setq P1 (getpoint))

    użytkownik zostanie poproszony o wskazanie punktu. Współrzędne tego punktu zostaną przypisane do zmiennej P1, dzięki temu będzie można w łatwy sposób pracować na tych współrzędnych, dokonywać obliczeń, używać ich jako argumentu innych funkcji itp.

  • (print zmienna) Może się wydawać, że jeszcze zbyt wcześnie na wyjaśnianie print. W sumie nie mamy jeszcze nic do wydrukowania, jednak lepiej nie tylko poznać tą funkcję, ale zaprzyjaźnić się z nią, gdyż to właśnie dzięki niej możemy sprawdzić jaka jest wartość dowolnej zmiennej. Przynajmniej do czasu, kiedy nie zostanie opracowane środowisko programistyczne LISP do ZWCAD pozwalające na debugownie kodu. print wyświetla w linii poleceń wartość dowolnej zmiennej. W tym przypadku nie musimy się zajmować typem danych. Zmienna nie musi być typu string. Może to być wartość liczbowa, lista a nawet obiekt - choć w tym przypadku jedynie możemy sprawdzić jego istnienie a nie własności. np.
    (print (list 0 0 0))
  • (list [wartości]) funkcja ta tworzy listę z wartości będących kolejnymi parametrami funkcji. Nie jest istotne ile będzie parametrów tej funkcji, lista zostanie utworzona z wszystkich. Np.

    (list)

    stworzy pustą listę. Natomiast

    (list 1 2 0)

    stworzy listę trzech elementów, mogących być np. współrzędnymi punktu.

    Funkcja

    (setq WzoryKreskowania(list ”Solid” ”ANSI31” ” ANSI32” ” ANSI33” ” ANSI34” ” ANSI35” ” ANSI36”))

    stworzy listę wybranych wzorów kreskowania, i przypisze ją do zmiennej WzoryKreskowania .

  • (nth indeks lista) pobiera z listy element będący w liście na pozycji określonej parametrem indexnp.:
    (nth 3 WzoryKreskowania)

(nth 3 WzoryKreskowania)

 zwróci nam ” ANSI33

  • Zwrócony zostaje czwarty element pomimo że wpisaliśmy index 3. Jest to poprawne, ponieważ lista indeksowana jest od 0 więc 3 jest czwartym elementem listy.

  • (cons)Funkcja ta łączy dwa elementy, tworząc listę asocjacyjną. np.
    (cons 8 WarstwaOsi)

    oznacza że w liście wartości 8 zostanie przypisana wartość zapisana w zmiennej WarstwaOsi . Później można się odwoływać do tych elementów przez (assoc) aby sprawdzić na jakiej warstwie leży dany element.
    Bardzo bliska funkcji cons jest konstrukcja np.

    '(0 . "LINE")
    Konstrukcja ta jest równoważna
    (cons 0 "LINE")

    Różnica jest taka, że zawsze można zamienić

    '(0 . "LINE")
    na
    (cons 0 "LINE")
    ale nie można zamiennie zapisać
    (cons 0 wartosc)
    i
    '(0 . wartosc)
    ponieważ wartosc jest tutaj zmienną. Jeśli wartość przypisywana jest zmienną, musimy używać cons. Jeśli wartość jest stała, możemy użyć (cons) lub '( . )
  • (assoc) zwraca wartość z listy indeksowanej po nazwach. np.

    (setq Przykładowalista(list '(0 . "LINE") '(67 . 0) '(8 . "0") '(410 . "Model") '(62 . 256) '(6 . "ByLayer") '(370 . -1) '(48 . 1.00000) '(60 . 0) '(10 -468.040 1313.60 0.000000) '(11 1055.68 626.068 0.000000) '(210 0.000000 0.000000 1.00000))) ; przypisanie listy do zmiennej Przykładowalista
    (setq warstwa(ASSOC 8 Przykładowalista )) ;zwróci parę (8 . "0")
    (setq warstwa(cdr(ASSOC 8 Przykładowalista ))) ;zwróci drugi element pary czyli prawidłową nazwę warstwy elementu, w tym przypadku "0"
  • (car ),(cdr ) jest kilka funkcji o podobnych nazwach: car, cdr, cadr, caddr, cddar, cdaadr mam nadzieję, że się nie zapędziłem. Wszystkie te funkcje są specjalnymi przypadkami fukcji nth, czyli zwracają określoną wartość z listy. I tak:

    • car - zwraca pierwszy element z listy
    • cdr - zwraca wszystkie elementy oprócz pierwszego, (najczęściej używana jest do zwracania wartości z listy asocjacyjnej
    • cadr - można inaczej zapisać jako (car(cdr)) czyli najpierw wykonane jest (cdr) – zwraca wszytko poza pierwszym elementem, następnie na tym wykonywane jest (car) czyli pierwszy element. Wniosek – zostanie zwrócona druga wartość z listy.
    • caddr - analogicznie do poprzedniego przypadku inaczej można zapisać to jako (car(cdr(cdr))) czyli w skrócie mówiąc trzeci element z listy zapis taki jest równoznaczny z zapisem (nth 2 lista)
    • można tworzyć inne tego typu kombinacje w zależności od konstrukcji listy istnieją ograniczenia zagnieżdżenia tego typu konstrukcji jeśli się nie mylę do czterech stopni czyli (caaaar) będzie jeszcze poprawne, ale już (caaaaar) to za dużo, inna sprawa, że aby użyć takiej konstrukcji trzeba sobie wyobrazić macierz nawet pięciowymiarową. Nieczęsto stosuje się takie konstrukcje.
  • (append) - funkcja ta służy łączeniu list, lub dodaniu czegoś do istniejącej już listy.
  • (mapcar) funkcja ta wykonuje określone polecenie na każdym elemencie z listy, i zwraca listę będącą wynikiem wykonania tej operacji. dla przykładu:
    (setq powiekszona(mapcar '1+ (list 1 2 3 4))) ;zwróci (2 3 4 5)
    Funkcja ta wykonuje określone polecenie na każdym elemencie zadanej listy, niestety nie ma możliwości wykonania funkcji z zdanym parametrem, czyli jest możliwość wywołania funkcji 1+ zwiększającej wartość o 1, ale gdybyśmy chcieli dodać 5 musielibyśmy wywołać funkcję + z parametrem 5. A to już nie jest możliwe przy użyciu funkcji mapcar. W tym przypadku z pomocą przychodzi nam funkcja lambda
  • (lambda) - tworzy funkcję tymczasową, nie jest ona nigdzie zapisywana a jedynie wykonywana. Wiem że brzmi to przynajmniej dziwnie, ale może sprawa wyjaśni się po przyjrzeniu się przykładowi
    Nasz przykład będzie dodawał 5 do każdego elementu listy
    (setq c (list 15.0 30.0 45.0 22.5 90))
    (setq d (mapcar '(lambda (a) (+ a 5)) c))
    ; zwróci nam: (20.0000 35.0000 50.0000 27.5000 95)

(lambda (a) ...) można potraktować jak (defun (a) ...) z tą drobną różnicą, że po jednokrotnym zdefiniowaniu funkcji przez defun może być ona używana później, w przypadku (lambda) funkcja nie jest nazywana, nie można jej później używać, rodzi się pytanie po co w ogóle taka konstrukcja? odpowiedzią może być, że czasem jeśli potrzebna jest nam prosta funkcja zwyczajnie nie warto tworzyć takiej oddzielnej funkcji.

Jeśli funkcja lambda dalej nie jest jasna, proszę się nie martwić, zrozumienie jej przydatności nie jest niezbędne w przypadku tworzenia prostych funkcji a z odrobiną doświadczenie jej zrozumienie okaże się łatwiejsze.


No dobrze, skoro umiemy już tworzyć i wczytywać programy w LISP, operować na listach, trzeba stworzyć coś co można by nazwać programem.