Scheme dla ka??dego
październik 26th, 2008 at 1:46 pm (scheme, haskell)
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) = valextractValue 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.