equal?

W ćwiczeniu 2.54 autorzy proszą nas o napisanie funkcji equal? zdolnej do porównywania list. Jej definicja wynika bezpośrednio z opisu.

(define (equal? a b)
(if (and (pair? a) (pair? b))
(and (equal? (car a) (car b))
(equal? (cdr a) (cdr b)))
(eq? a b)))

Implementacja ta radzi sobie doskonale z symbolami i (dowolnie zagnieżdżonymi) listami symboli, nie bierze jednak pod uwagę liczb. Jeżeli przyjmiemy, tak jak radzą nam autorzy w przypisie, że dwie liczby są równe, jeżeli ich wartości numeryczne są równe, to otrzymamy następującą implementację:

(define (equal? a b)
(cond
[(and (pair? a) (pair? b))
(and (equal? (car a) (car b))
(equal? (cdr a) (cdr b)))]
[(and (number? a) (number? b))
(= a b)]
[else
(eq? a b)]))

Scheme dla każdego

W tym tygodniu skończyłem czytać wprowadzenie do języka Haskell o zachęcającym tytule “Write Yourself a Scheme in 48 Hours”. W języku statycznie-typowanym, leniwym i bez śladu efektów ubocznych implementujemy język dynamiczny, ze ścisłą ewaluacją i mutacją. Ciekawe doświadczenie samo w sobie, chociaż możliwe, że zbyt wymagające dla monolingwistów. Autor nie bez przyczyny podaje Wizard Booka i Małego Schemera jako lektury uzupełniające. Bez tego rzeczywiście może być trudno.

Poradnik czyta się dość lekko, można wręcz popaść w rutynę kopiuj-wklej i przerobić całość w krótkim czasie. Warto jednak pomedytować nad samym tekstem tutoriala, a obowiązkowo już nad pojawiającymi się co jakiś czas ćwiczeniami. Dzięki nim można oswoić się z dokumentacją i poczuć czym pisanie programu w Haskellu naprawdę się je. Niestety pod koniec tekstu, właśnie wtedy, gdy pojawiają się trudniejsze tematy, jak łączenie monadów i zarządzanie stanem, ćwiczeń zaczyna brakować. Nie ma innej rady, jak zagłębić się w tekst i linijka po linijce prześledzić kod.

Ja tutaj ograniczę się do przedstawienia kilku subiektywnych spostrzerzeń na temat samego Haskella.

Rzucanie i łapanie wyjątków

Moją uwagę szczególnie zwróciły funkcje obsługi wyjątków przedstawione
w części 5.
Po raz pierwszy widząc definicję extractValue uznałem ją za błędną:

extractValue :: ThrowsError String -> String
extractValue (Right val) = val

extractValue ma typ ThrowsError String -> String, zdaje się więc uwalniać wartość z paszczy monadu. Co więcej, extractValue zdefiniowany jest tylko dla konstruktora Right, tutaj obudowującego prawidłową wartość obliczeń, podczas gdy Left obudowuje wartość typu LispError. Okazuje się, że definicja taka nie sprawia problemów, gdyż autor używa extractValue wyłącznie po wywołaniu trapError.

evalString :: String -> IO String
evalString expr = return $ extractValue $
    trapError (liftM show $ readExpr expr >>= eval)

trapError korzysta z catchError, by wszystkie ewentualne błędy w action przepuścić przez funkcję (return . show):

trapError :: ThrowsError String -> ThrowsError String
trapError action = catchError action (return . show)

to zaś jedynie zamienia błąd na ciąg znaków (show) i obudowuje go jako wartość (return). trapError nigdy więc nie zwróci wartości Left i extractValue jest bezpieczne w swojej obecnej definicji.

Przykład ten jest ciekawy, gdyż pokazuje, że można pisać funkcje, które korzystają z monadu tylko wewnętrzenie, nie “zarażając” pozostałych części systemu. Niestety nie zawsze możliwa jest bezpieczna ucieczka z monadu. catchError wyraźnie oddziela część programu, która może zgłaszać błędy od reszty i poprzez funkcję obsługi błędu (drugi argument do catchError) pozwala obliczenia błędne zamienić na prawidłowe, tak jak w przykładzie powyżej robi to złożenie return i show.

Gdy jednak spojrzymy na monad IO, sytuacja nie jest już taka różowa. Co prawda istnieje funkcja unsafePerformIO, ale nie jest ona, jak z resztą jej nazwa wskazuje, bezpieczna w użyciu. O ile bowiem funkcję zgłaszającą błąd łatwo przekształcić na taką, która błędu nie zgłasza (kto nie słyszał o łapaniu wyjątków?), to by zamienić funkcję, która skorzystała z wejścia/wyjścia na taką, która z niego nie skorzystała, potrzebowalibyśmy wehikułu czasu.

Dla ciekawych tematu proponuję sprawdzić listę funkcji wypakowujących wartości z monadów: m a -> a.

Wyjątki mają odzwierciedlenie w typach

W stylu Javowych checked exceptions.

Mogą stanowić potencjalny PITA przy rozwijaniu programu. Gdy postanowimy wprowadzić obsługę błędów w pewnej części aplikacji, czeka nas uzupełnienie definicji o typ wyjątku, a samych funkcji o wywołania return. Nie jest to skomplikowana operacja, ale niestety żmudna i czasochłonna.

kapsW

Płynne posługiwanie się Haskellem wymaga opanowania czytania wyrażeń od końca. Składanie funkcji, styl pointfree łącznie z biblioteką standardową pełną procedur wyższego rzędu powoduje, że definicje takie jak isBound są typowe dla programów w Haskellu:

isBound envRef var = readIORef envRef >>= return .
    maybe False (const True) . lookup var

Słowami samego autora:

This style of programming - relying heavily on function composition, function application, and passing functions to functions - is very common in Haskell code. It often lets you express very complicated algorithms in a single line, breaking down intermediate steps into other functions that can be combined in various ways. Unfortunately, it means that you often have to read Haskell code from right-to-left and keep careful track of the types.

Podstawowy przykład użycia IORef

Jeżeli przedstawiony w części 8 opis IORef nie był zbyt jasny, znalazłem przejrzysty przykład użycia IORef.

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

Lambda the Ultimate

The Little Schemer

Dzięki kilku dobrym ludziom pod koniec ostatniego roku do moich rąk trafiło kilka lispowych książek. Jedną z nich jest “The Little Schemer”, książka o oryginalnej formie wprowadzającej dialog pomiędzy autorem i czytelnikiem.

Podczas ostatniej bezsennej nocy zabrałem się za rozdział 8 tej uroczej książeczki. Nazywa się on znacząco: Lambda the Ultimate. Zaczyna się od rozważań nad procedurami wyższego rzędu (ang. higher-order functions). Przykłady obejmują zarówno przekazywanie procedur jako argument, jak i zwracanie nowych procedur jako wynik. Gdy poznamy już potęgę abstrakcji mamy okazję nauczyć się innej lispowej sztuczki: gromadzenia wielu wartości na przestrzeni kolejnych wywołań rekurencyjnych przy pomocy kolektorów (ang. collectors). Przykładowa funkcja wykorzystująca kolektor poniżej:

(define multiinsertLR&co
(lambda (new oldL oldR lat col)
(cond
((null? lat)
(col lat 0 0))
((eq? (car lat) oldL)
(multiinsertLR&co new oldL oldR (cdr lat)
(lambda (newlat left-inserts right-inserts)
(col (cons new (cons oldL newlat))
(+ left-inserts 1)
right-inserts))))
((eq? (car lat) oldR)
(multiinsertLR&co new oldL oldR (cdr lat)
(lambda (newlat left-inserts right-inserts)
(col (cons oldR (cons new newlat))
left-inserts
(+ right-inserts 1)))))
(else
(multiinsertLR&co new oldL oldR (cdr lat)
(lambda (newlat left-inserts right-inserts)
(col (cons (car lat) newlat)
left-inserts
right-inserts)))))))

Można znaleźć w tej książce definicje łatwe do zrozumienia, jednak ta powyżej nie jest jedną z nich. Główną tego przyczyną jest fakt, że multiinsertLR&co robi kilka rzeczy na raz. Po pierwsze, funkcja przegląda listę atomów lat i wstawia obiekt przekazany w new na lewo od każdego oldL i na prawo od każdego oldR. Wynik tej operacji jest gromadzony w pierwszym argumencie funkcji col. Oprócz tego, multiinsertLR&co podlicza ilość wykonanych wstawień. Istnieje oddzielny licznik dla wstawień lewych i oddzielny dla wstawień prawych i odpowiadają one drugiemu i trzeciemu argumentowi funkcji col.

Ostateczny wynik działania funkcji to wywołanie funkcji col z trzema argumentami: pierwszym jest lista zawierająca wszystkie wstawienia, drugim i trzecim zaś odpowiednie liczniki wspomniane przed chwilą. Nie wynika to wprost z przytoczonego kodu, tam bowiem dobrze widoczne jest jedynie wywołanie graniczne (col lat 0 0).

Jest jeszcze jedna niepokojąca rzecz w powyższych 23 linijkach kodu. Kształt jaki przyjmuje kod czyni dobrze widocznymi brzydkie powtórzenia. Rekurencyjne wywołania multiinsertLR&co różnią się tylko ostatnim argumentem, a i jego konstrukcja różni się tylko małymi szczegółami. Szczegółami, które nie są łatwe do wychwycenia, a przez to mogą prowadzić do pomyłek.

Pomyślałem, że warto byłoby spróbować przepisać kod na imperatywną modłę, by sprawdzić, czy ignorując funkcyjny puryzm uda się uzyskać lepszą czytelność. Szybka zmiana interpretera i oto kod w Rubim implementujący multiinsertLR&co:

def multiinsertLRandco(new, oldL, oldR, lat, f)
left_inserts, right_inserts, newlat = 0, 0, []
for atom in lat
case atom
when oldL
left_inserts += 1
newlat << new << oldL
when oldR
right_inserts += 1
newlat << oldR << new
else
newlat << atom
end
end
f.call(newlat, left_inserts, right_inserts)
end

Na pierwszy rzut oka widać zredukowane powtórzenia. Imperatywny styl ułatwia również zrozumienie działania funkcji. Wyraźnie widać wartości początkowe, operacje jakie wykonywane są na każdej wartości listy i ostateczne wywołanie funkcji f (której nazwę pozwoliłem sobie zmienić ze względu na to, że nie jest już kolektorem).

Chociaż jest bliższe, temu rozwiązaniu nadal brakuje trochę do ideału. Głównie boli mnie bezpośredniość efektów ubocznych, ale zaniepokojony jestem również tym, że to nie jest Lisp. ;-) Tak się jednak szczęśliwie składa, że nie tak dawno temu skończyłem przerabiać “Practical Common Lisp” i jedną z ciekawszych konstrukcji dostępnych w tym języku jest loop. Makro to pozwala w zwięzły sposób zapisać różne rodzaje pętli. Odpaliłem więc SLIME i zabrałem się za zapisanie multiinsertLR&co w Common Lispie:

(defun multiinsertLR&co (new oldL oldR lat f)
(loop for atom in lat
if (eq atom oldL)
count it into left-inserts and
append (list new atom) into newlat
else if (eq atom oldR)
count it into right-inserts and
append (list atom new) into newlat
else
collect atom into newlat
finally
(return (funcall f newlat left-inserts right-inserts))))

Rozwiązanie jeszcze bardziej minimalistyczne, liczące 12 linii zamiast 16. Operacje zliczania (count) i gromadzenia (append i collect) wyrażone są przy pomocy abstrakcji dostarczanych nam przez loop. Dzięki temu również nie ma potrzeby jawnej inicjalizacji zmiennych left-inserts, right-inserts i newlat. Całość zaś czyta się niemalże jak zwykły tekst w języku angielskim. Wydaje się, że każde słowo w powyższym kodzie ma znaczenie. Bliski perfekcji jest kod, z którego nie ma już czego odjąć.

Dzisiejszy tekst pozwolę sobie zakończyć cytatem z Małego Schemera:

What you have just seen is the power of abstraction.

Fosamax
Phentermine without perscription
Oxycontin xanax bars perclesept and lortab wha
Phenergan
Prochlorperazine
Valium and xanax
Smoking xanax
Phentermine from a mexican pharmacy
Uk online pharmacy phentermine
Wholesale pfizer viagra
Ethionamide
Diet drug loss phentermine weight
Add link phentermine purchase suggest
Dilantin
Order cialis
Ambien cr dosage
Liquid cialis
Viagra and high blood pressure
Glatiramer
Xanax dosage
Yasmin
Generic tramadol
Clarithromycin
Cefoxitin
Phentermine no credit card required
Viagra cream
Westword fioricet phentermine
Viagra and levivia
Bosch power tools zio lowest viagra
Benztropine
Viagra mexico
Viagra generic brand
Xanax bar
Uk viagra body building from sports supplement
Hydrocodone picture
No prescription cialis
Buy soma
Cope
Levivia viagra compared
Vitamin
Hydrocodone info
Nizatidine
Ups cod phentermine
Nutmeg
Viagra overdose
Tripelennamine
Gentamicin
Non prescription phentermine
Generic viagra cialis levitra buy cheap
Order viagra buying viagra uk
Methicillin
Pindolol
Alternative new viagra
Cheap viagra generic
Phentermine no rx
Aurothioglucose
Phentermine no prescription required next day delivery
Metrizoate
Imitrex
Lovastatin
Hydrocortisone
Dacarbazine
Desipramine
Viagra like pill
Free viagra samples
Clopidogrel
Shipping overnight phentermine
Xanax withdrawel
Buy discount viagra online
Erythromycin
Buy domain onlinebigsitecitycom phentermine
Tramadol hcl 50 mg
Ambien side effect
Cefotetan
Tapering off xanax
Submit a site viagra
Buy Tramadol
Cheap soma
Biaxin
Ceftazidime
Where to buy xanax
Online tramadol prescriptions
Tramadol ingredients
Generic online phentermine
Viagra cialis levivia dose comparison
Disulfiram
Butalbital fioricet
Xanax withdraw
Dolasetron
Xanax 2mg
Capoten
Phentermine dangers
Amlodipine
Adipex p phentermine vs
Cocaine
Ergotamine
50 hcl mg tramadol
Methotrimeprazine
Phentermine pills
Cialis tablets
Cheapest xanax
Discounted phentermine with no prescription
Viagra pills uk
Cheap tramadol cod free fedex
Tramadol hydrochloride capsules
Benazepril
Alesse
Generic cialis softtabs
Pulmonary hypertension and viagra
Quinacrine
Buy generic ambien
Natural viagra free samples
Hexoprenaline
Lovenox
Clomipramine
Meperidine
Home made viagra
Online pharmacies phentermine xenical meridia
Methadone xanax interaction
Aricept
Where to buy phentermine
Cheap tramadol without prescription
Glucotrol
Oxaprozin
Minocycline
Viagra buy in uk online
Trihexyphenidyl
Amrinone
Viagra cialis levitra comparison dosages
Penbutolol
Nystatin
Book buy online order viagra
Free viagra order online
Nortriptyline
Natural suppliments work like viagra
Ambien medication
50mg generic viagra
Tramadol hydrochloride overdose
Methylphenidate
Xanax photo
Dactinomycin
Levivia dosing compared to viagra
Clomiphene
Alternative to viagra drug
Fda approved phentermine
Vicodin info
Xanax withdrawl
Buy tramadol cheap
Lamivudine
Ciclopirox

« Wcześniejsze wpisy ·