Tworzenie biblioteki dla PLT

Niedawno napisaną
bibliotekę do generowania identikon udostępniłem na
planecie PLT - PLTowym odpowieniku
Perlowego CPANu, czy
Pythonowego indeksu pakietów. Zadanie
przygotowania biblioteki w taki sposób, by mogła być umieszczona na planecie
nie było trudne, ale niosło ze sobą kilka niespodzianek.

Efekt końcowy można
znaleźć na stronie pakietu,
razem z dokumentacją
i pełnym kodem źródłowym.
By skorzystać z biblioteki wystarczy teraz dodać do programu jedną linijkę:

(require (planet "identicons.ss" ("mk" "identicons.plt")))

Sam proces przygotowania biblioteki jest
dobrze opisany w dokumentacji.
Mój kod był już podzielony na moduły, pozostało więc napisanie dokumentacji w
formacie Scribble i wypełnienie
pliku info.ss. Możliwe do ustawienia atrybuty dla info.ss
są ładnie opisane we wspomnianym już dokumencie,
skupię się więc na samej tylko dokumentacji.

Upraszczając, dokument Scribble pisze się tak, jak zwykły dokument tekstowy.
Jest jednak dostępny cały zestaw specjalnych znaczników umożliwiających
linkowanie do innych stron dokumentacji, zagnieżdżanie kodu Scheme, wizualne
upiększanie tekstu itp. Przede wszystkim dokument musi zaczynać się od
#lang scribble/doc, po czy zazwyczaj następuje require.
Warta zapamiętania jest składnia z for-label.
Nagłówek pliku manual.scrbl z pakietu identikon wygląda następująco:

#lang scribble/doc
@(require scribble/manual
scribble/eval
(for-label scheme/gui)
(for-label "identicons.ss"))

@title{Identicons library}

@title ustawia tytuł strony z dokumentacją. Przy dokumentowaniu
modułów po tytule następuje zazwyczaj znacznik
@defmodule.
Efektem ubocznym zwykłego @defmodule jest zaimportowanie podanego modułu,
co w przypadku dokumentowania biblioteki, której jeszcze na planecie nie ma, jest dość
niefortunne. Problem udało mi się obejść w następujący sposób:

@defmodule*/no-declare[((planet "identicons.ss" ("mk" "identicons.plt")))]
@declare-exporting["identicons.ss"]

Poza tą małą niedogodnością inne znaczniki działają bez większych niespodzianek.
Dla wygody zebrałem poniżej wszystkie znaczniki, jakich użyłem w dokumentacji identikon:

Przy okazji budowania dokumentacji natrafiłem na błąd, który niezwłocznie
zgłosiłem.
Nie minęło 5 godzin jak w repozytorium PLT pojawiła się poprawka. To się
nazywa dobry support w projekcie open-source!
Lista dyskusyjna
również działa bardzo sprawnie i wcale nierzadko można otrzymać odpowiedź
od samego Matthiasa Felleisena,
współautora m.in. wspomnianego wcześniej
“Małego Schemera”.
Wokół PLT istnieje aktywna społeczność, z którą warto się zapoznać. Do usłyszenia więc
na plt-scheme!

Implementacja identikon w PLT Scheme

Gdy w sieci href="http://www.docuverse.com/blog/donpark/2007/01/18/visual-security-9-block-ip-identification">przeczytałem
o identikonach o wiele bardziej urzekła mnie estetyka ich wyglądu
niż ich techniczne zalety. Identikony, w formie zaimplementowanej
przez Dona Parka, służyć mają jako graficzna reprezentacja tożsamości
osoby w sieci. Założenie jest takie, że kolorowe wzory są łatwiejsze
do porównania i zapamiętania przez człowieka, niż numer IP. Dodatkowo,
przepuszczając IP użytkownika przez funkcję haszującą z sekretnym
ziarnem możemy zachować właściwości identyfikujące bez potrzeby
ujawniania samego numeru IP.


Będąc zainteresowany samymi wzorami, szybko odnalazłem href="http://www.docuverse.com/blog/donpark/2007/01/19/identicon-updated-and-source-released">opis
sposobu ich generowania i zacząłem zastanawiać się nad
implementacją. Pomyślałem bowiem, że to świetna okazja na wypróbowanie
możliwości Scheme i bliższe poznanie wybranego środowiska. Padło na href="http://www.plt-scheme.org/">PLT, głównie ze względu na moją
wcześniejszą z nim styczność i dobre doświadczenia wyniesione z eksperymentów w
DrScheme.

Jak się później dowiedziałem, bloczki, z których zbudowane są
identikony, zostały początkowo href="http://www.levitated.net/daily/lev9block.html">opisane przez
Jareda Tarbella
na podstawie mającej długą historię sztuki href="http://en.wikipedia.org/wiki/Quilting">quiltu. A to z kolei
pamiętam, że gdzieś
już widziałem
.

Przede wszystkim zachęcam do ściągnięcia
i wypróbowania kodu
. Do prawidłowego działania wymaga on
PLT z serii 3.99
(lub 4.0, który ma
wyjść całkiem niedługo).
Poniżej zaś kilka komentarzy i wniosków, które nasunęły mi się podczas
implementacji.

Bibliotekę wrzuciłem na Planetę PLT, by jej użyć wystarczy więc jedna linijka kodu:

(require (planet "identicons.ss" ("mk" "identicons.plt")))

Implementacja identikon zostanie automatycznie ściągnięta i zainstalowana.

System obiektowy

System obiektowy PLT Scheme funkcjonalnie nie różni się bardzo od tych
zawartych w językach takich jak Java, Python, czy Ruby. Filozofia jest
jednak trochę inna, głównie z tej przyczyny, że system obiektowy nie
jest podstawą języka, ale jednym z wielu sposobów organizacji programu.
Po załadowaniu modułu scheme/class nie doświadczymy więc
wielkich zmian. Podstawowe typy danych, takie jak liczby, symbole, czy
ciągi znaków nie zostają wciągnięte w hierarchię klas. Prawdę mówiąc,
poza klasą
object%
i interfejsem
externalizable<%>
hierarchia ta jest całkiem pusta i zadanie wypełnienia jej leży
w rękach programisty. Po bibliotekach standardowych pękających od
podstawowych klas i obiektów jest to całkiem odświeżające doświadczenie,
muszę przyznać. W założeniu twórców PLT obiekty mają służyć do sprawnego
zamodelowania dziedziny problemu, przed jakim staje programista. A do
tego nie potrzeba bagażu historycznego jaki często niosą ze sobą hierarchie
starszych języków obiektowych.

Podstawowe konstrukcje są dość dobrze opisane w
dokumentacji,
a pomiędzy liniami można też poznać kilka konwencji. Nazwy klas kończą się
%, a nazwy interfejsów <%>. W obrębie
wyrażenia definiującego klasę
dostępnych jest kilka form specjalnych służących do definiowania atrybutów
i metod. Do komfortowej pracy wystarczy znajomość czterech z nich:

  • init-field do definiowania atrybutów, które może ustawić użytkownik podczas tworzenia obiektu
  • field do definiowania pozostałych atrybutów
  • define/public do definiowania publicznych metod
  • define do definiowania metod prywatnych

Dla przykładu przyjrzyjmy się implementacji identikony:

(define identicon%
(class object%
(init-field seed)

(field (32-bits (make-bit-stream seed))
(center-patch-shape (list-ref center-patch-shapes (32-bits 2)))
(side-patch-shape (list-ref patch-shapes (32-bits 4)))
...)

(define (get-patch-size dc)
...)

(define/public (draw dc)
...)

(super-new)))

Kod zdaje się mówić sam za siebie. seed jest atrybutem
ustawianym podczas konstrukcji obiektu. 32-bits,
center-patch-shape i side-patch-shape
są ukrytymi atrybutami przyjmującymi zadane wartości. Warte zauważenia
jest to, że 32-bits zależy od atrybutu seed,
podczas gdy samo 32-bits jest używane w następujących
po nim definicjach. get-patch-size jest metodą dostępną
tylko dla pozostałych metod tej klasy (czyli jest metodę prywatną),
a draw jest metodą publiczną. Wywołanie (super-new)
kończące definicje klasy jest potrzebne do właściwej inicjalizacji
obiektu przez nadklasę object%.

Konstrukcja obiektów jest bezbolesna. Przykładowo, by utworzyć nową identikonę
wystarczy napisać:

(make-object identicon% 1234567890)

i dostaniemy z powrotem identikonę pokazaną poniżej. By zapisać identikonę
do pliku, wystarczy wywołać na niej metodę save-to-file z podaniem
nazwy pliku i rozmiarem identikony w pikselach:

(send identicon save-to-file "1234567890.png" 200)


O ile do tego momentu nie miałem większych zastrzeżeń do systemu obiektowego
PLT to do sposobu wywoływania metod muszę się już przyczepić. :-)
System obiektowy z przekazywaniem komunikatów (ang.
message passing)
moim zdaniem idzie trochę na przekór lispowej filozofii. Common Lispowe procedury
ogólne (ang. generic procedures)
nie dość, że potężniejsze, to moim zdaniem bardziej idą z duchem Lispa. Pisanie kodu
podobnego do:

(send identicon draw 30)

wydaje się nienaturalne, umieszczając nazwę wykonywanej operacji dopiero na
trzeciej pozycji wyrażenia. Naturalną składnią dla wołania metody draw
jest następująca postać powyższego wyrażenia:

(draw identicon 30)

Co prawda, można by zacząć dodawać dla wszystkich metod publicznych
odpowiadającą im procedurę, np.:

(define (draw object . args)
(send/apply object draw args))

i może nawet powiązać ten efekt z define/public, ale wtedy
mielibyśmy już nowy system obiektowy, łamiący świętą w świecie Scheme
makrową higienę.
Oczywiście świat nie jest czarno-biały i istnieją zalety podejścia
używanego przez PLT. Jednym z niewątpliwych plusów jest fakt, że jedynym
symbolem, jaki trzeba eksportować z modułu jest nazwa klasy - nazwy
metod bowiem rozwijane są jedynie w kontekście konkretnego obiektu.
Metody “podążają za” obiektami, co w systemie z przekazywaniem komunikatów
ma sens.

W PLTowym pakiecie Swindle
jest dostępny system obiektowy bliższy Common Lispowemu CLOSowi, będzie
więc jeszcze okazja przyjrzeć się innemu podejściu.

Inną ciekawostką związaną ze standardowym PLTowym systemem obiektowym jest
to, że klasy same nie są obiektami:

(object? identicon%) ;; => #f

Nie możemy więc na nich wywoływać metod i definiować atrybutów. Nie jest to
jednak potrzebne, w PLT istnieją bowiem moduły, w których możemy zawrzeć
zarówno lokalny stan, jak i grupę procedur. O zabawach z metaklasami możemy
jednak zapomnieć (co prawdopodobnie językowi wychodzi na zdrowie ;-).

Moduły i kontrakty

Wraz z nadejściem R6RS doczekaliśmy się
standardowego
wsparcia dla modułów (bibliotek) w Scheme
, zanim jednak ten czas nastąpił
każda z implementacji musiała rozwiązać ten problem po swojemu. Moduły w PLT,
w ograniczonym zakresie jakim miałem okazję ich używać, spełniają swoją funkcję
doskonale. By obudować całą zawartość pliku w moduł wystarczy dodać na jego
początku linijkę z określeniem używanego podzbioru języka przy pomocy
#lang.
Przykładowo, by użyć języka rozszerzonego o
możliwości operowania oknami i grafiką
należy użyć:

#lang scheme/gui

Osiągamy w ten sposób efekt podobny do modułów pythonowych - każdy plik
stanowi moduł, którego nazwa wywodzi się bezpośrednio z nazwy pliku,
z pominięciem rozszerzenia. Plik identicons.scm zawiera
więc moduł o nazwie identicons.

Prócz tej jednej linijki moduł prawdopodobnie nie będzie wymagał żadnych zmian.
require
wciąż działa, a wszystkie definicje są szczelnie zamknięte dla zewnętrznego
świata. Udostępnianie symboli na zewnątrz odbywa się przy pomocy formy
specjalnej
provide,
ja jednak od pierwszego wejrzenia zakochałem się w formie
provide/contract.
Przy jej pomocy bowiem możemy nie tylko określić eksportowane symbole, ale
również określić
kontrakt,
który będzie obowiązywał pomiędzy modułem, a jego użytkownikiem. Przykładowo,
kontrakt funkcji degrees->radians zdefiniowanej w module
util wygląda następująco:

[degrees->radians (number? . -> . number?)]


Oznacza on, że degrees->radians jest funkcją, która oczekuje
liczbę jako argument i obiecuje zwrócić liczbę. Proste. Jak widać, kontrakty
mogą służyć do dynamicznego sprawdzania typów, ale ich możliwości sięgają
daleko poza. Kontrakt można zbudować z dowolnego predykatu, a te mogą
sprawdzać nie tylko typ, ale i wartości i stan obiektów przekraczających
granice kontraktu. Świetne w kontraktach jest to, że są całkowicie opcjonalne.
Nie ma więc żadnego problemu w tym, by dojrzalsze części budowanego przez nas
systemu posiadały kontrakty i jednocześnie współpracowały z nowszymi modułami,
których projekt wciąż jest niejasny - a przez to - pozbawiony kontraktów. Poza
oczywistymi względami technicznymi kontrakty posiadają również niewątpliwą
wartość dokumentacyjną. Odpowiednio dobrane nazwy predykatów mogą wiele
powiedzieć o oczekiwaniach, jakie kładziemy na argumenty funkcji
i o obietnicach jakie składamy co do wartości przez te funkcje zwracanych.

Co ciekawe, kontrakty można stosować nie tylko w kontekście modułów, ale
również
bezpośrednio do definicji,
jak i
do metod i atrybutów obiektów.

Składnia infixowa

Jak wspomina przypis na stronie dokumentacji kontraktów
PLT Scheme udostępnia specjalną
składnię z dwoma kropkami.
Jej działanie jest bardzo proste. Każde wyrażenie o postaci:

(a b . x . c d)

przekształcane jest na:

(x a b c d)

jeszcze przed interpretacją. Oprócz tego, że składnia ta jest bardzo przydatna
przy definiowaniu kontraktów, można ją (nad)użyć chociażby do pisania wyrażeń
algebraicznych. Przykładowo:

(a . * . (b . + . c))

jest tym samym co:

(* a (+ b c))

Osobiście jednak odradzałbym posuwanie się z użyciem tego składniowego lukru
zbyt daleko. ;-)

Narzędzia graficzne

Moje zainteresowanie grafiką w PLT zaczęło się od
tego wspaniałego tutoriala wstępnego.
Polecam go tym, którzy chcą zacząć przygodę z Lispem, ale nudzi ich operowanie
wyłącznie na liczbach. A w zasadzie to polecam go każdemu, tak
dobry jest. :-)

Niestety do implementacji identikon
biblioteka prezentacyjna
jest niewystarczająca. Głównym jej minusem jest brak możliwości obracania
obiektów graficznych. Indeks szybko jednak zaprowadził mnie do
metody rotate klasy dc-path%,
która to stanowi część
większej biblioteki graficznej
dostępnej wraz z PLT.
Klasa dc-path% umożliwia
narysowanie, wypełnienie i manipulacje dowolnym kształtem, złożonym z odcinków,
czy łuków. Konstrukcja takiej ścieżki jest bardzo prosta. Przykładowo, by narysować
trójkąt najpierw tworzymy nowy obiekt dc-path%:

Cały poniższy kod musi
być uruchamiany z linii poleceń mred lub w środowisku
drscheme; funkcjonalność graficzna nie jest dostępna z poziomu
mzscheme.

(define path (make-object dc-path%))

Po tym określamy punkt startowy (”punkt przyłożenia ołówka do kartki”):

(send path move-to 0 0)

i zaczynamy rysować jedną z dostępnych metod, przykładowo
line-to:

(send path line-to 100 100)
(send path line-to 0 100)
(send path line-to 0 0)

Żeby zobaczyć efekt musimy przygotować odpowiedni kontekst do rysowania
(ang. drawing context), czyli w praktyce cokolwiek, co implementuje
interfejs dc<%>.
Skorzystajmy z kodu identikon i użyjmy do tego celu zwykłej ramki (czy też
“okna”):

(define (make-frame-dc)
(let* ((frame (new frame% (label "New frame") (width 200) (height 200)))
(canvas (new canvas% (parent frame))))
(send frame show #t)
(sleep/yield 1)
(send canvas get-dc)))

Utwórzmy więc właściwy obiekt i narysujmy na nim nasz trójkąt:

(define dc (make-frame-dc))
(send dc draw-path path)

W tym momencie na ekranie powinniśmy zobaczyć zarys trójkąta. Pokolorowanie go
to tylko kwestia użycia odpowiedniego pędzla. Wybierzmy dla niego jakiś pozytywny
kolor i przerysujmy nasz trójkąt ponownie (oczywiście wszystko bez restartowania
interpretera, czy otwierania nowej ramki):

(send dc set-brush "green" 'solid)
(send dc draw-path path)

Gotowe, nasz trójkąt jest teraz zielony. A stąd już niedaleko do generowania
kolorowych identikon. Po szczegóły zapraszam do
źródeł.

Wnioski

Podsumowując, PLT jest dojrzałą i bogatą w rozszerzenia implementacją języka
Scheme. Dzięki obszernej i dokładnej dokumentacji łatwo się jej nauczyć
i korzystać z jej możliwości. Dla tych, którzy wolą zacząć od łagodniejszej niż
strony dokumentacji lektury jest blisko z PLT związana książka
“How to Design Programs”. Twórcy PLT nie
boją się eksperymentować - dystrybucja PLT pełna jest
ciekawych
minijęzyków
i odważnych
pomysłów, jest więc prawdziwą kopalnią wiedzy
dla zainteresowanych możliwościami Scheme i implementacją języków programowania ogólnie.Viagra versus levivia
Feldene
Women using viagra
Accutane
Xanax and pregnancy
Information phentermine
Indomethacin
Adipex phentermine xenical
Cefamandole
Adenosine
Lanoxin
Tramadol cod
Oxprenolol
Methylergonovine
Ibuprofen
Nicorette
Pulmonary hypertension and viagra
Probucol
Cialis drug impotence
Cialis impotence drug eli lilly co
Lortab and xanax without a prescription
Levivia viagra compared
Meropenem
Viagra herbal alternative
Saccharin
Flexeril
Colace
Buy phentermine online same day delivery
Mecamylamine
Phentermine dangers
Info on meridia
Aldara
Cheap price on phentermine
Reglan
Purchase xanax
Hydrocodone ap ap
Cialis soft
Levoxyl
Buy phentermine with no prescription
Pyridoxine
Cialis drug for impotence
Order buy phentermine online
Metronidazole
Pharmacy phentermine sister
Ipratropium
Fioricet
Clomocycline
Cheap pharmacy viagra
Pentaerythritol
Buy hydrocodone overnight
Xanax dosage
Amiodarone
10 min viagra
Voltaren
Viagra drug
Low natural resources for the drug phentermine
Phentermine side effects
Order cheap phentermine
Paxil
Lowest price on phentermine
Lexapro interaction with phentermine
Cialis price
Zithromax
Lotrel
Arava
Decamethonium
Arthrotec
Approval cialis
Cope
Levivia and viagra
Xanax for sale
Adipex diet phentermine pill prescription
Potassium
Buy cialis viagra
Pain medication tramadol
Diet ingredient phentermine pill
Dangers of phentermine heart
Terbutaline
Saturday delivery phentermine
Killer pain tramadol
Ambien rx
Cardizem
Viagra sample pack
Cheap tramadol prescriptions online
Meridia weight loss pill
Isoniazid
Cod online pharmacy phentermine sell
Phentermine in florida
Generic viagra overnight delivery
Dexbrompheniramine
Hydrocodone picture
Canadian cialis
Cialis sales uk
Lopressor
Xanax no rx
Xanax death
About phentermine
Cialis immunity
Viagra kaufen
Buy generic viagra online
Phentermine hc
Podophyllum
Ipodate
Octreotide
Real phentermine
Xanax pictures
Kaopectate
Lamivudine
Herbal alternative to viagra
Phentermine blue 30 mg
Valsartan
Anafranil
Norgestrel
Lansoprazole
Tramadol online pharmacy
Budesonide
Phentermine blogging
Mephenytoin
Ditropan
Order viagra now
Keflex
Phentermine with no prescription
Difference between cialis and viagra
Ambien overdose
Tramadol hydrochloride tablet
Cialis review
Phentermine 37.5 mg free shipping
Online pharmacies with doctor consultation for viagra
Busulfan
Phentermine prescribed online
Buying tramadol online
Levivia viagra online
Phentermine worldwide shipment
Phentermine ephedrine
Estrone
Phentermine 37.5 mg sale
Viagra commercial
Nasalcrom
Shipping overnight phentermine
Phentermine without doctor’s approval
Actonel
Phentermine credit card or cod
_cialis et levitra
How long does xanax stay in your body
Ethotoin
Where can i buy phentermine
Buy Atarax
Zidovudine
Order soma carisoprodol
Macrodantin

« Wcześniejsze wpisy ·