0 odds and ends in haskell: folding, i/o, and functors adapted from material by miran lipovaca

23
1 Odds and Ends in Haskell: Folding, I/O, and Functors Adapted from material by Miran Lipovaca

Upload: garey-francis

Post on 13-Dec-2015

217 views

Category:

Documents


3 download

TRANSCRIPT

1

Odds and Ends in Haskell:

Folding, I/O, and Functors

Adapted from material by Miran Lipovaca

2

The foldl functionWe’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

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

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

Another example: elemReturns True if the variable is present in the listelem' :: (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

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

File I/OSo 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

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

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

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

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

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

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

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

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

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

Other I/O functions: -print (works on any type in show, but calls show first)-putStr - And as putStrLn, but no newline-putChar and getCharmain = 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

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

FunctorsFunctors 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 bThis 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

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

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

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

Using it: ghci> fmap (++ " HEY GUYS IM INSIDE THE  JUST") (Just "Something serious.")  Just "Something serious. HEY GUYS IM INSIDE THE JUST"  ghci> fmap (++ " HEY GUYS IM INSIDE THE  JUST") Nothing  Nothing  ghci> fmap (*2) (Just 200)  Just 400  ghci> fmap (*2) Nothing  Nothing