Tworzenie własnych typów. danych oraz klas typów

Podobne dokumenty
WEJŚCIE/WYJŚCIE HASKELL ŁUKASZ PAWLAK DARIUSZ KRYSIAK

Zatem w jaki sposób nasze programy mają komunikować się ze światem zewnętrznym?

TWORZENIE SWOICH TYPÓW I TYPÓW KLAS HASKELL (RODZIAŁ 8) ZAJĘCIA 4

znajdowały się różne instrukcje) to tak naprawdę definicja funkcji main.

4. Funkcje. Przykłady

Po uruchomieniu programu nasza litera zostanie wyświetlona na ekranie

Podstawy programowania, Poniedziałek , 8-10 Projekt, część 1

Informatyka I. Klasy i obiekty. Podstawy programowania obiektowego. dr inż. Andrzej Czerepicki. Politechnika Warszawska Wydział Transportu 2018

Wstęp do programowania INP001213Wcl rok akademicki 2017/18 semestr zimowy. Wykład 12. Karol Tarnowski A-1 p.

Podstawy programowania. Wykład Funkcje. Krzysztof Banaś Podstawy programowania 1

Haskell Moduły Ładowanie

Programowanie proceduralne INP001210WL rok akademicki 2018/19 semestr letni. Wykład 6. Karol Tarnowski A-1 p.

Wstęp do Programowania, laboratorium 02

- nawiasy kwadratowe oznaczają, że to lista

Ok. Rozbijmy to na czynniki pierwsze, pomijając fragmenty, które już znamy:

Wskaźniki a tablice Wskaźniki i tablice są ze sobą w języku C++ ściśle związane. Aby się o tym przekonać wykonajmy cwiczenie.

Typy, klasy typów, składnie w funkcji

Obiekt klasy jest definiowany poprzez jej składniki. Składnikami są różne zmienne oraz funkcje. Składniki opisują rzeczywisty stan obiektu.

Podstawy Programowania C++

7. Pętle for. Przykłady

1 Podstawy c++ w pigułce.

Nazwa implementacji: Nauka języka Python wyrażenia warunkowe. Autor: Piotr Fiorek. Opis implementacji: Poznanie wyrażeń warunkowych if elif - else.

Niezwykłe tablice Poznane typy danych pozwalają przechowywać pojedyncze liczby. Dzięki tablicom zgromadzimy wiele wartości w jednym miejscu.

Podstawy algorytmiki i programowania - wykład 4 C-struktury

Podstawy programowania skrót z wykładów:

Podstawy i języki programowania

Część XVII C++ Funkcje. Funkcja bezargumentowa Najprostszym przypadkiem funkcji jest jej wersja bezargumentowa. Spójrzmy na przykład.

Programowanie w języku Python. Grażyna Koba

JAVA W SUPER EXPRESOWEJ PIGUŁCE

Podstawy Pythona. Krzysztof Gdawiec. Instytut Informatyki Uniwersytet Śląski

Programowanie 3 - Funkcje, pliki i klasy

Języki i techniki programowania Ćwiczenia 2

Podstawy języka C++ Maciej Trzebiński. Instytut Fizyki Jądrowej Polskiej Akademii Nauk. Praktyki studenckie na LHC IVedycja,2016r.

Python wprowadzenie. Warszawa, 24 marca PROGRAMOWANIE I SZKOLENIA

Programowanie strukturalne. Opis ogólny programu w Turbo Pascalu

utworz tworzącą w pamięci dynamicznej tablicę dwuwymiarową liczb rzeczywistych, a następnie zerującą jej wszystkie elementy,

Podstawy Programowania Podstawowa składnia języka C++

Zajęcia nr 2 Programowanie strukturalne. dr inż. Łukasz Graczykowski mgr inż. Leszek Kosarzewski Wydział Fizyki Politechniki Warszawskiej

Wykład 4: Klasy i Metody

PROE wykład 3 klasa string, przeciążanie funkcji, operatory. dr inż. Jacek Naruniec

Przykład 1: Funkcja jest obiektem, przypisanie funkcji o nazwie function() do zmiennej o nazwie funkcja1

Liczby losowe i pętla while w języku Python

INFORMATYKA Studia Niestacjonarne Elektrotechnika

Argumenty wywołania programu, operacje na plikach

Elementy języka Haskell

KLASA UCZEN Uczen imię, nazwisko, średnia konstruktor konstruktor Ustaw Wyswietl Lepszy Promowany

1 Podstawy c++ w pigułce.

Część 4 życie programu

Podstawowe elementy proceduralne w C++ Program i wyjście. Zmienne i arytmetyka. Wskaźniki i tablice. Testy i pętle. Funkcje.

Szablony klas, zastosowanie szablonów w programach

Programowanie - wykład 4

lekcja 8a Gry komputerowe MasterMind

Definicje wyższego poziomu

Informatyka I. Typy danych. Operacje arytmetyczne. Konwersje typów. Zmienne. Wczytywanie danych z klawiatury. dr hab. inż. Andrzej Czerepicki

Programowanie obiektowe

Uwagi dotyczące notacji kodu! Moduły. Struktura modułu. Procedury. Opcje modułu (niektóre)

JĘZYKI PROGRAMOWANIA Z PROGRAMOWANIEM OBIEKTOWYM. Wykład 6

Informatyka II. Laboratorium Aplikacja okienkowa

C++ Przeładowanie operatorów i wzorce w klasach

Dekoratora używa się wstawiając linijkę zaczynającą się przed definicją dekorowanego obiektu (klasy czy funkcji).

Wykresy i interfejsy użytkownika

Swift (pol. jerzyk) nowy język programowania zaprezentowany latem 2014 r. (prace od 2010 r.)

Wyrażenie include(sciezka_do_pliku) pozwala na załadowanie (wnętrza) pliku do skryptu php. Plik ten może zawierać wszystko, co może się znaleźć w

Wstęp do programowania

Rozdział 4 KLASY, OBIEKTY, METODY

Wykład 8: klasy cz. 4

3. Instrukcje warunkowe

KLASA UCZEN Uczen imię, nazwisko, średnia konstruktor konstruktor Ustaw Wyswietl Lepszy Promowany

Obiektowy PHP. Czym jest obiekt? Definicja klasy. Składowe klasy pola i metody

Widoczność zmiennych Czy wartości każdej zmiennej można zmieniać w dowolnym miejscu kodu? Czy można zadeklarować dwie zmienne o takich samych nazwach?

Programowanie C++ Wykład 2 - podstawy języka C++ dr inż. Jakub Możaryn. Warszawa, Instytut Automatyki i Robotyki

IMIĘ i NAZWISKO: Pytania i (przykładowe) Odpowiedzi

Kompilacja javac prog.java powoduje wyprodukowanie kilku plików o rozszerzeniu.class, m.in. Main.class wykonanie: java Main

8. Wektory. Przykłady Napisz program, który pobierze od użytkownika 10 liczb, a następnie wypisze je w kolejności odwrotnej niż podana.

Czym są właściwości. Poprawne projektowanie klas

Przypomnienie o klasach i obiektach

Stałe, znaki, łańcuchy znaków, wejście i wyjście sformatowane

PHP 5 język obiektowy

Programowanie strukturalne i obiektowe. Funkcje

Aplikacje w środowisku Java

Laboratorium 1 - Programowanie proceduralne i obiektowe

PROE wykład 2 operacje na wskaźnikach. dr inż. Jacek Naruniec

Języki programowania Haskell

Paradygmaty programowania

Ćwiczenie: JavaScript Cookies (3x45 minut)

C++ - przeciążanie operatorów. C++ - przeciążanie operatorów. C++ - przeciążanie operatorów. C++ - przeciążanie operatorów

Zmienne i struktury dynamiczne

ROZDZIAŁ 8: KLASY TYPÓW

Programowanie Komputerów

Jak napisać program obliczający pola powierzchni różnych figur płaskich?

Konstruktory. Streszczenie Celem wykładu jest zaprezentowanie konstruktorów w Javie, syntaktyki oraz zalet ich stosowania. Czas wykładu 45 minut.

Podstawy programowania funkcjonalnego

WSNHiD, Programowanie 2 Lab. 2 Język Java struktura programu, dziedziczenie, abstrakcja, polimorfizm, interfejsy

Język C zajęcia nr 11. Funkcje

1 Powtórzenie wiadomości

Język JAVA podstawy. wykład 1, część 3. Jacek Rumiński. Politechnika Gdańska, Inżynieria Biomedyczna

Podstawy Informatyki. Inżynieria Ciepła, I rok. Wykład 10 Kurs C++

Podstawy programowania. Wykład 6 Złożone typy danych: struktury, unie. Krzysztof Banaś Podstawy programowania 1

Systemy operacyjne. Laboratorium 9. Perl wyrażenia regularne. Jarosław Rudy Politechnika Wrocławska 28 lutego 2017

Podstawy programowania. Podstawy C# Tablice

Transkrypt:

Justyna Łowicka

Tworzenie własnych typów danych oraz klas typów

Algebraiczne typy danych

konstruktorów umieszczonych w deklaracji data. Zobaczmy, jak typ Bool jest zdefiniowany w bibliotece standardowej. data Bool = False True Data oznacza, że mamy do zdefiniowania nowy typ danych. Przed znakiem równania oznaczono typ, którym jest Bool,zaś po znaku - wartości, jakie może mieć dany typ, oddzielone odczytywanym jako lub. Czyli rozumiemy to jako: Typ Bool może mieć wartość True lub False. Na przykład typ Int jest zdefiniowany w następujący sposób: data Int = -2147483648-2147483647... -1 0 1 2... 2147483647

Aby utworzyć kształt w Haskell możemy posłużyć się jednym ze sposobów jakim jest krotka. Koło może być oznaczone jako (43.1, 55.0, 10.4), gdzie dwa pierwsze parametry oznaczają współrzędne środka okręgu, zaś trzeci promień koła. Nie jest to dobre rozwiązanie, gdyż mogą one stanowić wektor 3D lub coś innego. Lepszym sposobem jest reprezentacja kształtu za pomocą typów. Dla koła czy prostokąta mamy: data Shape = Circle Float Float Float Rectangle Float Float Float Float Koło zawiera trzy parametry określające tak jak powyżej dwa współrzędne środka okręgu oraz promień, określające typy wartości jakie będą zdefiniowane. Jeśli chodzi o prostokąt, to mamy dwa pierwsze współrzędne odnoszące się do lewego górnego rogu i dwa kolejne określające położenie dolnego prawego rogu.

powierzchnię: surface :: Shape -> Float surface ( Circle r) = pi * r ^ 2 surface ( Rectangle x1 y1 x2 y2 ) = ( abs $ x2 - x1 ) * ( abs $ y2 - y1 ) W wierszu polecenia wygląda to tak: ghci > surface $ Circle 10 20 10 314.15927 ghci > surface $ Rectangle 0 0 100 100 10000.0

5 w wierszu, otrzymamy błąd. To dlatego, że Haskell nie wie, w jaki sposób wyświetlać nasz typ danych jako ciąg znaków. Aby się dowiedział, wystarczy dodać na końcu deklaracji deriving ( Show ). data Shape = Circle Float Float Float Rectangle Float Float Float Float deriving ( Show ) ghci > Circle 10 20 5 Circle 10.0 20.0 5.0 ghci > Rectangle 50 230 60 90 Rectangle 50.0 230.0 60.0 90.0

data Point = Point Float Float deriving ( Show ) data Shape = Circle Point Float Rectangle Point Point deriving ( Show ) Więc teraz Circle ma dwa pola, z czego jeden jest typu Point a drugi jest typu Float. To sprawia, że łatwiej jest zrozumieć co jest czym. Podobnie jest w przypadku prostokąta. surface :: Shape -> Float surface ( Circle _ r) = pi * r ^ 2 surface ( Rectangle ( Point x1 y1 ) ( Point x2 y2 )) = ( abs $ x2 - x1 ) * ( abs $ y2 - y1 )

10000.0 ghci > surface ( Circle ( Point 0 0) 24) 1809.5574 Możemy utworzyć funkcję, która przesuwa kształt. Przenosi ona obiekty na osi x i y, a następnie zwraca nowy kształt, który ma te same wymiary, tyle że znajduje się gdzieś indziej. nudge :: Shape -> Float -> Float -> Shape nudge ( Circle ( Point x y) r) a b = Circle ( Point ( x + a) (y + b )) r nudge ( Rectangle ( Point x1 y1 ) ( Point x2 y2 )) a b = Rectangle ( Point ( x1 +a) ( y1 +b )) ( Point ( x2 + a) ( y2 + b ))

Jeśli nie chcemy przesuwać bezpośrednio z punktów, możemy dodać funkcję pomocniczą, która tworzy kształty danej wielkości zaczynając od współrzędnych zera, a następnie się nimi sugerować. basecircle :: Float -> Shape basecircle r = Circle ( Point 0 0) r baserect :: Float -> Float -> Shape baserect width height = Rectangle ( Point 0 0) ( Point width height ) ghci > nudge ( baserect 40 100) 60 23

nawiasy z określonymi konstruktorami wartości, rozdzielając je przecinkami. Jeśli chcesz wyeksportować wszystkie konstruktory dla danego rodzaju, wystarczy wpisać w nawiasie... module Shapes ( Point (..), Shape (..), surface, nudne, basecircle, baserect ) where Możemy również nie eksportować żadnych konstruktorów

Składnia record

nazwisko, wiek, wzrost, numer telefonu i ulubiony smak lodów. data Person = Person String String Int Float String String deriving ( Show ) ghci > let guy = Person " Buddy " " Finklestein " 43 184.2 " 526-2928 " " Chocolate " ghci > guy Person " Buddy " " Finklestein " 43 184.2 " 526-2928 " " Chocolate " Zapis ten jest dość nieczytelny. Jak stworzyć funkcję, aby otrzymać oddzielne informacje od osoby? Funkcja,

Zamiast nazywania rodzajów pól jeden po drugim i oddzielania je spacjami, używamy nawiasów klamrowych. data Person = Person { firstname :: String lastname :: String age :: Int height :: Float phonenumber :: String flavor :: String deriving ( Show ),,,,, }

Za pomocą składni recordu do stworzenia tego typu danych, Haskell automatycznie wykonuje funkcje: firstname, lastname, age, height, phonenumber, flavor. ghci > :t flavor flavor :: Person -> String ghci > :t firstname firstname :: Person -> String Zaletą korzystania ze składni rekord jest to, że nie trzeba koniecznie umieszczać pola w odpowiedniej kolejności, dopóki mamy listę wszystkich danych. Ale jeśli nie używamy składni rekord, musimy określić je w kolejności.

Rodzaje parametrów

Konstruktor może podjąć pewne parametry wartości, a następnie stworzyć nową wartość. Na przykład, konstruktor Samochód przyjmuje trzy wartości i tworzy wartość samochodu. W podobny sposób konstruktorzy mogą wziąć typ jako parametr do produkcji nowych typów. data Maybe a = Nothing Just a Parametrem typu jest tutaj a. W zależności od tego, co chcemy w tym typie przechowywać, jeżeli nie jest to Nothing, ten konstruktor może stworzyć rodzaj Maybe Int, Maybe Car, Maybe String itd. Bez wartości może mieć typ po prostu Meybe.

Typ Nothing jest typem polimorficznym. Jeśli funkcja wymaga Meybe Int jako parametru, możemy mu przypisać Nothing, gdyż Nothing nie zawiera wartości. Typ Meybe może działać jak Meybe Int, podobie jak 5 może działać jak Int lub Double. Podobnie dzieje się w pustej liście []. Może ona działać jak lista niczego. Dlatego możemy zrobić [1,2,3] ++ [] i [ ha, ha, ha ] ++ [].

korzystamy z nich, gdy nasz typ danych będzie działał niezależnie od rodzaju wartości przechowywanych, tak jak Maybe. Jeśli typ może służyć jak pewnego rodzaju pudełko, warto z niego skorzystać. Na przykład, możemy zmienić typ danych z tego: data Car = Car { company :: String, model :: String, year :: Int } deriving ( Show ) Na ten: data Car a b c = Car { company :: a, model :: b

Biorąc pod uwagę naszą pierwszą definicję Car, możemy zrobić funkcję która wyświetla właściwości samochodu. tellcar :: Car -> String tellcar ( Car { company = c, model = m, year = y }) = " This " ++ c ++ " " ++ m ++ " was made in " ++ show y ghci > let stang = Car { company =" Ford ", model =" Mustang ", year =1967} ghci > tellcar stang " This Ford Mustang was made in 1967 "

data Vector a = Vector a a a deriving ( Show ) vplus :: ( Num t ) = > Vector t -> Vector t -> Vector t ( Vector i j k ) vplus ( Vector l m n) = Vector ( i+l) (j+ m) (k+n) vectmult :: ( Num t) => Vector t -> t -> Vector t ( Vector i j k ) vectmult m = Vector ( i*m) (j* m) ( k*m) scalarmult :: ( Num t) = > Vector t -> Vector t -> t ( Vector i j k ) scalarmult ( Vector l m n) = i*l + j*m + k *n Funkcja vplus pozwala dodać dwa wektory, ale tylko gdy zostaną wstawione ich odpowiednie komponenty, zaś ScalarMult służy do iloczynu skalarnego dwóch wektorów, a vectmult do pomnożenia wektora z skalarem (element ustalonego ciała nad którym zbudowany jest dowolny moduł). Funkcje te mogą działać z Vector Int,

Vector 12 7 16 ghci > Vector 3 5 8 vplus Vector 9 2 8 vplus Vector 0 2 3 Vector 12 9 19 ghci > Vector 3 9 7 vectmult 10 Vector 30 90 70 ghci > Vector 4 9 5 scalarmult Vector 9.0 2.0 4.0 74.0 ghci > Vector 2 9 3 vectmult ( Vector 4 9 5 scalarmult Vector 9 2 4) Vector 148 666 222 Bardzo ważne jest odróżnianie konstruktora typu od konstruktora wartości. Deklarując typ danych, część przed = jest konstruktorem typu, zaś po wskazuje na konstruktor wartości (ewentualnie oddzielone znakiem ). Nadanie funkcji typu Vector ttt -> Vector ttt -> t byłoby błędem, ponieważ musimy

Bartłomiej Zieliński YesNo typeclass (klasa typu YesNo) Functor typeclass (klasa typu Functor) Kinds (rodzaje) Input and Output (wejście i wyjście)

Klasa typu YesNo Klasa typu (ang. typeclass) YesNo pozwala na skonstruowanie klasy, której obiekty można stosować w wyrażeniach warunkowych w taki sposób, jakby były one typu Bool. Sama klasa typu jest ekwiwalentem interfejsów znanych z obiektowych języków programowania, takich jak C# czy Java.

Klasa typu YesNo Klasa implementująca YesNo może wyglądać następująco: class YesNo param where yesno :: param -> Bool yesnolist :: [param] -> Bool

Klasa typu YesNo Teraz możemy utworzyć konkretne instancje tej klasy. Dla przykładu, utwórzmy instancję, która jako parametr przyjmie liczbę i zwróci True, jeśli będzie ona różna od 0, lub False, jeśli będzie równa 0.

Klasa typu YesNo instance YesNo Int where yesnolist = undefined yesno 0 = False yesno _ = True Zastosowaliśmy tu słowo kluczowe undefined, ponieważ nie ma potrzeby na implementację metody yesnolist dla wartości typu Int, która nie jest listą.

Klasa typu YesNo Przykład: *Main> yesno (1 :: Int) True *Main> yesno (0 :: Int) False *Main> yesno (1012321321321 :: Int) True

Klasa typu YesNo Tak naprawdę nie ma ograniczeń w kwestii tego, do czego możemy odnieść naszą klasę YesNo. Możemy np. sprawdzić poprawność wprowadzonego imienia na podstawie listy dostępnych imion.

Klasa typu YesNo instance YesNo Char where yesno = undefined yesnolist [] = False yesnolist str = (str `elem` ["Jan", "Waldemar", "Marianna", "Abdul"]) Znowu zastosowaliśmy słowo kluczowe undefined. Bez niego otrzymywalibyśmy w trakcie kompilacji ostrzeżenie, że metoda yesno nie została zaimplementowana, co wprawdzie nie uniemożliwiłoby uruchomienia programu, ale wprowadzałoby niepotrzebny stres.

Klasa typu YesNo Przykład: *Main> yesnolist "Jan" True *Main> yesnolist "abdul" False *Main> yesnolist "Abdul" True *Main> yesnolist "Konrad" False

Klasa typu YesNo Możemy oczywiście powstałe w ten sposób instacje stosować później w funkcjach, chociażby w instrukcjach warunkowych if. checkname :: [Char] -> [Char] checkname str = if (yesnolist str) then "Ok :)" else "Not ok :("

Klasa typu YesNo Przykład: *Main> map (checkname) ["Jan", "Waldemar", "Janina", "Karol", "Abdul"] ["Ok :)","Ok :)","Not ok :(", "Not ok :(","Ok :)"]

Klasa typu Functor Klasa typu Functor pozwala na określenie, w jaki sposób można wykonać mapowanie jednego elementu na drugi, na podstawie pewnej danej (np. funkcji lub liczby). Definicja: class Functor f where fmap :: (a -> b) -> f a -> f b (a->b) oznacza funkcję przyjmującą jako parametr a i zwracającą b.

Klasa typu Functor Przykładowo, znana nam dobrze funkcja map implementuje interfejs typu klasowego Functor w taki sposób, by działał on tylko na listach: map :: (a -> b) -> [a] -> [b] instance Functor [] where fmap = map Co wynika już z samej definicji, zarówno fmap, jak i map, zadziałają w identyczny sposób dla parametrów jako list.

Klasa typu Functor Przykład: *Main> map (length) ["Haskell", "Dwa Haskelle", "Pincet Haskelli"] [7,12,15] *Main> fmap (length) ["Haskell", "Dwa Haskelle", "Pincet Haskelli"] [7,12,15]

Klasa typu Functor Nasuwa się pytanie kiedy zaimplementowanie klasy typu Functor faktycznie ma sens? Odpowiedź brzmi: ma ono sens dla każdego typu, który może zachowywać się jak pudełko, tj. może być puste, pełne lub po prostu zawierać pewną ilość elementów wewnątrz siebie.

Klasa typu Functor Klasa typu Maybe jest funktorem i przyjmuje formę: instance Functor Maybe where fmap f (Just x) = Just (f x) fmap f Nothing = Nothing W tym momencie warto zauważyć, że jako parametr instancji podajemy tylko typ piszemy Maybe, a nie np. Maybe m. Ma to sens o tyle, iż klasa fmap oczekuje jako parametru typu, a nie konkretnej danej.

Klasa typu Functor Maybe pozwala nam na niepodejmowanie radykalnych kroków w stylu wyrzucenia błędu, gdy coś pójdzie nie tak w trakcie wykonywania pewnych obliczeń. Zamiast tego zwrócona zostanie nieco bardziej przyjazna wartość Nothing. Dobrym przykładem będzie tu próba dzielenia przez 0 domyślnie działanie zostanie wykonane i zwróci nieskończoność (Infinity), co nie zawsze jest pożądane istnieje możliwość, że w naszym programie nieskończoność to przyjmowalny wynik, jednak tutaj powstałaby ona wskutek błędu.

Klasa typu Functor divide :: Float -> Float -> Maybe Float divide x y y /= 0 = Just (x / y) otherwise = Nothing Przykład: *Main> divide 5 4 Just 1.25 *Main> divide 5 0 Nothing

Klasa typu Functor Inne użycie Maybe: Prelude> fmap (++ [1,2,3]) (Just [4,5,6]) Just [4,5,6,1,2,3] Prelude> fmap (++ [1,2,3]) Nothing Nothing

Rodzaje Rodzaj w teorii typów, jest to typ konstruktora typu ( typ typu ). - zgodnie z angielską Wikipedią, czyli jedynym słusznym źródłem informacji. Definicja, choć krótka, na pierwszy rzut oka jest dość zawiła. Mając daną klasę A, rodzajem w Haskellu nazwiemy konstruktor jej typu, a więc nie konstruktor obiektu typu A, tylko konstruktor samego typu A.

Rodzaje By sprawdzić rodzaj w Haskellu, możemy skorzystać z komendy :k (analogicznie jak :t, gdy chcemy sprawdzić typ), która jako parametr przyjmuje nazwę typu, np.: Prelude> :k Int Int :: * Prelude> :k [] [] :: * -> * Prelude> :k Char Char :: *

Rodzaje Gwiazdka w rodzaju oznacza, że określa on konkretny typ, nie wymagający już żadnego dodatkowego uszczegóławiania. Można ją odczytać po prostu jako typ.

Rodzaje Przypomnijmy, jak wyglądał rodzaj listy: [] :: * -> * Jak należy to odczytać? Otóż, lista ma rodzaj przyjmujący typ jako parametr i zwracający na podstawie owego parametru inny, konkretny typ. Np. dla Int otrzymamy: Prelude> :k [] Int [] Int :: * czyli już konkretny typ.

Rodzaje A co, jeśli rodzaj wymaga podania więcej, niż jednego typu? Prelude> :k Either Either :: * -> * -> * Jeśli podamy tylko jeden typ jako parametr, powinniśmy otrzymać już coś konkretniejszego, ale wciąż wymagającego dodatkowego skonkretyzowania. Rzeczywiście: Prelude> :k Either Int Either Int :: * -> *

Rodzaje Dopiero po podaniu dwóch typów jako parametrów otrzymamy pełnoprawny, konkretny typ: Prelude> :k Either Int Double Either Int Double :: *

Wejście i wyjście Zdefiniujmy prostą funkcję wypisującą pewien tekst do konsoli: let printhaskell = putstrln "Haskell" Nasuwa się pytanie co zwróci nam funkcja putstrln? Możemy to sprawdzić za pomocą komendy :t.

Wejście i wyjście Prelude> :t printhaskell printhaskell :: IO () printhaskell jest zatem funkcją, która nie przyjmuje żadnych parametrów i wykonuje akcję IO (skrót od angielskiego Input-Output), która w trakcie działania utworzy krotkę (ang. tuple) (). W krotce oczywiście mogą znaleźć się pewne dane, jednak w przypadku wypisania listy znaków do konsoli nie bardzo jest co w niej umieścić, dlatego też jest przedstawiona jako pusta.

Wejście i wyjście Napiszmy teraz pełnoprawny program (tj. poza GHCI), który połączy ze sobą kilka akcji IO, by zobaczyć, o co tu właściwie chodzi. Umieszczamy zatem w pliku o nazwie program.hs następującą funkcję: main = do putstrln "Podaj liczbę: " x <- getline putstrln ("cos(" ++ x ++ ") = " ++ show (cos (read x)))

Wejście i wyjście Wyniki akcji możemy przypisywać do nazw, z wyjątkiem akcji wykonanej na końcu bloku do. Plik umieszczamy w folderze C:\Haskell i kompilujemy go poleceniem: ghc C:\Haskell\program.hs Jako wynik dostajemy plik wykonywalny, a także plik interfejsu (.hi) oraz plik obiektowy (.o). Uruchamiamy program.exe.

Wejście i wyjście C:\Haskell\Program.exe Podaj liczbę: 5.1 cos(5.1) = 0.37797774271298024 Wykonane zostały tutaj trzy akcje IO, jedno wczytanie (getline) oraz dwa wypisania (putstrln). Sprawdziliśmy już typ putstrln, jednak co dostaniemy w przypadku getline, które teoretycznie zwraca coś konkretnego?

Wejście i wyjście Prelude> :t getline getline :: IO String String jest synonimem dla listy znaków postaci [Char]. Funkcja getline wykona zatem pewną akcję IO, w trakcie której utworzy wartość będącą Stringiem. By wydostać tę wartość z wnętrza getline już po wykonaniu akcji, stosujemy konstrukcję <-.

Wejście i wyjście Uzyskaną i przypisaną na poprzednim slajdzie do x listę możemy później dowolnie wykorzystywać w programie. Jest to uwaga o tyle istotna (szczególnie jeśli chodzi o przypisanie do x), że nie możemy zapisać tego programu w krótszej postaci, która wydawałaby się mieć sens: putstrln ("cos(" ++ x ++ ") = " ++ show (cos (read getline)))

Wejście i wyjście Dzieje się tak, ponieważ getline jako funkcja nie zwraca listy znaków. Jej typ (IO String) mówi nam jedynie, że dopiero po wykonaniu akcji IO (w tym wypadku przyjęciu danych z klawiatury) będzie można wydobyć z niej Stringa, tak więc musimy mieć tu pewną pośredniość, którą uzyskaliśmy dzięki wykorzystaniu x. Warto zauważyć, że w programie w miejscu wykonania getline nie zastosowaliśmy =, a <-, co daje nam do zrozumienia, iż nie jest to zwykłe przypisanie wyniku.

Wejście i wyjście A co, gdybyśmy spróbowali zastosować = zamiast <-? Wówczas nadalibyśmy tylko getline inną nazwę, którą można by stosować zamiennie. Akcje IO mogą być wykonywane tylko wtedy, gdy są przypisane do main, lub gdy kolejne, zagnieżdżone bloki do, w których się znajdują, ostatecznie wpadną do main.

Wejście i wyjście main = do putstrln "Podaj liczbę: " x <- getline do putstrln "Akcja IO w zagnieżdżonym bloku do" putstrln ("cos(" ++ x ++ ") = " ++ show (cos (read x)))

Wejście i wyjście Podaj liczbę: 10 Akcja IO w zagnieżdżonym bloku do cos(10) = -0.8390715290764524

Wejście i wyjście Korzystanie z akcji IO jest też możliwe z poziomu GHCI, jednak nawet wtedy przed wypisaniem do konsoli poddane zostaną obróbce, m.in. w postaci konwersji na listę znaków funkcją show.

Wejście i wyjście Przeciwieństwem do <- jest return. Zastosowanie tego słowa może być niezwykle mylące, jeśli wcześniej programowało się w językach imperatywnych, tutaj bowiem nie służy ono do zwrócenia wartości, a do jej opakowania, tak jak robi to akcja IO.

Wejście i wyjście main = do x <- return "Something" putstrln (x ++ " in the way") Wynik: Something in the way

Wejście i wyjście Mogłoby się wydawać, że return to jedyna opcja, jeśli chcemy w bloku z akcjami IO przypisać pewnej konkretnej liście znaków nazwę. Okazuje się jednak, iż wciąż możemy stosować let: main = do let x = "Something" putstrln (x ++ " in the way")

Wejście i wyjście Poznaliśmy już dwie funkcje będące akcjami IO putstrln oraz getline. Teraz przedstawimy kilka dodatkowych, które mogą okazać się przydatne przy korzystaniu z wejścia/wyjścia.

Wejście i wyjście putstr działa dokładnie tak, jak putstrln, z tą różnicą, że nie przechodzi automatycznie do kolejnej linii. Prelude> putstr "Test" TestPrelude>

Wejście i wyjście putchar działa jak putstr, jednak pobiera jeden znak, a nie całą ich listę. TestPrelude> putchar 'x' xprelude>

Wejście i wyjście print wypisuje za pomocą funkcji show dowolną wartość, oczywiście o ile owa wartość jest instancją Show. Prelude> print 100 100 Prelude> print ["A", "B"] ["A","B"]

Wejście i wyjście getchar działa jak getline, jednak zamiast całej listy pobiera tylko jeden znak. import Data.Char main = do currentchar <- getchar if (isupper currentchar) then do putchar currentchar main else return ()

Wejście i wyjście when pozwala na skrócenie instrukcji warunkowej przy korzystaniu z akcji IO jeśli warunek jest spełniony, zwróci wynik akcji przekazanej jako drugi parametr, a w przeciwnym razie pustą krotkę. import Control.Monad import Data.Char main = do currentchar <- getchar when (isupper currentchar) $ do putchar currentchar main

Wejście i wyjście sequence wykonuje przekazaną jako parametr listę akcji IO, tworząc listę wyników. import Control.Monad main = do resultslist <- sequence [getline, getline] print resultslist

Wejście i wyjście mapm, mapm_ wykonują zadaną funkcję mapującą na liście, przy czym druga wersja odrzuca dodatkowy wynik w postaci listy pustych krotek. Prelude> mapm print [1, 2] 1 2 [(),()] Prelude> mapm_ print [1, 2] 1 2

Wejście i wyjście forever wykonuje podaną akcję IO bez końca. (Przykład na następnym slajdzie.)

Wejście i wyjście import Control.Monad import System.Random main = do forever $ do randx <- randomio :: IO Int randy <- randomio :: IO Int let max = 500 putstrln ("Ile to jest " ++ show (randx `mod` max) ++ " + " ++ show (randy `mod` max) ++ "?") givenresult <- getline if (givenresult == show (randx `mod` max + randy `mod` max)) then print "Brawo!" else print ("Zle! Prawidlowa odpowiedz to " ++ show (randx `mod` max + randy `mod` max) ++ "!")

Wejście i wyjście form działa jak mapm, jednak ma parametry zamienione miejscami, co pozwala np. na wygodne zastosowanie wyrażenia lambda (w mapm musielibyśmy najpierw wprowadzić wyrażenie lambda, a dopiero potem parametr, co bardzo utrudniłoby zrozumienie kodu). (Przykład na następnym slajdzie).

Wejście i wyjście import Control.Monad main = do let personslist = ["John", "Jenny", "Barry"] form personslist (\name -> do putstrln ("Hello, " ++ name ++ "!"))

showeet.com Dużo złych rzeczy Kamila Dzwonkowska

OPERACJE WEJŚCIA-WYJŚCIA getchar czyta pojedynczy znak z terminala getchar :: IO Char getline czyta całą linię z terminala getline :: IO String getcontents operacja ta czyta wszystko z wejścia aż do napotkania znaku EOF getcontents :: IO String Stosuje leniwe rodzaj wejścia-wyjścia, ponieważ czyta wskazaną rzecz dopiero kiedy jest ona potrzebna. Taki sposób jest pożyteczny kiedy wyjście z jednego programu jest wejściem naszego programu.

interact :: (String String) IO () interact bierze funkcję typu String String jako argument. Całe wejście jest przekazywane do funkcji jako jego argument i w rezultacie zmienna typu string jest na wyjściu. Może być używany w programach gdzie otrzymują constents, a następnie oddają wynik. Także gdzie użytkownik podaje na wejściu jedną linię i program musi podać wynik dla tej linii, a następnie ponownie użytkownik wspisuje pewne dane I ponownie czeka na wynik. Jednak dużo zależy co dokładnie chcemy otrzymać za program.

Operacje na plikach type FilePath = String Nazwy plików i katalogów są wartościami typu String, które są zależne od systemu operacyjnego. P liki mogą być otwarte ułatwiając pracę w treści pliku. readfile :: FilePath IO String Funkcja ta czyta plik I zwraca jego zawartość jako string. Stosuje leniwą operację, podobnie jak getcontents. writefile :: FilePath String IO () Funkcja zajmuje ścieżkę do pliku i zapisuje w niej zmienną typu String kiedy inna funkcja zwróci wyjście do zapisania. Jeśli plik już istnieje to zostanie on wyczyszczony przed zapisaniem.

Warto także wspomnieć data Handle Haskell definiuje operacje dla odczytu i zapisu znaków do pliku, reprezentowane są przez wartości typu Handle. Każda wartośc tego typu jest uchwytem. Właściwości Handle: zarządzanie wejściem, wyjściem lub oboma może być otwarty, zamknięty lub częściowo zamknięty obiekt jest mozliwy do przeszukania buforowanie jest wyłączone lub włączone stdin :: Handle zarządza wejściem stdout :: Handle zarządza wyjściem stderr :: Handle zarządza wyjściem kiedy w programie jest błąd

withfile :: FilePath IOMode (Handle IO r) IO r Otwiera plik używając OpenFile i przekazuje uzyskany uchwyt do obliczeń. Uchwyt będzie wtedy zamknięty. openfile :: FilePath IOMode IO Handle Alokuje i zwraca nowy, otwart uchwyt do zarządzania plikiem. Zarządza on wejściem jeśli tryb jest ReadMode, a wyjście ma tryb WriteMode lub ReadMode i oba jeśli wejście I wyjście ma tryb ReadWriteMode. Jeśli plik nie istnieje lub jest otwarty dla wyjścia, to należy go stworzyć jako nowy plik. Jeśli jest tryb WriteMode I plik już istnieje, to wtedy zostanie wyczyszczony. Niektóre systemy w momencie kiedy plik zostanie wyczyszczony to go usuwają, więc nie ma gwaracji, że plik po użyciu OpenFile w trybie zapisu wciąż będzie istniał.

appendfile :: FilePath String IO () Różnica pomiędzy poprzednią funkcją jest taka, że appendfile nie czyści pliku ale jeśli już istnieje to tylko dołącza do niego następne rzeczy. System.Directory Teraz ta zabawna część z katalogami createdirectory :: FilePath IO () Tworzy nowy katalog, który początkowo nie istnieje. Ma parę ładnych errorów przez które może nie wyjść, np. ispermissionerror/ PermissionDenied proces miał niewystarczające uprawnienia do operacji. Katalog może już istnieć, może wystąpić fizyczny błąd systemu, nazwa katalogu nie może zostać przyjęta, nie istnieje ścieżka do tego katalogu lub brak miejsca w pamięci.

createdirectoryifmissing :: Bool jeśli chcemy stworzyć rodziców katalogu FilePath dodać ścieżkę do katalogu IO () jeśli pierwszy argument ma wartość True to funkcja stworzy rodziców katalogu jeśli ich nie ma/ nie można ich odnaleźć removedirectory :: FilePath IO () Usuwa istniejące już katalogi. Również jest podatny na errory. Występił fizyczny błąd, nie istnieje już katalog z taką nazwą, proces nie miał wystarczający uprawnień do usunięcia katalogu, nie można usunąć katalogu w tym momencie, nie istnieje do niego ścieżka.

renamedirectory :: FilePath FilePath IO () Zmienia nazwę katalogu. Najpierw trzeba podać ścieżkę ze starą nazwą a później z nową. Nie można zmienić nazwy, jeśli proponowana nowa nazwa już aktualnie istnieje. getcurrentdirectory :: IO FilePath Zwraca ścieżkę do katalogu, w którym się znajdujemy. Operacje na plikach removefile :: FilePath IO () Usuwa plik, ale tylko jeśli nie jest otwarty w programie.

renamefile :: FilePath FilePath IO () Zmienia nazwę pliku, który już istnieje w taki sposób, że zastępuje stary plik nowym plikiem z nową nazwą. Możemy również sprawdzić czy plik lub katalog istnieje doesfileexist :: FilePath IO Bool doesdirectoryexist :: FilePath IO Bool zwraca nam to wartość True albo False

Losowe dane Często w programach są nam potrzebne losowe dane, np. do zwykłych testów. Najprostszym przykładem losowości jest to polecenie randomnumber :: (Num a) => a wtedy określamy wartość randomnumber np. randomnumber = 13 wtedy zawsze zostanie nam szczęśliwa trzynastka, ale to kiepska losowość. Jeśli to można tak nazwać. W Haskellu możemy dostać losową liczbę dopiero gdy wykonujemy funckję, która przyjmuje jako parametr jej losowość i w oparciu o parametr zwraca pewną liczbę lub inny typ danych jaki określimy.

System.Random Ta biblioteka ma za zadanie rozwiązać nasz problem z pseudolosowym generowaniem liczb. Umożliwia ona generowanie powtarzalnych wyników w oparciu o pewien zalążek wokół którego ma generować te liczby. class RandomGen g where Klasa ta zapewnia wspólny interfejs dla generatorów liczb losowych. Metody tej klasy: next :: g -< (Int,g) Zwraca zmienne typu Int, które są równomiernie rozłożone w zakresie zwróconym przez genrange oraz nowy generator.

getrange :: g (Int, Int) Daje zakres wartości zwracanych przez generator. Wymagane jest, aby jeśli (a,b) = genrange g, wtedy a<b genrange zawsze zwraca parę określonych zmiennych typu Int Drugi warunek zapewnia, że genrange nie może badać swojego parametru, a tym samym zwracana wartość jest określona tylko przez RandomGen. To z kolei pozwala na wdrożenie do pojedynczego połączenia genrange ustalenie zakresu danego generatora bez obawy że następny generator będzie miał inny zakres.

split :: g (g,g) Operacja umożliwia uzyskanie dwóch różnych generatorów liczb losowych. Jest to bardzo przydatne w programach funkcjonalnych (np. gdy przechodzimy z generatora liczb losowych do rekurencyjnych wywołań), ale i tak mało kto używa split, bo w większości jest niepotrzebny. Biedny split :(

Standardowe generatory liczb pseudolosowych data StdGen różni się od RandomGen, że genrange ma co najmniej 30 bitów. Jego właściwości: gwarantuje sukces na dowolnym Stringu gwarantuje zużyć tylko skończoną część Stringu różnie zmienne typu String mogą skutkować różnymi wynikami. mkstdgen :: Int StdGen Zapewnia alternatywny sposób produkcji początkowego generatora poprzez mapowanie na Int w generatorze. Różne argumenty powinny produkować różne generatory.

Losowe wartości różnych typów class Random a where Metody tej klasy randomr :: RandomGen g => (a,a) g (a,g) Przyjmuje zasięg (lo,hi) i generator liczb losowych. Zwraca losową wartość równomiernie rozłożoną w zamkniętym przedziale [lo,hi] wraz z nowym generatorem. Nie określa to co zajdzie wtedy gdy lo będzie większe od hi (lo>hi). Dla stałych typów nie ma wymogu, że wartości lo i hi będą wyprodukowane, ale mogą być, wszystko zależy od implementacji I przedziału.

random :: RandomGen g => g (a,g) Tak samo jak w przypadku randomr, ale używamy domyślnego zakresu dla danego typu. Dla ograniczonych typów (np. Char) zakres jest wykle całym typem. Dla typów ułamkowych zakres jest zwykle częściowo zamknięty w przedziale [0,1). Dla Integer zakresem jest (arbitalnie) zakres Int.

randomrs :: RandomGen g => (a, a) -> g -> [a] Wariant randomr tworzący nieskończoną listę wartości losowych zamiast zwrócić nowy generator. randoms :: RandomGen g => g -> [a] Wariant random tworzący nieskończoną listę wartości losowych zamiast zwrócić nowy generator. randomrio :: (a, a) -> IO a Wariant randomr, który korzysta z globalnego generatora liczb losowych. randomio :: IO a Wariant random, który korzysta z globalnego generatora liczb losowych.