Skip to content

Latest commit

 

History

History
213 lines (148 loc) · 6.57 KB

05poly.md

File metadata and controls

213 lines (148 loc) · 6.57 KB

Polimorfizm

Polimorfizm (z gr. wielopostaciowość) to w odniesieniu do funkcji możliwość działania na obiektach różnych typów. W tym miejscu zajmiemy się podstawową formą polimorfizmu - polimorfizmem parametrycznym.

Przypomnijmy funkcję activityOf:

activityOf :: world ->
	      (Event -> world -> world) ->
	      (world -> Picture) ->
              IO ()

Funkcja ta jest polimorficzna: możemy ją zastosować używając w miejscu zmiennej world dowolnego typu.

Parametryczność

Ważne aby pamiętać, że możliwość wyboru typu leży po stronie wywołującego. Oznacza to, że implementacja funkcji musi działać dla wszystkich typów. Co więcej, musi działać dla wszystkich typów tak samo.

Dlaczego parametryczność jest ważna?

  1. Umożliwia wycieranie typów. Skoro funkcja wywoływana działa dla każdego typu tak samo, nie potrzebuje informacji, jakiego typu są jej faktyczne parametry. W związku z tym informacja typowa nie jest potrzebna w trakcie wykonania, a jedynie w trakcie kompilacji.

  2. Ograniczenie sposobów działania funkcji polimorficznych daje nam twierdzenia za darmo (theorems for free, termin ukuty przez Phila Wadlera, a zarazem tytuł jego słynnej pracy).

📝 Rozważmy na przykład funkcje o następujących sygnaturach

zagadka1 :: a -> a
zagadka2 :: a -> b -> a

ile różnych implementacji potrafisz napisać?

📝 Trochę trudniejsze, być może do domu: co można powiedzieć o rodzinie funkcji typu

(a -> a) -> a -> a

Równość (nie) dla wszystkich

Czy da się zdefiniować uniwersalną równość, czyli parametryczną funkcję typu eq :: a -> a -> Bool?

Możemy podejrzewać, że nie, ale jak to udowodnić?

Otóż "darmowe twierdzenie" dla tego typu mówi że dla dowolnych typów A,B i funkcji

f :: A -> B

mamy

(eq x y) = (eq (f x) (f y))

co pokazuje, że eq jest funkcją stałą, czyli nie jest zbyt użyteczna jako równość (chyba, że w sensie "wszyscy są równi").

O tym jak poradzić sobie z tym problemem porozmawiamy na kolejnych zajęciach.

Polimorficzne typy danych

Polimorficzne mogą być nie tylko funkcje, ale i typy danych. W poprzednim tygodniu pisaliśmy wariant funkcji activityOf pozwalający na cofnięcie poziomu do stanu początkowego. Spróbujmy teraz rozszerzyć tę funkcję o wyświetlanie ekranu startowego i rozpoczynanie właściwej gry po naciśnięciu spacji. Na początek możemy stworzyć bardzo prosty ekran startowy:

startScreen :: Picture
startScreen = scaled 3 3 (lettering "Sokoban!")

Musimy wiedzieć czy jestesmy na ekranie startowym czy też gra już się toczy. Najprościej zapamiętać tę informację w stanie

data SSState = StartScreen | Running world

Niestety przy takim kodzie dostaniemy komunikat o błędzie Not in scope: type variable ‘world’. Istotnie, co to jest world? Możemy uczynić go parametrem typu:

data SSState world = StartScreen | Running world

Teraz możemy zaimplementować:

startScreenActivityOf ::
    world ->
    (Event -> world -> world) -> 
    (world -> Picture) ->
    IO ()
startScreenActivityOf state0 handle draw
  = activityOf state0' handle' draw'
  where
    state0' = StartScreen

    handle' (KeyPress key) StartScreen
         | key == " "                  = Running state0
    handle' _              StartScreen = StartScreen
    handle' e              (Running s) = Running (handle e s)

    draw' StartScreen = startScreen
    draw' (Running s) = draw s

📝 Dodaj ekran startowy do swojej gry.

Interakcje całościowe

Chcielibyśmy teraz połączyć funkcjonalność startScreenActivityOf z funkcjonalnością resettableActivityOf

resettableActivityOf ::
    world ->
    (Event -> world -> world) ->
    (world -> Picture) ->
    IO ()

tak, aby nacisnięcie ESC wracało do ekranu startowego. Ale nie możemy - obie te funkcje daja wynik typu IO() a nie biorą argumentów takiego typu. Musimy spróbowac innego podejścia.

Gdybyśmy mieli typ Activity, opisujący interakcje, oraz funkcje

resettable :: Activity -> Activity
withStartScreen :: Activity -> Activity

moglibyśmy uzyskać pożądany efekt przy pomocy ich złożenia. Potrzebowalibyśmy jeszcze funkcji

runActivity :: Activity -> IO ()

Jak możemy zdefiniować taki typ Activity ?

Na razie obrazek, zeby zasugerować rozwiązanie, ale jeszcze go nie zdradzać:

Cat in a box with a cat in a box

Musimy opakować argumenty funkcji activityOf wewnątrz typu Activity:

data Activity world = Activity {
        actState  :: world,
	actHandle :: (Event -> world -> world),
	actDraw   ::(world -> Picture)
	}

Zwróćmy uwagę, że dla pełnej ogólności typ świata world musi być parametrem typu Activity.

Implementacja funkcji resettable nie przedstawia większych trudności - musimy po prostu wypakować potrzebne wartości przy pomocy dopasowania wzorca:

resettable :: Activity s -> Activity s
resettable (Activity state0 handle draw)
  = Activity state0 handle' draw
  where handle' (KeyPress key) _ | key == "Esc" = state0
        handle' e s = handle e s

Implementacja (a co najmniej zapisanie typu) funkcji withStartScreen wymaga chwili namysłu. Zauważmy, że funkcjonalność tę osiagnęliśmy przez rozszerzenie stanu świata:

data SSState world = StartScreen | Running world

Sygnatura naszej funkcji może wyglądać tak:

withStartScreen :: Activity s -> Activity (SSState s)

a implementacja np. tak:

withStartScreen (Activity state0 handle draw)
  = Activity state0' handle' draw'
  where
    state0' = StartScreen

    handle' (KeyPress key) StartScreen
         | key == " "                  = Running state0
    handle' _              StartScreen = StartScreen
    handle' e              (Running s) = Running (handle e s)

    draw' StartScreen = startScreen
    draw' (Running s) = draw s

Do kompletu potrzebujemy jeszcze funkcji runActivity.

📝 Napisz funkcję runActivity :: Activity s -> IO ()

📝 Przepisz funkcje walk2 i walk3 ze swojego rozwiązania tak aby używały funkcji runActivity i resettable.

📝 Napisz funkcję walk4 :: IO () rozszerzającą walk3 o ekran startowy.

📝 Zadanie - Sokoban 3

https://github.com/jnp3-haskell-2021/sokoban-3

Oddawanie przez https://classroom.github.com/a/KEvrUDqe Termin: 22.11.2022 18:00