<

Pozycjonowanie stron www i SEO / SEM

Pozycjonowanie i optymalizacja stron.

* 100% mierzalność efektów pozycjonowanie e trzeba działać szybko rotujące na tej strony (może być nieopłacalne.

Dlatego tak ważne jest:

Common Lisp

Common Lisp
Pojawienie się 1984, specyfikacja ANSI w 1994
Paradygmat wieloparadygmatowy (funkcyjny, obiektowy, proceduralny)
Typowanie dynamiczne (statyczne na życzenie)
Implementacje CLISP, AllegroCL, CMUCL, GCL, LispWorks, MCL, SBCL
Pochodne CLtL1, CLtL2, ANSI Common Lisp
Twórca komitet ANSI X3J13
Platforma sprzętowa wieloplatformowy
Platforma systemowa wieloplatformowy

Common Lisp (często skracane do CL) – dialekt języka programowania Lisp, zbudowany przez ANSI (X3.226-1994) jako specyfikacja, nie implementacja. Istnieje parę implementacji, zarówno zamkniętych, jak oraz dostępnych jako FOSS. Common Lisp jest wieloparadygmatowym językiem programowania ogólnego przeznaczenia, skupiającym się na programowaniu funkcyjnym, pozwalając jednak stosować obiektowość, co daje programiście dużą swobodę.

Spis treści

Składnia

Common Lisp jest dialektem Lispu. Używa S-wyrażeń do opisywania kodu oraz struktur danych. Funkcje oraz wywołania makr zapisuje się w postaci list z nazwą funkcji (lub makra) jako pierwszym elementem.

(+ 2 2)           ; sumuje 2 oraz 2, zwraca 4
 
(setf p 3.1415)   ; ustawia wartość zmiennej p na 3.1415, zwraca 3.1415
 
#+(or) 37 42 ; komentowanie wyrażeń; wynik to 42
 
; tworzy funkcję podnoszącą liczbę do kwadratu
(defun kwadrat (x) 
  (* x x))
 
; wywołuje powyższą funkcję
(kwadrat 3)        ; zwraca 9
; specjalna forma let tworzy nowe zmienne lokalne, których zasięg ogranicza się do końca bloku.
(let ((a 6) (b 4)) 
  (+ a b)) ; zwraca 10

Typy danych

Typy skalarne

Do typów liczbowych należą liczby całkowite, zmiennoprzecinkowe oraz zespolone oraz ułamki zwykłe. Common Lisp wykorzystuje z dużych liczb do reprezentowania wartości liczbowych o dowolnym rozmiarze oraz precyzji. Typ ułamkowy nie jest dostępny w większości innych języków. Przy zwracaniu liczby CL automatycznie nadaje jej właściwy typ.

Znaki w CL nie muszą się koniecznie zawierać w standardzie ASCII. Wynika to z faktu, że Lisp stał się zbudowany przed standardem. Pewne implementacje [1] dopuszczają znaki ze standardu Unicode.

Typ symboliczny jest wspólny dla Lispów, ale raczej nieznany poza nimi. Symbol to unikalny, nazwany obiekt z kilkoma slotami na dane. Symbole w Lispie są wielokrotnie używane analogicznie do nazw w innych językach (przechowywanie wartości zmiennej, nazywanie funkcji), jednakże istnieje wiele innych sposobów użycia. W CL w zależności od miejsca użycia symbol zwraca powiązaną z nim funkcję albo wartość. Część symboli przy użyciu zwraca same siebie, dla przykładu T, NIL czy wszystkie symbole z pakietu KEYWORDS.

Wartości logiczne w Common Lispie reprezentowane są przez symbole T oraz NIL, każda wartość, która nie jest NIL, jest automatycznie uznawana za prawdę.

Struktury danych

Do typów sekwencyjnych w CL zaliczają się listy oraz wektory. Wiele funkcji może pracować na wielorakich typach sekwencyjnych, dla przykładu map.

Listy w CL są wykonywane za pomocą funkcji cons, czasami skracane do tej nazwy są także komórki cons (ang. cons cell, czasami też używa się nazwy para, ang. pair). Cons jest strukturą danych z dwoma slotami zwanymi car oraz cdr. Lista to łańcuch połączonych komórek cons. W liście slot car komórki cons zawiera element listy (na przykład liczbę, znak czy inną listę), a slot cdr wskazuje na następny element listy, albo na symbol NIL, kiedy dany dana komórka jest ostatnia w liście. Listy bywają wykorzystywane do implementacji drzew, grafów oraz innych struktur danych.

Common Lisp obsługuje wielowymiarowe tablice (array) oraz potrafi w razie potrzeby dynamicznie zmieniać ich rozmiar. Wielowymiarowe tablice bywają używane dla przykładu do implementacji macierzy. Wektor (vector) to jednowymiarowa tablica. W tablicy da się przechowywać wszystkie typy danych (nawet zróżnicowane typy w jednej tablicy) albo da się posiadać tablice specjalizowane do przechowywania określonego typu, dla przykładu wektor liczb zmiennoprzecinkowych. W standardzie określone są dwie wyspecjalizowane tablice – string to wektor znaków, a wektor bitowy (bit-vector) to wektor zawierający bity.

Hashe przechowują zależności pomiędzy kluczami a wartościami tak, by wydajne było pobranie wartości na podstawie klucza.

Przestrzenie nazw (package, znane w poniektórych językach jako namespace) są realizowane zaledwie dla symboli; parsing niekwalifikowanych ciągów znaków na symbole w przestrzeni nazw przebiega się zwykle podczas kompilacji, ale jeszcze przed semantyczną analizą grafu wyrażeń symbolicznych. Przestrzeń nazw może eksportować symbole oznaczając je jako zewnętrzny interfejs, jednak ścisła enkapsulacja nie jest w żaden sposób zapewniona. Zawsze możliwe jest odwoływanie się do prywatnych symboli jak oraz redefinicja cudzych klas, zmiennych czy we wzorcowych implementacjach samego kompilatora. Jest to filozofia tworzenia oprogramowania wewnątrz maszyny wirtualnej obecna także w Smalltalku wedle której używane biblioteki, aplikacja, kompilator składają się na organiczną całość.

Struktury, analogicznie do struktur C oraz pascalowych rekordów, reprezentują złożone struktury danych z dowolną liczbą oraz typem pól (zwanych slotami). Struktury róznią się od zwykłych obiektów z metaklasą standard-class złożonością obliczeniową dostępu do slotu. Przy optymalnej implementacji pobranie wartości zapisanej w strukturze będzie stale O(1), niezależnie od ilości slotów, jednak za cenę niemożności redefinicji struktur tudzież konieczności ponownej kompilacji kodu po redefinicji struktury.

CLOS

Klasy w Common Lisp Object System posiadają możliwość modyfikacji zachowania obiektów poprzez metaklasy oraz użycie ``protokołu metaobiektowego pozwalając na zachowania nie planowane przez tworców jak np. obiekty automatycznie dodające oraz uzupełniające się w bazie danych SQL [2] czy korzystające z modelu propagacji więzów [3][4] przypominającego w działaniu arkusz kalkulacyjny.

Protokół metaobiektowy (MOP) [5] nie stał się zawarty w standardzie ANSI, jednak książka [6] opisująca jego działanie oraz przykładową implementację stała się de-facto standardem wśród implementacji Lispu.

W przeciwieństwie do języków takich jak Smalltalk czy C++, metody nie należą do przestrzeni nazw klas; należą one do generic function znajdującej się w przestrzeni nazw pakietu (lub modułu, namespace). Możliwa jest specjalizacja typu nie tylko pierwszego argumentu (self, this) ale każdego z wymaganych argumentów. Określanie pasującej metody jest względnie wydajne dzięki sprytnej memoizacji opisanej w AMOP.

Funkcje

W CL funkcje są traktowane jako typ danych. Umożliwia to dla przykładu pisanie funkcji, które pobierają inne funkcje jako argument albo zwracających inne funkcje. Standardowa biblioteka CL szeroko wykorzystuje tę możliwość, dla przykładu funkcja sort pobiera funkcję porównującą jako argument. Umożliwia to na sortowanie nie tylko każdego typu danych, ale także dopuszcza sortowanie złożonych struktur danych zależnie od klucza.

 (sort (list 5 2 6 3 1 4) #'>)
 ; Sortuje listę używając > jako operatora porównującego.
 ; Zwraca (6 5 4 3 2 1).
 
 (sort (list '(9 a) '(3 b) '(4 c))
       (lambda (x y) (< (car x) (car y))))
 ; Sortuje listę zależnie od pierwszego elementu podlisty.
 ; Zwraca ((3 b) (4 c) (9 a)).
 ; Lub: (sort (copy-seq '((9 a) (3 b) (4 c))) #'< :key #'car)
 ; korzystając z ''keyword parameters'.

Model wartościowania dla funkcji jest bardzo prosty. Gdy parser napotyka na formę (F A1 A2 A3 ...), zakłada, że symbol F jest albo:

  1. Specjalnym operatorem (z ustalonej listy)
  2. Zdefiniowanym wcześniej makrem
  3. Nazwą funkcji

Jeżeli F jest nazwą funkcji, argumenty A1, A2, A3 itd. są wartościowane w porządku od lewej do prawej oraz, o ile funkcja istnieje, jest wywoływana z wartościami zwróconymi przez argumenty. Jeśli zaś F desygnuje makro, zostanie ono rozszerzone rekursywnie aż zostaną zaledwie specjalne formy oraz wywołania funkcji.

Definiowanie funkcji

Makro defun służy do definiowania funkcji. Definicja zawiera nazwę funkcji, nazwy jej argumentów oraz wyrażenia stanowiące zawartość funkcji (ang. body).

 (defun square (x)
    (* x x))

Definicja funkcji może zawierać deklaracje, które dostarczają kompilatorowi informacji o ustawieniach optymalizacji albo typach danych argumentów. Może także zawierać dane do dokumentacji, z których Lisp może generować interaktywną dokumentację:

 (defun square (x)
    (declare (integer x) (optimize (speed 3) (debug 0) (safety 1)))
    "Oblicza kwadrat liczby podanej w argumencie x"
    (* x x))

Anonimowe funkcje są definiowane przy użyciu lambda-wyrażeń. Styl programowania w Lispie wielokrotnie wykorzystuje anonimowe funkcje jako argumenty do innych funkcji.

Jest wiele operatorów związanych z definiowaniem oraz manipulowaniem funkcjami. Dla przykładu funkcja może zostać skompilowana za pomocą funkcji compile. Część implementacji CL uruchamia funkcje w interpreterze, jeżeli nie wydano instrukcji kompilacji, inne domyślnie kompilują w locie.

Przestrzeń nazw funkcji

Przestrzeń nazw funkcji jest oddzielona od przestrzeni zmiennych, co jest główną różnicą pomiędzy CL a Scheme. Do operatorów definiujących nazwy w przestrzeni funkcji należą m.in. defun, flet oraz labels.

Aby poprzez nazwę przekazać funkcję jako argument do innej funkcji trzeba użyć specjalnego operatora function, zwykle skracanego do #'. Pierwszy przykład użycia funkcji sort (powyżej) odwołuje się do funkcji poprzez symbol > za pomocą #'>.

Model wartościowania Scheme jest prostszy – jest tylko jedna przestrzeń nazw oraz wszystkie elementy formy są wartościowane w dowolnej kolejności – nie tylko argumenty. Przez to kod napisany w jednym dialekcie bywa mylący dla programisty bardziej doświadczonego w innym. Dla przykładu wielu programistów CL używa nazw opisujących zmienną (np. list czy string), co może powodować problemy poprzez lokalne przysłanianie nazw funkcji. By wywołać funkcję znajdującą się w przestrzeni wartości, konieczne jest użycie funkcji funcall czy apply - złożone formy [7] jako pierwszy element ewaluowanej listy są bezwarunkowo niedozwolone. Pociągnęło to za sobą także pozornie nie związane odmienności w kulturach Scheme oraz Common Lispu - przykładowo, w CL-u klasy leżą w osobnej "przestrzeni nazw" z interfejsem find-class, zaś w Scheme dodawane do globalnego środowiska jak np. w systemie obiektowym Meroon.

Pytanie, czy oddzielna przestrzeń nazw dla funkcji jest zaletą, czy nie, jest źródłem sporów w społeczności Lispu. Jest to zwykle nazywane debatą Lisp-1 vs. Lisp-2. Nazwy te zostały wymyślone w artykule z 1988 r. przez Richarda Gabriela oraz Kenta Pitmana [8], którzy porównują w nim oba podejścia[9].

Inne typy danych

Do pozostałych typów w CL zaliczają się:

  • Ścieżki (pathnames) reprezentują pliki w systemie plików. Ścieżki w CL są bardziej ogólne niż zasady nazewnictwa plików w większości plików operacyjnych, co sprawia że dostęp do plików w wielorakich systemach operacyjnych jest zwykle identyczny.
  • Strumienie (streams) wejścia oraz wyjścia reprezentują źródła danych binarnych albo tekstowych, takich jak np. terminal czy otwarte pliki.
  • Condition to typ reprezentujący błąd, wyjątek czy inne zdarzenie, na które program może odpowiedzieć.

Makra

Makra w użyciu przypominają funkcje. Jednakże, zamiast reprezentowania wartościowanego wyrażenia, reprezentują raczej transformację kodu źródłowego.

Makra pozwalają programistom tworzyć nowe składniowe formy języka. Na przykład, makro działające jak until z Perla da się napisać tak:

 (defmacro until (test &body body)
   `(do ()
        (,test)
      ,@body))
 
 ;; przyklad
 (until (= (random 10) 0) 
   (write-line "Hello"))

Wszystkie makra muszą być rozwinięte, aby kod źródłowy zawierający je mógł być normalnie ewaluowany albo kompilowany. Makra da się uznać za funkcje, które przyjmują oraz zwracają drzewa składniowe (lispowe S-wyrażenia). Są wywoływane przed ewaluatorem albo kompilatorem, by wyprodukować finalny kod źródłowy. Makra są pisane w pełni funkcjonalnym Common Lispie oraz potrafią używać dostępnych w nim funkcji. Użyty powyżej zapis z backqoute (`) w CL upraszcza zwykły przypadek podstawiania w szablonie kodu.

Higiena makr oraz przechwytywanie zmiennych

Makra Common Lispu dopuszczają wstawianie symboli istniejących w środowisku wywołującym, pozwalając na generację kodu wykorzystującego zmienne istniejące w kontekście; są to tzw. niehigieniczne makra.

Taka praktyka może powodować nieprzewidziane oraz niezwykłe błędy. Pewne dialekty Lispu, jak Scheme, unikają ich dzięki używaniu tzw. "higienicznych makr". W Common Lispie da się zwykle uniknąć niechcianego przechwycenia używając unikalnych symboli (generowanych przez funkcję gensym) – ich użycie wcale nie spowoduje kolizji z występującym symbolem.

Przydatne może się okazać klasyczne makro with-gensyms

(defmacro with-gensyms ((&rest syms) &body body)
  `(let ,(loop for oraz in syms collect (list oraz `(gensym ,(symbol-name i))))
     ,@body))

Kolejną sprawą jest nieumyślne przysłanianie istniejącej funkcji/makra w rozwinięciach makr. Dla przykładu w następującym (niepoprawnym) kodzie:

 (macrolet ((do (...) ... cos-innego ...))
    (until (= (random 10) 0) (write-line "Hello")))-

Kod makra UNTIL zostanie rozwinięty do formy wywołującej DO które winno odwoływać się do wbudowanego makra DO. Jednak w tym kontekście, DO może posiadać całkowicie inne znaczenie.

Porównanie z innymi dialektami Lispu

Common Lisp jest najczęściej porównywany ze Scheme— albowiem są one najpopularniejszymi dialektami Lispu. Scheme poprzedza CL, oraz pochodzi nie tylko z tej samej tradycji Lispu ale ma też kilku wspólnych twórców – Guy L. Steele'a, z którym Gerald Jay Sussman zaprojektował Scheme, przewodził komitetowi standaryzacyjnemu Common Lispu. Common Lisp, w przeciwieństwie do Emacs Lisp oraz AutoLISP (które są językami rozszerzeń osadzonymi w określonych produktach), jest językiem programowania ogólnego przeznaczenia. Tak jak Scheme, używa leksykalnego zasięgu zmiennych. Istnieją jednak pewne różnice kulturowe pomiędzy społecznościami Scheme oraz CL oraz wedle Kenta Pitmana zachodzą wątpliwości czy Scheme w ogóle zalicza się do rodziny języków lispowych [10].

Przeważajaca ilość z dialektów Lispu których konstrukcja nawiązuje do Common Lispu – jak ZetaLisp oraz Franz Lisp – używa dynamicznych zasięgów zmiennych w swoich interpreterach a leksykalnych w kompilatorach. Scheme, zainspirowany ALGOLem 68 (w którym było to przez wielu uważane za dobry pomysł), wprowadził wyłączne użycie leksykalnego zasięgu zmiennych. CL wspiera także dynamiczny zasięg, ale takie zmienne muszą być wyraźnie zadeklarowane jako "specjalne". Nie ma różnicy pomiędzy zasięgiem w interpreterach oraz kompilatorów ANSI CL.

Common Lisp jest czasami nazywany Lispem-2 a Scheme Lispem-1, odnosząc się do używania przez CL osobnych przestrzeni nazw dla funkcji oraz zmiennych. (Prawdę mówiąc CL ma wiele przestrzeni nazw, listy własności (ang. property lists), makra zwykłe oraz kompilatora, wartość dynamiczna oraz funkcyjna) Między zwolennikami CL oraz Scheme istnieje długotrwały spór na temat wad oraz zalet używania wielu przestrzeni nazw. W Scheme (najczęściej) trzeba unikać nadawania zmiennym nazw kolidujących z nazwami funkcji; funkcje napisane w Scheme posiadają wielokrotnie argumenty o nazwach lis, lst czy lyst by nie konfliktowały z wbudowaną funkcją list. W CL podczas przekazywania argumentu do funkcji trzeba się odwołać do jej przestrzeni nazw –- co jest także wielokrotnie spotykane, jak w przykładzie sort powyżej.

CL różni się od Scheme także pod względem obsługi zmiennych boolowskich. Scheme używa specjalnych wartości #t oraz #f by reprezentować prawdę oraz fałsz. CL, zgodnie z konwencją starszych Lispów używa symboli T oraz NIL, gdzie NIL oznacza także pustą listę. W CL, jakakolwiek wartość inna niż NIL jest traktowana jako prawda przez wyrażenia warunkowe takie jak if tak jak wartości inne niż #f w Scheme. To dopuszcza niektórym operatorom działać jako predykaty oraz zwracać użyteczną wartość dla dalszych obliczeń.

Na koniec, dokumenty standaryzacyjne Scheme wymagają optymalizacji rekursji ogonowej, z tym że standard CL nie. Przeważajaca ilość implementacji CL oferuje optymalizację rekursji ogonowej, lecz zwykle tylko wtedy, kiedy programista użyje dyrektywy optymalizacyjnej. Mimo wszystko styl programowania CL nie faworyzuje powszechnego stosowania rekursji jaki preferuje styl Scheme – to, co programista Scheme wyraziłby za pomocą rekursji ogonowej, programista CL wyraziłby raczej za pomocą wyrażenia iteracyjnego w do, dolist, loop czy (ostatnio) za pomocą pakietu iterate.

Implementacje

Common Lisp jest zdefiniowany przez specyfikację (jak Ada oraz C) a nie przez jedną implementację (jak Perl). Jest wiele implementacji, oraz standard wyraża obszary gdzie potrafią się one różnić.

W dodatku, implementacje zwykle posiadają pakiety biblioteczne, które zapewniają funkcjonalność nie zawartą w standardzie. Było stworzonych wiele bibliotek Wolnego Oprogramowania by wspierać te właściwości w przenośny sposób, w szczególności Common-Lisp.net oraz projekt Common Lisp Open Code Collection. Istnieje także wiki cliki pomagające programistom w wymianie bibliotek oraz informacji o implementacjach.

Gdy ANSI CL był definowany w 1984, nie istniał powszechny teraz zwyczaj definiowania w ramach standardu obsługi formatów plików, protokołów sieciowych etc. Dlatego w ANSI CL nie jest zdefiniowana nawet obsługa gniazd sieciowych. Mimo to są de-facto standardy uznawane przez społeczność takie jak McCLIM jako interfejs graficzny czy usocket jako interfejs do gniazd sieciowych. Istnieje także klasa bibliotek stanowiących niewiele więcej niż interfejs zapewniający kompatybilność pomiędzy implementacjami cliki.net.

Common Lisp stał się zaprojektowany do implementacji za pomocą kompilatorów przyrostowych. Standardowe deklaracje optymalizujące kompilację (jak funkcje inline) zostały przedstawione w specyfikacji języka. Przeważajaca ilość implementacji CL kompiluje funkcje do kodu maszynowego. Odmienne kompilują do kodu bajtowego, który zwiększa przenośność kodu binarnego kosztem prędkości. Błędne mniemanie o tym, że Lisp jest językiem jedynie interpretowanym było spowodowane najprawdopodobniej przez fakt, że środowiska Common Lispu zapewniają interaktywny wiersz poleceń oraz kompilowanie funkcji jedna po drugiej, w sposób inkrementalny.

Pewne uniksowe implementacje, jak CLISP, bywają używane jako [4] interpretery skryptów uniksowych; tj. wywoływane przez system w przezroczysty sposób, jak interpreter Perla czy powłoki uniksowej.

Lista implementacji

Wolne implementacje CL to pomiędzy innymi:

  • CMUCL, pierwotnie napisany w Uniwersytecie Carnegie Mellon, aktualnie utrzymywany przez grupę ochotników jako Wolne Oprogramowanie. CMUCL używa szybkiego kompilatora do kodu natywnego. Jest dostępny na Linuksie oraz BSD dla Intela x86; Linuksie dla Alpha; oraz Solaris, IRIX oraz HP-UX na ich natywnych platformach.
  • Steel Bank Common Lisp (SBCL), odgałęzienie CMUCL. "Ogólnie mówiąc, SBCL w przeciwieństwie od CMU CL kładzie większy nacisk na przenośność." [5]. SBCL działa na tych samych platformach co CMUCL, poza HP/UX; w dodatku działa na Linuksie dla PowerPC. SPARC oraz MIPS, oraz Mac OS X. Eksperymentalne wsparcie dla systemów Microsoft Windows.
  • CLISP, przenośna implementacja kompilująca do kodu bajtowego, działa na wielu uniksowych oraz uniksopodobnych systemach (włączając Mac OS X), jak oraz Microsoft Windowsie oraz kilku innych systemach.
  • GNU Common Lisp (GCL), kompilator CL spod znaku projektu GNU. Nie jest jeszcze w pełni zgodny ze standardem ANSI, GCL jest implementacją wybraną przez parę wielkich projektów językowych włączając narzędzia matematyczne Maxima, AXIOM oraz ACL2. GCL działa pod GNU/Linuksem na jedenastu wielorakich architekturach, oraz pod Windowsem, Solaris oraz FreeBSD
  • Embeddable Common Lisp (ECL), zaprojektowany do osadzania w programach C.
  • OpenMCL [6], wolne/open source'owe odgałęzienie Macintosh Common Lispu. Jak sugeruje nazwa, OpenMCL był z początku natywny dla Macintosha, aktualnie działa na Mas OS X, Darwinie oraz GNU/Linuksie dla PowerPC oraz Intela x86-64.
  • Movitz implementuje środowisko Lispu na komputerach x86 bez żadnego systemu operacyjnego.
  • System Poplog system implements a version of CL, with POP-11, and optionally Prolog, and Standard ML (SML), implementuje wersję CL za pomocą POP-11 oraz opcjonalnie Poploga oraz Standard MLa, pozwalając na programowanie mieszanym językiem. Posiada także zintegrowany, podobny do Emacsa, edytor komunikujący się z kompilatorem.
  • Związane z Javą:

Komercyjne implementacje to pomiędzy innymi:

Sprawdź też

Przypisy

  1. Implementacje obsługujące Unikod: CLISP , SBCL , Allegro CL , OpenMCL oraz możliwie inne
  2. cl-perec
  3. Cells
  4. computed-class /
  5. Protokół metaobiektowy
  6. The Art of the Metaobject Protocol, Gregor Kiczales (AMOP)
  7. Z wyjątkiem typu (cons (eql lambda) t)
  8. Nazwy Lisp-1 oraz Lisp-2 zostały wybrane by odizolować koncepcję wielu przestrzeni wartości desygnowanych symbolem od różnic ideologicznych obozów Lispu oraz Scheme które zwykły wywoływać wielkie emocje podczas konfrontacji obu obozów
  9. Technical Issues of Separation in Function Cells and Value Cells
  10. Kent Pitman na Usenecie [1] [2] [3]

Bibliografia

bonprix katalog | Biuro Nieruchomości Białystok - Aktywne pośrednictwo, Bezpieczne transakcje | szafy bhp | pozycjonowanie stron w internecie | zbiór stron www