Implementacja identikon w PLT Scheme
czerwiec 6th, 2008 at 5:10 pm (scheme, plt)
Gdy w sieci 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 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 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 opisane przez Jareda Tarbella na podstawie majÄ?cej d??ugÄ? historiÄ? sztuki 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??.