200 likes | 291 Views
A comprehensive guide to higher-order types, type constructors, Functors, Monads, file handling, IO operations, and error handling in Haskell programming language. Learn about kinds, classes, instances, and more.
E N D
Higher Order types • Type constructors are higher order since they take types as input and return types as output. • Some type constructors (and also some class definitions) are even higher order, since they take type constructors as arguments. • Haskell’s Kind system • A Kind is haskell’s way of “typing” types • Ordinary types have kind * • Int :: * • [ String ] :: * • Type constructors have kind * -> * • Tree :: * -> * • [] :: * -> * • (,) :: * -> * -> *
The Functor Class class Functor f where fmap :: (a -> b) -> (f a -> f b) • Note how the class Functorrequires a type constructor of kind * -> *as an argument. • The method fmap abstracts the operation of applying a function on every parametric Argument. a a a Type T a = x x x (f x) (f x) (f x) fmap f
Notes • Special syntax for built in type constructors (->) :: * -> * -> * [] :: * -> * (,) :: * -> * -> * (,,) :: * -> * -> * -> * • Most class definitions have some implicit laws that all instances should obey. The laws for Functor are: fmap id = id fmap (f . g) = fmap f . fmap g
Instances of class functor data Tree a = Leaf a | Branch (Tree a) (Tree a) instance Functor Tree where fmap f (Leaf x) = Leaf (f x) fmap f (Branch x y) = Branch (fmap f x) (fmap f y) instance Functor ((,) c) where fmap f (x,y) = (x, f y)
More Instances instance Functor [] where fmap f [] = [] fmap f (x:xs) = f x : fmap f xs instance Functor Maybe where fmap f Nothing = Nothing fmap f (Just x) = Just (f x)
Other uses of Higher order T.C.’s data Tree t a = Tip a | Node (t (Tree t a)) t1 = Node [Tip 3, Tip 0] Main> :t t1 t1 :: Tree [] Int data Bin x = Two x x t2 = Node (Two(Tip 5) (Tip 21)) Main> :t t2 t2 :: Tree Bin Int
What is the kind of Tree? • Tree is a binary type constructor • It’s kind will be something like: ? -> ? -> * • The first argument to Tree is itself a type constructor, the second is just an ordinary type. • Tree :: (* -> *)-> * -> *
Functor instances of Tree instance Functor (Tree2 Bin) where fmap f (Tip x) = Tip(f x) fmap f (Node (Two x y)) = Node (Two (fmap f x) (fmap f y)) instance Functor (Tree2 []) where fmap f (Tip x) = Tip(f x) fmap f (Node xs) = Node (map (fmap f) xs)
Can we do better instance Functor t => Functor (Tree2 t) where fmap f (Tip x) = Tip(f x) fmap f (Node xs) = Node (fmap (fmap f) xs)
The Monad Class Note m is a type constructor class Monad m where (>>=) :: m a -> (a -> m b) -> m b (>>) :: m a -> m b -> m b return :: a -> m a fail :: String -> m a p >> q = p >>= \ _ -> q fail s = error s
Generic Monad functions sequence :: Monad m => [m a] -> m [a] sequence = foldrmcons (return []) where mcons p q = do x <- p xs <- q return (x:xs) sequence_ :: Monad m => [m a] -> m () sequence_ = foldr (>>) (return ()) mapM :: Monad m => (a -> m b) -> [a] -> m [b] mapM f as = sequence (map f as) mapM_ :: Monad m => (a -> m b) -> [a] -> m () mapM_ f as = sequence_ (map f as) (=<<) :: Monad m => (a -> m b) -> m a -> m b f =<< x = x >>= f
Files and Handles • The functions: import System.IO writeFile:: FilePath -> String -> IO () appendFile :: FilePath -> String -> IO () are used to read and write to files, but they incur quite a bit of overhead if they are used many times in a row. Instead we wish to open a file once, then make many actions on the file before we close it for a final time. openFile :: FilePath -> IOMode -> IO Handle hClose :: Handle -> IO () data IOMode = ReadMode | WriteMode | AppendMode deriving (Eq, Ord, Ix, Bounded, Enum, Read, Show)
File Modes • A file mode tells how an open file will be used. Different modes support different operations. • When in WriteMode hPutChar :: Handle -> Char -> IO () hPutStr :: Handle -> String -> IO () hPutStrLn :: Handle -> String -> IO () hPrint :: Show a => Handle -> a -> IO () • When in ReadMode hGetChar :: Handle -> IO Char hGetLine :: Handle -> IO String
Standard Channels and Errors • Predefined standard Channels stdin, stdout, stderr :: Handle • Error Handling while doing IO isEOFError :: IOError -> Bool -- Test if the EOF error ioError :: IOError -> IO a -- Raise an IOError catch :: IO a -> (IOError -> IO a) -> IO a -- Handle an Error • Other IO types of errors and their predicates. isAlreadyExistsError, isDoesNotExistError, isAlreadyInUseError, isFullError, isEOFError, isIllegalOperation, isPermissionError, isUserError,
IOError • IOError is an abstract datatype • NOT and algebraic datatype, defined with data like [ ] orTree • Thus it does not admit pattern matching. • Hence the use of all the IOError recognizing predicates. • isAlreadyExistsError, isDoesNotExistError, • isAlreadyInUseError, isFullError, • isEOFError, isIllegalOperation, • isPermissionError, isUserError • This was a concious decision, made to allow easy extension of the kinds of IOErrors, as the system grew.
Handling IO Errors • Any action of type IO a may potentially cause an IO Error. • The function catch ::IO a -> (IOError -> IO a) -> IO a can be used to gracefully handle such an error by providing a “fix” getChar' :: IO Char getChar' = catch getChar (\ e -> return '\n') getChar2 :: IO Char getChar2 = catch getChar (\ e -> if isEOFError e then return '\n' else ioError e) –- pass non EOF errors on
An Example getLine' :: IO String getLine' = catch getLine'' (\ e -> return ("Error: " ++ show e)) where getLine'' = do { c <- getChar2 ; if c == '\n' then return "" else do { l <- getLine' ; return (c:l) } }
Catching errors when opening files getAndOpenFile :: String -> IOMode -> IO Handle getAndOpenFile prompt mode = do { putStr prompt ; name <- getLine ; catch (openFile name mode) (\e -> do { putStrLn ("Cannot open: "++name) ; print e ; getAndOpenFile prompt mode }) }
Copying Files main = do { fromHandle <- getAndOpenFile "Copy from: " ReadMode ; toHandle <- getAndOpenFile "Copy to: " WriteMode ; contents <- hGetContentsfromHandle ; hPutStrtoHandle contents ; hClosefromHandle ; hClosetoHandle ; putStr "Done\n" }