Download presentation
Presentation is loading. Please wait.
Published byGarey Francis Modified over 9 years ago
1
0 Odds and Ends in Haskell: Folding, I/O, and Functors Adapted from material by Miran Lipovaca
2
1 The foldl function We’ve seen a particular pattern quite often with lists: - base case on empty list - some operation with the head, plus a recursive call on the tail This is such a common pattern that there is a higher-order function to handle it. Inputs: a function, a initial starting value (which we’ll call the accumulator, although it can have any name) and a list to “fold up”
3
2 Example: implementing the sum function sum' :: (Num a) => [a] -> a sum' xs = foldl (\acc x -> acc + x) 0 xs The binary function is applied to the accumulator and the first element (in foldl), and produces a new accumulator. Then called again with the new accumulator and the new first element of the list, until the rest of the list is empty. ghci> sum' [3,5,2,1] 11
4
3 In fact, we can write this function in an even shorter way, since functions can be returned as parameters: sum' :: (Num a) => [a] -> a sum' = foldl (+) 0 The lambda function on the previous slide is really the same as (+), and we can omit xs because the function written above will just return a function that takes a list as input.
5
4 Another example: elem Returns True if the variable is present in the list elem' :: (Eq a) => a -> [a] -> Bool elem' y ys = foldl (\acc x > if x == y then True else acc) False ys - Starting value and accumulator are booleans. - Start (and default) is False, which makes sense. - Check if current element is what we want. If so, done (so return True). Otherwise, accumulator is unchanged, and it continues on with the tail.
6
5 Other functions: - Foldr is the same, except starts with the end of the list (and accumulator is the second input to the function). - Scanl and scanr work just the same, but return all intermediate accumulator values in a list. - foldl1 and foldr1 work just the same as foldl and foldr, but don’t need to provide a starting value - they assume first (or last) element of the list is the starting value.
7
6 File I/O So far, we’ve worked mainly at the prompt, and done very little true input or output. This is logical in a functional language, since nothing has side effects! However, this is a problem with I/O, since the whole point is to take input (and hence change some value) and then output something (which requires changing the state of the screen or other I/O device. Luckily, Haskell offers work-arounds that separate the more imperative I/O.
8
7 A simple example: save the following file as helloword.hs main = putStrLn "hello, world" $ ghc --make helloworld [1 of 1] Compiling Main ( helloworld.hs, helloworld.o ) Linking helloworld... $./helloworld hello, world Now we actually compile a program:
9
8 What are these functions? ghci> :t putStrLn putStrLn :: String -> IO () ghci> :t putStrLn "hello, world" putStrLn "hello, world" :: IO () So putStrLn takes a string and returns an I/O action (which has a result type of (), the empty tuple). In Haskell, an I/O action is one with a side effect - usually either reading or printing. Usually some kind of a return value, where () is a dummy value for no return.
10
9 An I/O action will only be performed when you give it the name “main” and then run the program. A more interesting example: main = do putStrLn "Hello, what's your name? ” name <- getLine putStrLn ("Hey " ++ name ++ ", you rock!") Notice the do statement - more imperative style. Each step is an I/O action, and these glue together.
11
10 More on getLine: ghci> :t getLine getLine :: IO String This is the first I/O we’ve seen that doesn’t have an empty tuple type - it has a String. Once the string is returned, we use the <- to bind the result to the specified identifire. Notice this is the first non-functional action we’ve seen, since this function will NOT have the same value every time it is run! This is called “impure” code.
12
11 An invalid example: nameTag = "Hello, my name is " ++ getLine What’s the problem? Well, ++ requires both parameters to have the same type. What is the return type of getLine? Another word of warning: what does the following do? name = getLine
13
12 Just remember that I/O actions are only performed in a few possible places: - A main function - inside a bigger I/O block that we have composed with a do (and remember that the last action can’t be bound to a name, since that is the one that is the return type). -At the ghci prompt: ghci> putStrLn "HEEY" HEEY
14
13 You can use let statements inside do blocks, to call other functions (and with no “in” part required): import Data.Char main = do putStrLn "What's your first name?" firstName <- getLine putStrLn "What's your last name?" lastName <- getLine let bigFirstName = map toUpper firstName bigLastName = map toUpper lastName putStrLn $ "hey " ++ bigFirstName ++ " " ++ bigLastName ++ ", how are you?" Note that <- is for I/O, and let for expressions.
15
14 Return in haskell: NOT like other languages. main = do line <- getLine if null line then return () else do putStrLn $ reverseWords line main reverseWords :: String -> String reverseWords = unwords map reverse. words
16
15 What is return? Does NOT signal the end of execution! Return instead makes an I/O action out of a pure value. main = do a <- return "hell" b <- return "yeah!" putStrLn $ a ++ " " ++ b In essence, return is the opposite of <-. Instead of “unwrapping” I/O Strings, it wraps them.
17
16 Other I/O functions: -print (works on any type in show, but calls show first) -putStr - And as putStrLn, but no newline -putChar and getChar main = do print True print 2 print "haha" print 3.2 print [3,4,3] main = do c <- getChar if c /= ' ' then do putChar c main else return ()
18
17 More advanced functionality is available in Control.Monad: import Control.Monad import Data.Char main = forever $ do putStr "Give me some input: " l <- getLine putStrLn $ map toUpper l (Will indefinitely ask for input and print it back out capitalized.)
19
18 Functors Functors are a typeclass, just like Ord, Eq, Show, and all the others. This one is designed to hold things that can be mapped over; for example, lists are part of this typeclass. class Functor f where fmap :: (a -> b) -> f a -> f b This type is interesting - not like previous exmaples, like in EQ, where (==) :: (Eq a) => a -> a -> Bool. Here, f is NOT a concrete type, but a type constructor that takes one parameter.
20
19 Compare fmap to map: fmap :: (a -> b) -> f a -> f b map :: (a -> b) -> [a] -> [b] So map is a lot like a functor! Here, map takes a function and a list of type a, and returns a list of type b. In fact, can define map in terms of fmap: instance Functor [] where fmap = map
21
20 Notice what we wrote: instance Functor [] where fmap = map We did NOT write “instance Functor [a] where…”, since f has to be a type constructor that takes one type. Here, [a] is already a concrete type, while [] is a type constructor that takes one type and can produce many types, like [Int], [String], [[Int]], etc.
22
21 Another example: instance Functor Maybe where fmap f (Just x) = Just (f x) fmap f Nothing = Nothing Again, we did NOT write “instance Functor (Maybe m) where…”, since functor wants a type constructor. Mentally replace the f’s with Maybe, so fmap acts like (a -> b) -> Maybe a -> Maybe b. If we put (Maybe m), would have (a -> b) -> (Maybe m) a -> (Maybe m) b, which looks wrong.
23
22 Using it: ghci> fmap (++ " HEY GUYS IM INSIDE THE JUST") (Just "Something serious.") Just "Something serious. HEY GUYS IM INSIDE TH E JUST" ghci> fmap (++ " HEY GUYS IM INSIDE THE JUST") Nothing Nothing ghci> fmap (*2) (Just 200) Just 400 ghci> fmap (*2) Nothing Nothing
Similar presentations
© 2024 SlidePlayer.com. Inc.
All rights reserved.