Funktory, funktory aplikatywne, MONoidy

Slides:



Advertisements
Podobne prezentacje
Teoria układów logicznych
Advertisements

Wstęp do strumieni danych
Instrukcje - wprowadzenie
C++ wykład 2 ( ) Klasy i obiekty.
Język C/C++ Funkcje.
Programowanie obiektowe
C++ wykład 13,14,15 (16/23/ ) STL.
Deklaracje i definicje klas w C++ Składowe, pola, metody Konstruktory
Programowanie obiektowe
Wzorce.
Język ANSI C Funkcje Wykład: Programowanie komputerów
Prowadzący: mgr inż. Elżbieta Majka
PROGRAMOWANIE STRUKTURALNE
Materiały do zajęć z przedmiotu: Narzędzia i języki programowania Programowanie w języku PASCAL Część 7: Procedury i funkcje © Jan Kaczmarek.
Materiały do zajęć z przedmiotu: Narzędzia i języki programowania Programowanie w języku PASCAL Część 5: Typy porządkowe, wyliczeniowe i okrojone. Definiowanie.
Typy pochodne 1 Często dogodnie jest wprowadzić nowy typ, który jest podobny do istniejącego, niemniej jednak różny. Niech T będzie pewnym typem. Możemy.
Tablice jednowymiarowe 1
Rekordy 1 Definicja Typ strukturalny nazywamy typem rekordowym, jeżeli zawiera pewną liczbę nazwanych składowych, które mogą być różnych typów. Dostęp.
Klasy w C++. Deklaracja klasy class NazwaTwojejKlasy { //w tym miejscu piszemy definicje typów, //zmienne i funkcje jakie mają należeć do klasy. }; //tutaj.
Pakiety w Javie Łukasz Smyczyński (132834). Czym są pakiety? Klasy w Javie są grupowane w pewne zbiory zwane pakietami. Pakiety są więc pewnym podzbiorem.
Podstawy programowania II Wykład 2: Biblioteka stdio.h Zachodniopomorska Szkoła Biznesu.
Funkcje w Pascalu Przypomnienie wiadomości o procedurach Prowadzący: Anna Kaleta Piotr Chojnacki.
ANNA BANIEWSKA SYLWIA FILUŚ
Elementy Rachunku Prawdopodobieństwa i Statystyki
Programowanie obiektowe – zastosowanie języka Java SE
Andrzej Repak Nr albumu
Java – coś na temat Klas Piotr Rosik
Dziedziczenie Maciek Mięczakowski
Inicjalizacja i sprzątanie
Programowanie obiektowe Wykład 3 dr Dariusz Wardowski, Katedra Analizy Nieliniowej, WMiI UŁ 1/21 Dariusz Wardowski.
XML – eXtensible Markup Language
Przekazywanie parametrów do funkcji oraz zmienne globalne i lokalne
Programowanie obiektowe – język C++
Wykład 10 typ zbiorowy rekurencja.
Kurs języka C++ – wykład 9 ( )
PL/SQL – dalsza wędrówka
Programowanie w języku C++
Projektowanie stron WWW
Aplikacje internetowe
Programowanie strukturalne i obiektowe C++
Model obiektowy bazy danych
Kurs języka C++ – wykład 4 ( )
Krakowski Piotr, Woliński Radosław, Kowalski Piotr, Machowski Michał.
Haskell. Dopasowanie do wzorca Jest to operacja, gdzie pewnie wyrażenie sprawdza się ze wzorcem, w którym może znajdować się jedno lub więcej "wolnych.
Formatowanie dokumentów
I TY ZOSTAŃ WEBMASTEREM! CZĘŚĆ 2 – „STRUKTURA STRONY” STWORZYŁ GABRIEL ŚLAWSKI.
Programowanie Zaawansowane
Tryby adresowania i formaty rozkazów mikroprocesora
Mapa STL – C++. Problem polega na tym, że najczęściej chcielibyśmy przechowywać w zbiorze elementy jakiegoś bardziej złożonego typu, których on nie będzie.
Visual Basic przygotował Michał Miłek Visual Basic – język programowania wysokiego poziomu i narzędzie programistyczne firmy Microsoft. Składnia jest oparta.
Testy jednostkowe. „Test jednostkowy (unit test) to fragment kodu, który sprawdza inny fragment kodu”
Types and Typeclasses Syntax in Functions
Haskell - Modules.
Rekurencja - Haskell Bartosz Pawlak Sebastian Żółtowski Adam Stegenda Krystian Sobótka Tomasz Gołębiewski.
Strumienie, Wczytywanie, Zapisywanie, Operacje na plikach
Funkcje wyższego rzędu Moduły
Typy wyliczeniowe, kolekcje
Rozdział 5 REKURENCJA.
Funkcje wyższego rzędu
Delegaty Delegat to obiekt „wiedzący”, jak wywołać metodę.
Programowanie Obiektowe – Wykład 2
Projekt modułu BANK INTERNETOWY Moduł funkcji banku
Funkcje wyższego rzędu
Functors, Applicative Functors and Monoids
Rekurencja Ponieważ w Haskellu nie mamy klasycznych pętli for czy while dlatego w wielu algorytmach musimy używać rekurencji.
Haskell Składnia funkcji.
Język C++ Typy Łukasz Sztangret Katedra Informatyki Stosowanej i Modelowania Prezentacja przygotowana w oparciu o materiały Danuty Szeligi i Pawła Jerzego.
PGO Dziedziczenie Michail Mokkas.
SHA1 – Secure Hash Algorithm
Zapis prezentacji:

Funktory, funktory aplikatywne, MONoidy Damian Brzuzy, Bartłomiej Lewandowski, Sylwia Maślankowska, Paweł Traczyk, Dawid Szczypiński

Czym się dziś zajmiemy: Funktory Funktory Aplikatywne Nowe Słowa Kluczowe Monoidy

Wstęp W Haskell’u możliwy jest wyższy poziom polimorfizmu niż w innych językach programowania. Typy klas w Haskell’u są otwarte, co oznacza, że ​​możemy zdefiniować własny typ danych, dzięki temu możemy skupić się na tym jak coś ma działać i połączyć to z odpowiednią klasę która definiuje właśnie to działanie. Język Haskell zawiera dużo typów oraz potrafi nawet dopasować właściwy typ po samej deklaracji funkcji. Kolejną własnością Haskell’a jest możliwość deklaracji własnych typów ogólnych i abstrakcyjnych. Powyższe właściwości Haskell’a znalazły swoje zastosowanie w funktorach i monoidach.

FUNKTORY

FunKtorY – Porównanie do MAP Funkcja map to przykład funkcji wyższego rzędu, funkcji której jednym z parametrów jest inna funkcja. Dla funkcji map jest to funkcja która pobiera wartość typu "a" i zwraca wartość typu "b". Funkcja "map" potrzebuje tej funkcji żeby wykonać swoje zadanie, które jest trochę bardziej złożone. Zmienia listę elementów typu "a" na listę elementów typu "b„ Przykład 0. map :: (a -> b) -> [a] -> [b] map f [] = [] map f (x:xs) = (f x) : (map f xs) ghci>> map (\x -> [-x,x]) [1,2,3] Tak naprawdę, kiedy przyjrzymy się definicji funkcji map dla listy, to sprowadza się ona do jednego. Tworzona jest lista której pierwszym elementem jest wynik zastosowania funkcji f do pierwszego elementu listy wejściowej. Następnie funkcja map stosowana jest do każdego kolejnego elementu listy.

FunKtorY – CO TO JEST? Funktory najprościej można zrozumieć jako odwzorowanie danych wejściowych dowolnego, abstrakcyjnego typu na nowy dowolny abstrakcyjny typ, który najczęściej będzie przedstawiony jako mapy, listy, drzewa, typ wyliczeniowy itp. Funktory charakteryzowane są tylko przez jedną metodę Typeclass, którą jest fmap. Klasa funktora jest zdefiniowana następująco: fmap :: (a -> b) -> fa -> fb Klasa ta jest dostępna z poziomu Prelude, zdefiniowany w import Data.Functor Można to zrozumieć jako sytuacje że mamy funkcje, która pobiera dane typu a i zwraca dane typu b oraz kontener (box) z a oraz kontener (box) z b.

FunKtorY - FMAP Podczas definiowania funktorów można dostarczać dowolne typy, lecz nie można tego brać tak dosłownie. Trzeba wziąć pod uwagę ograniczenia języka Haskell (np. problemy z listami). Funktory oprócz znanych nam typów np. typu wyliczeniowego, listy itp. mogą być zdefiniowane przez swoje własne specjalne typy, zwane instancjami: IO i (->) r Funktory w funkcji fmap mogą zwrócić typ instancji IO, dzięki czemu nie trzeba stosować zmiennych pomocniczych. Od razu możemy zastosować dowolną funkcję na naszych danych wejściowych Powyższe własności funktorów pokazują polimorfizm w języku Haskell

FunKtorY - io Przykład 1. Wykorzystanie funktora działającego na typie instancji IO import Data.Char import Data.List main = do line <- getLine let line' = reverse line putStrLn $ "You said " ++ line' ++ " backwards!" putStrLn $ "Yes, you really said " ++ line' ++ " backwards! " import Data.Char import Data.List main = do line <- fmap reverse getLine putStrLn $ "You said " ++ line ++ " backwards!" putStrLn $ "Yes, you really said" ++ line ++ " backwards!"

FunKtorY - io Przykład 2. Porównanie funktora z funkcją złożoną import Data.Char import Data.List main = do line <- fmap (intersperse '-' . reverse . map toUpper) getLine putStrLn line zzz = (\xs -> intersperse '-' (reverse (map toUpper xs))) Na pierwszy rzut oka widać, że funkcja fmap (funktor) jest bardziej przejrzysta dzięki wykorzystaniu notacji kropkowej Obie implementacje dadzą nam ten sam wynik Dzięki funktorom w łatwy sposób możemy rozbudować naszą funkcję

FunKtorY – sposób działania fmap można użyć na dwa sposoby: fmap które przyjmuje wartość funkcji wejściowej i funktora, a następnie wyświetla wartość funktora za pomocą funkcji pomocniczej np. g (przykłady wcześniej) fmap która przyjmuje funkcję i pracuje nad wartościami funktorów (przykłady poniżej) ghci> fmap (replicate 3) [1,2,3,4] [[1,1,1],[2,2,2],[3,3,3],[4,4,4]] ghci> fmap (replicate 3) (Just 4) Just [4,4,4] ghci> fmap (replicate 3) (Right "blah") Right ["blah","blah","blah"] ghci> fmap (replicate 3) Nothing Nothing ghci> fmap (replicate 3) (Left "foo") Left "foo" fmap w drugim sposobie działania powinien wyświetlać tylko funktor z funkcji i nic więcej.

FunKtorY – PRAWA FUNKTORÓW Wszystkie kopie funktora na których działamy w drugim sposobie ich funkcjonowania powinny przestrzegać „Dwóch Praw Funktorów”. Haskell nie zmusza, aby te prawa wypełniały się automatycznie, dlatego powinno się sprawdzić je podczas tworzenia funktora. Wszystkie typy oparte na kopiach funktorów wbudowane w standardową bibliotekę podlegają tym prawom. Prawo I Pierwsze prawo funktorów stwierdza, że jeśli zastosujemy id funkcji do wartości funktora, który otrzymamy, powinno być ono takie same jak wartość inicjująca funktora. W kilku bardziej formalnych oznaczeniach to, fmap id = id. W istocie stwierdza się, że jeśli zastosujemy id fmap do wartości funktora, to powinno być ono takie samo jakie niesie ze sobą sam funktor. Przypomnijmy sobie, że id - to jest tożsamość, która po prostu zwróci parametr do zmiennej.

FunKtorY – PRAWA FUNKTORÓW Jeśli postrzegamy jaką wartość niesie ze sobą funktor, prawo fmap id = id wygląda na dość banalne i jest oczywiste. Spójrzmy jak to prawo działa dla niektórych wartości funktorów. ghci> fmap id (Just 3) Just 3 ghci> id (Just 3) ghci> fmap id [1..5] [1,2,3,4,5] ghci> id [1..5] ghci> fmap id [] [] ghci> fmap id Nothing Nothing

FunKtorY – PRAWA FUNKTORÓW Jeśli spojrzymy na implementację wbudowanych typów opartych na kopiach funktorów, na przykład dla Maybe, moglibyśmy zrozumieć, dlaczego spełnia się pierwsze prawo: instance Functor Maybe where fmap f (Just x) = Just (f x) fmap f Nothing = Nothing Prawo II Druga zasada mówi, że ​​złożenie dwóch funkcji z użyciem funktora powinno dać ten sam wynik, jak zastosowanie pierwszej funkcji do funktora, a następnie zastosowanie kolejnej funkcji do tego funktora W oficjalnym zapisie oznacza to, że fmap (f . g) = fmap f . fmap g Jeśli chcemy zapisać to dla dowolnej wartości funktora x możemy zapisać: fmap (f . g) x = fmap f (fmap g x) Możemy wyjaśnić, w jaki sposób spełnić drugie prawo w odniesieniu do jakiegokolwiek typu opartego na kopiach funktorów (Maybe, Just)

Funktor może być nawet zupełnie bez sensu, ale będzie funktorem o ile będzie spełniał wspomniane prawa. Funktory mają swoje zastosowanie w instancjach newtype (a one kolejno w monoidach tworząc jedną wielką całość).

FUNKTORY APLIKATYWNE

FunKtorY aplikatywne – Czym są? Są to funktory które są wzbogacone o Typeclass Applicative znajdujący się w module Control.Applicative Funkcje w języku Haskell są domyślnie kalibrowane, co oznacza, że ​​funkcja, która przyjmuje kilka parametrów, faktycznie przyjmuje tylko jeden parametr i zwraca funkcję, która przyjmuje następny parametr i tak dalej. Czyli jeżeli funkcja jest typu a -> b -> c, mówimy, że ma dwa parametry i zwraca c, ale w rzeczywistości dostaje a i zwraca funkcję b -> c. Dlatego możemy wywołać funkcję f x y lub (f x) y. Mechanizm ten umożliwia nam częściowe zastosowanie funkcji, co powoduje, że funkcje możemy przekazać innym funkcjom jako parametr.

FunKtorY aplikatywne – (*) Do tej pory, podczas mapowania funkcji nad funktorami, zazwyczaj mapowaliśmy funkcje, które przyjmują tylko jeden parametr. Ale co się stanie, gdy mapujemy funkcję taką jak *, która przyjmuje dwa parametry przez funktor? (*) traktujemy jako dowolną funkcję Spójrzmy na kilka konkretnych przykładów do tego. Jeśli mamy Just 3 i zrobimy fmap (*) (Just 3) co otrzymamy? Z implementacji instancji Maybe w Funktorach wiemy, że jeśli jest to wartość Just something, zastosuje tę funkcję do something wewnątrz Just. Dlatego wykonanie fmap (*) (Just 3) zwróci Just ((*) 3), który można również zapisać jako Just (* 3), jeżeli używamy sekcji.

FunKtorY aplikatywne – (*) Przykład 7. Użycie (*) ghci> :t fmap (++) (Just "hey")   fmap (++) (Just "hey") :: Maybe ([Char] -> [Char])   ghci> :t fmap compare (Just 'a')   fmap compare (Just 'a') :: Maybe (Char -> Ordering)   ghci> :t fmap compare "A LIST OF CHARS"   fmap compare "A LIST OF CHARS" :: [Char -> Ordering]   ghci> :t fmap (\x y z -> x + y / z) [3,4,5,6]   fmap (\x y z -> x + y / z) [3,4,5,6] :: (Fractional a) => [a -> a -> a]    Przykład 8. Użycie (*) ghci> let a = fmap (*) [1,2,3,4]   ghci> :t a   a :: [Integer -> Integer]   ghci> fmap (\f -> f 9) a   [9,18,27,36]  

FunKtorY aplikatywne – Applicative W przypadku gdy chcemy wykonać mapowanie funktura aplikatywnego na inną wartość funktora automatycznie są wykorzystywane dwie metody pure i <*> które znajdują się w module Control.Applicative. class (Functor f) => Applicative f where        pure :: a -> f a        (<*>) :: f (a -> b) -> f a -> f b   Z powyżej implementacji wynika że możemy używać fmap jeżeli podczas deklaracji pierwszy parametr będzie funkcją aplikatywną (gwarantuje nam to deklaracja pure)

FunKtorY aplikatywne – Applicative Przykład 10. Prezentację drugiej metody aplicative ghci> Just (+3) <*> Just 9   Just 12   ghci> pure (+3) <*> Just 10   Just 13   ghci> pure (/3) <*> Just 9   Just 3.0   ghci> Just (++"hahah") <*> Nothing   Nothing   ghci> Nothing <*> Just "woot"  

FunKtorY aplikatywne – Applicative Przykład 10. Prezentację pierwszej i drugiej metody aplicative ghci> pure (+) <*> Just 3 <*> Just 5   Just 8   ghci> pure (+) <*> Just 3 <*> Nothing   Nothing   ghci> pure (+) <*> Nothing <*> Just 5   ghci> [(*0),(+100),(^2)] <*> [1,2,3]   [0,0,0,101,102,103,1,4,9] ghci> [(+),(*)] <*> [1,2] <*> [3,4]   [4,5,5,6,3,4,6,8]  

FunKtorY aplikatywne – Applicative Przykład 11. Prezentację pierwszej metody aplicative w postaci <$> ghci> (++) <$> ["ha","heh","hmm"] <*> ["?","!","."]   ["ha?","ha!","ha.","heh?","heh!","heh.","hmm?","hmm!","hmm."] ghci> fmap negate (Just 2) Just (-2) ghci> (\x y z -> [x, y, z]) <$> (+3) <*> (*2) <*> (/2) $ 1 [4.0,2.0,0.5] ghci> ((\x y z -> [x, y, z]) <$> (+3) <*> (*2)) 1 2 [4,2,2] Odpowiednik użycia pure … <*> jest <$> 

NOWE SŁOWA KLUCZOWE

Nowe słowa kluczowe Słowo kluczowe newtype w Haskell’u jest użyteczne w sytuacjach, w których chcemy po prostu wziąć jeden typ i „opakować” go w coś, aby przedstawić go jako inny typ. Przy użyciu słowa kluczowego newtype można mieć tylko jeden konstruktor wartości, a konstruktor wartości może mieć tylko jedno pole. newtype CharList = CharList { getCharList :: [Char] } deriving (Eq, Show) ghci> :t CharList CharList :: [Char] -> CharList ghci> CharList "this will be shown!"   CharList {getCharList = "this will be shown!"}   ghci> CharList "benny" == CharList "benny"   True   ghci> CharList "benny" == CharList "oisters"   False  

Nowe słowa kluczowe Newtype jest szybszy. Jeśli używasz słowa kluczowego do opakowania typu, w narzędziu jest cały pakiet, który ma zostać opakowany i rozpakowany podczas uruchamiania danego programu. Jeśli jednak użyjesz nowego typu, Haskell wie, że po prostu używasz go do opakowania istniejącego typu do nowego typu (stąd nazwa), ponieważ chcesz, aby był on taki sam, ale różny. Mając to na uwadze, Haskell może pozbyć się owijania i rozpakowywania, gdy tylko ustali, która wartość jest tego typu. Swoje największe zastosowanie znalazły one w monoidach

Nowe słowa kluczowe - fmap Przykład 13. Przykład wykorzystania słowa kluczowego z funktorem newtype Pair b a = Pair { getPair :: (a,b) }   instance Functor (Pair c) where       fmap f (Pair (x,y)) = Pair (f x, y)   ghci> :load src\newtype.hs ghci> getPair $ fmap (*100) (Pair (2,3))   (200,3)   ghci> getPair $ fmap reverse (Pair ("london calling", 3))   ("gnillac nodnol",3) 

MONOIDY

Monoidy - Wstęp ghci> (3 * 2) * (8 * 5) ghci> 4 * 1 240 4 240   ghci> 3 * (2 * (8 * 5))   ghci> "la" ++ ("di" ++ "da")   "ladida"   ghci> ("la" ++ "di") ++ "da"   ghci> 4 * 1   4   ghci> 1 * 9   9   ghci> [1,2,3] ++ []   [1,2,3]   ghci> [] ++ [0.5, 2.5]   [0.5,2.5]   Na wstępie przed zapoznaniem się z monoidami zauważmy parę faktów Funkcje ++ , * przyjmuje 2 parametry, dlatego musimy używać () aby grupować operacje. Mimo tego grupowania trzeba pamiętać o zasadach z matematyki ponieważ 1/(2*3) to nie to samo co (1/2)*3.

Monoidy - Class Typ klasy monoid: import Data.Monoid class Monoid m where       mempty :: m       mappend :: m -> m -> m       mconcat :: [m] -> m       mconcat = foldr mappend mempty m w klasie monoid nie przyjmuje typu parametrycznego mempty jest to funkcja która pełni role polimorficznej stałej utożsamianej z określonym monoidem. mappend jest funkcją binarną pobiera dwie wartości tego samego typu i zwraca ten sam typ. mconcat pobiera listę monoidów i zwraca pojedynczą wartość. To ma trudną implementację, ale po po prostu monoid pobiera jako wartość startową mempty i wykonuję na niej mappend dla kolejnych elementów.

Monoidy Wspominanie na samym wstępie informację mają swoje odzwierciedlenie w prawach monoidów. 3 Prawa monoidów: 1. mempty `mappend` x = x 2. x `mappend` mempty = x 3. (x `mappend` y) `mappend` z = x `mappend` (y `mappend` z)

Monoidy - Listy Monoidy w praktyce: instance Monoid [a] where     mempty = []       mappend = (++)   ghci> [1,2,3] `mappend` [4,5,6]   [1,2,3,4,5,6]   ghci> ("one" `mappend` "two") `mappend` "tree"   "onetwotree"   ghci> "one" `mappend` ("two" `mappend` "tree")   ghci> "one" `mappend` "two" `mappend` "tree"   ghci> "pang" `mappend` mempty   "pang"   ghci> mconcat [[1,2],[3,6],[9]]   [1,2,3,6,9]   ghci> mempty :: [a]   []  

MonoidY – Product and SUM newtype Product a =  Product { getProduct :: a }       deriving (Eq, Ord, Read, Show, Bounded)   instance Num a => Monoid (Product a) where       mempty = Product 1       Product x `mappend` Product y = Product (x * y) ghci> :load src\monoid.hs ghci> getProduct $ Product 3 `mappend` Product 9   27   ghci> getProduct $ Product 3 `mappend` mempty   3   ghci> getProduct $ Product 3 `mappend` Product 4 `mappend` Product 2   24   ghci> getProduct . mconcat . map Product $ [3,4,2]     

MonoidY – Product and SUM Wykorzystanie monoidów z fmap w ostatnim przykładzie ghci> getSum $ Sum 2 `mappend` Sum 9   11   ghci> getSum $ mempty `mappend` Sum 3   3   ghci> getSum . mconcat . map Sum $ [1,2,3]   6  

MonoidY – any newtype Any = Any { getAny :: Bool }     deriving (Eq, Ord, Read, Show, Bounded)    instance Monoid Any where           mempty = Any False           Any x `mappend` Any y = Any (x || y)  ghci> :load src\monoid.hs ghci> getAny $ Any True `mappend` Any False   True   ghci> getAny $ mempty `mappend` Any True   ghci> getAny . mconcat . map Any $ [False, False, False, True]   ghci> getAny $ mempty `mappend` mempty   False  

MonoidY – aLL newtype All = All { getAll :: Bool }         deriving (Eq, Ord, Read, Show, Bounded)    instance Monoid All where           mempty = All True           All x `mappend` All y = All (x && y) ghci> :load src\monoid.hs ghci> getAll $ mempty `mappend` All True   True   ghci> getAll $ mempty `mappend` All False   False   ghci> getAll . mconcat . map All $ [True, True, True]   ghci> getAll . mconcat . map All $ [True, True, False]  

MonoidY – ORDERING instance Monoid Ordering where mempty = EQ     LT `mappend` _ = LT       EQ `mappend` y = y       GT `mappend` _ = GT  ghci> LT `mappend` GT   LT   ghci> GT `mappend` LT   GT   ghci> mempty `mappend` LT   ghci> mempty `mappend` GT  

Monoids – ORDERING import Data.Monoid    lengthCompare :: String -> String -> Ordering   lengthCompare x y = (length x `compare` length y) `mappend`                       (x `compare` y) ghci> :load src\monoid.hs ghci> lengthCompare "zen" "ants"   LT   ghci> lengthCompare "zen" "ant"   GT  

Monoids – ORDERING import Data.Monoid lengthCompare :: String -> String -> Ordering   lengthCompare x y = (length x `compare` length y) `mappend`                       (vowels x `compare` vowels y) `mappend`                       (x `compare` y)       where vowels = length . filter (`elem` "aeiou")   ghci> :load src\monoid.hs ghci> lengthCompare "zen" "anna"   LT   ghci> lengthCompare "zen" "ana"   ghci> lengthCompare "zen" "ann"   GT   Kolejny przykład monoidu to ramka logiczna XOR w pliku monoid.xor.hs