Pagini

2013-09-02

Site in Common Lisp, partea 1

Voi incepe o serie de articole in care voi povesti cum lucrez in Common Lisp
la un site, un proiect personal. Intentia este sa scriu macar un articol pe
saptamana. Pentru setarea mediului de lucru Common Lisp vezi aici
cum se seteaza pe un Raspberry Pi si poti extrapola pentru un PC obisnuit.



In primul rand, trebuie sa fie instalate Emacs, Quicklisp si o implementare
de Common Lisp - eu folosesc Clozure Common Lisp (CCL).

Asadar, pornesc Emacs, M-x slime , si deschid fisierul lucru.lisp
(C-x C-f lucru.lisp ).
Mini-terminologie Emacs:
- C- inseamna ca trebuie apasate simultan tastele Control si
- M- inseamna ca trebuie apasate simultan tastele Alt si
- C-x C-f lucru.lisp inseamna sa apesi simultan Control si x,
sa le dai drumul, apoi sa apesi simultan Control si f, sa le dai drumul,
sa scrii "lucru.lisp" in mini-buffer-ul din partea de jos a lui Emacs
si apoi sa apesi Enter
- ceea ce in mod normal se numeste window/fereastra, in Emacs este frame,
si contine ferestre Emacs, bara de meniu si alte obiecte de GUI
- in acest frame poti sa ai mai multe ferestre Emacs
- in fiecare fereastra Emacs editezi un singur buffer, care poate sa fie
continutul unui fisier de pe disc sau pur si simplu niste text
Deci Emacs are un frame mare, in care pot fi vizibile mai multe ferestre.

Cu Emacs lucrez in felul urmator: C-x 1 pe o fereastra o face sa ocupe tot
frame-ul Emacs, apoi C-x 2 sau C-x 3 imparte fereastra in 2 pe orizontala,
respectiv pe verticala. In una din ferestre acum pot sa apas C-x
sau C-x pentru a cicla intre bufferele deschise. Astfel, imi pastrez
REPL-ul Common Lisp intr-unul din buffere si in celalalt vad fisierul deschis,
in cazul de fata lucru.lisp. Intre cele doua ferestre ma misc cu C-x o.
In fereastra non-REPL evaluez expresii cu C-x e la finalul expresiei.
C-M k sterge expresia curenta.
C-M e si C-M a se misca intre expresii, adica inainte si inapoi.
C-a si C-e muta cursorul la inceputul, respectiv la sfarsitul liniei curente.
Daca apare o eroare (controlul se muta intr-o fereastra cu o lista de optiuni
numerotate - restart-uri, urmata de o lista de apeluri de functii numerotate
in sens invers - stiva de apeluri) cea mai usoara cale de a scapa de ea este
sa apesi q (quit).


Carti despre Common Lisp, disponibile pe net:
- "Practical Common Lisp" de Peter Seibel, carte foarte buna pentru incepatori
in care se explica aspecte teoretice sir si arata si punerea lor in aplicare;
gratuita aici; [PCL]
- "On Lisp" de Paul Graham, carte dedicata in mare parte studiului macrourilor,
necesita o minima pregatire; aici; [OL]
- "Let Over Lambda" de Doug Hoyte, in care sunt explicate alte tehnici de
macrouri; aici; [LOL]

OK, sa trecem la treaba!


Ce vreau sa obtin?

Vreau sa fac un site despre masini. In el vei putea sa faci diverse cautari
despre masini noi, dupa diverse criterii gen capacitate cilindrica, numar de
usi sau tip combustibil, ideea de baza fiind sa ajut pe cineva care cauta o
masina noua sa-si gaseasca un model cat mai apropiat de ce are nevoie.
Vreau sa am mai multe colectii de date. Vreau sa am modele de masini, fiecare
masina avand atributele sale proprii. Atributele sa nu fie chiar la liber,
sa nu poti introduce ca si caracteristica a vehiculului orice text, ci sa fie
restrictionate la un anumit set, pastrat intr-o alta colectie. Eventual la un
moment dat sa pun si niste tipuri pe atribute.

In prima faza incarc cateva biblioteci si creez pachetul in care voi lucra.
Un pachet este o colectie de simboluri, faptul ca imi scriu codul intr-un
pachet este oarecum echivalent cu a scrie intr-un namespace in C#.

(ql:quickload '(:hunchentoot))

(defpackage :cars-site
    (:use :cl :hunchentoot))

(in-package :cars-site)
Functia in-package face ca pachetul respectiv sa devina curent.

Pun niste "infrastructura" pentru atribute:
(defparameter *all-attributes* nil)

(defvar *attribute-ids* 0)

(defun get-next-attribute-id ()
  (incf *attribute-ids*))

(defclass attribute ()
  ((name :initarg :name :initform (error "Attribute's name?") :accessor name)
   (id :initarg :id :initform (get-next-attribute-id) :reader id)
   (att-type :initarg :type :initform 'string :accessor att-type)))

(defun add-attribute (att)
  "Should check for existing attribute"
    (if (find-attribute-by-name (name att))
        (error "There is already an attribute by this name"))
    (push att *all-attributes*)
   att)

(defun find-attribute-by-name (name)
  (find name *all-attributes* 
 :test #'(lambda (to-find item) (string= (name item) to-find))))

(defun search-attribute-by-name (partial-name)
  (loop
    for a in *all-attributes*
    when
      (search partial-name (name a) :test #'string-equal)
    collect a))
*all-attributes* este colectia de atribute. *attribute-id* este ca o secventa,
va genera id-urile atributelor atunci cand acestea sunt adaugate in colectie.
Aceste doua variabile, fiind definite cu defparameter si respectiv defvar,
sunt variabile speciale si pot fi accesate din orice punct din program.

get-next-attribute-id returneaza urmatorul id de atribut.
find-attribute-by-name cauta precis un atribut dupa un nume, iar
search-attribute-by-name cauta dupa portiuni din nume.
Diferenta de test la cele doua functii de cautare provine din faptul ca
find-... are ca argument numele exact al atributului, iar
search-... poate sa primeasca si un string cu alta capitalizare
decat numele real al atributului. add-attribute adauga un atribut nou,
daca nu exista deja unul cu acelasi nume.

Am definit si o clasa de obiecte, attribute, care are 3 campuri momentan:
- name care trebuie sa fie initializat la crearea instantei, altfel va rezulta
o eroare/conditie
- id care poate fi initializat la crearea instantei, dar daca nu se intampla
lucrul acesta isi ia el automat valoarea apeland get-next-attribute-id
- type care este implicit 'string

In Common Lisp exista un sistem de obiecte care se numeste CLOS (Common Lisp
Object System). Cu acesta se pot crea sabloane de obiecte (clase) si instante
de obiecte (entitati concrete create conform sablonului). Clasele pot contine
sloturi, similare campurilor din limbajele obisnuite de programare. Metodele
sunt implementate "in afara claselor" ca functii generice care sunt apoi
specializate pe baza tipurilor parametrilor. Pentru detalii mai multe vezi
capitolele 16 si 17, care se refera la CLOS, din [PCL].

(add-attribute (make-instance 'attribute :name "Capacitate cilindrica"))
(add-attribute (make-instance 'attribute :name "Numar usi"))
(add-attribute (make-instance 'attribute :name "Forma"))
(add-attribute (make-instance 'attribute :name "Tip combustibil"))
(add-attribute (make-instance 'attribute :name "Culoare"))
Am adaugat un numar minim de atribute. make-instance creeaza o instanta noua
de clasa, aici se pot introduce initarg-uri, similare cu parametrii
constructorilor din alte limbaje de programare.

Similar voi proceda si pentru masini:
(defclass vehicle ()
  ((name :initarg :name :initform (error "What is the vehicle's name?") 
  :accessor name)
   (id :initarg :id :initform (get-next-car-id) :reader id)
   (attributes :initform '())))

(defparameter *all-vehicles* nil)

(defvar *vehicle-ids* 0)

(defun get-next-vehicle-id ()
  (incf *vehicle-ids*))

(defun add-vehicle (vehicle)
  (push vehicle *all-vehicles*)
  vehicle)

(defun find-vehicle (i)
  "Finds a vehicle by its id"
  (find i *all-vehicles* :test #'(lambda (to-find y) (= (id y) to-find))))

(add-vehicle (make-instance 'vehicle :name "Volkswagen Golf 4 1.9 TDI 90CP"))
(add-vehicle (make-instance 'vehicle :name "Mazda 3 1.6 105CP"))
(add-vehicle (make-instance 'vehicle :name "Dacia Duster Laureate 1.5 dCi 110CP"))

Am adaugat variabila *all-vehicles* in care voi stoca toate obiectele masina,
definite prin clasa vehicle.

Niciun comentariu:

Trimiteți un comentariu

S-ar putea sa nu vedeti comentariul aparand imediat, asta inseamna ca el asteapta aprobarea mea. Aceasta e o masura anti-SPAM.