230 likes | 246 Views
Learn about folding functions in Haskell for recursion, I/O functions like putStrLn, and working with functors in this adapted material. Dive into practical examples and understand how to handle file I/O in Haskell effectively.
E N D
Odds and Ends in Haskell: Folding, I/O, and Functors Adapted from material by Miran Lipovaca
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”
Example: implementing the sum function sum'::(Numa)=>[a]->a sum'xs=foldl(\accx-> 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
In fact, we can write this function in an even shorter way, since functions can be returned as parameters: sum'::(Numa)=>[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.
Another example: elem Returns True if the variable is present in the list elem'::(Eqa)=>a->[a]->Bool elem'yys=foldl (\accx>ifx==ythenTrueelseacc) Falseys • - 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. 4
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.
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.
A simple example: save the following file as helloword.hs main=putStrLn"hello,world" Now we actually compile a program: $ghc--makehelloworld [1of1]CompilingMain (helloworld.hs,helloworld.o) Linkinghelloworld... $./helloworld hello,world 7
What are these functions? ghci>:tputStrLn putStrLn::String->IO() ghci>:tputStrLn"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. 8
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'syourname?” name<-getLine putStrLn("Hey"++name++", yourock!") Notice the do statement - more imperative style. Each step is an I/O action, and these glue together. 9
More on getLine: ghci>:tgetLine getLine::IOString 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. 10
An invalid example: nameTag="Hello,mynameis"++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 11
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 12
You can use let statements inside do blocks, to call other functions (and with no “in” part required): importData.Char main=do putStrLn"What'syourfirstname?" firstName<-getLine putStrLn"What'syourlastname?" lastName<-getLine letbigFirstName=maptoUpperfirstName bigLastName=maptoUpperlastName putStrLn$"hey"++bigFirstName++""++ bigLastName++",howareyou?" Note that <- is for I/O, and let for expressions. 13
Return in haskell: NOT like other languages. main=do line<-getLine ifnullline thenreturn() elsedo putStrLn$reverseWordslinemain reverseWords::String->String reverseWords=unwords mapreverse.words 14
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. 15
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=doprintTrue print2 print"haha" print3.2 print[3,4,3] main=do c<-getChar ifc/='' thendo putCharc main elsereturn() 16
More advanced functionality is available in Control.Monad: importControl.Monad importData.Char main=forever$do putStr"Givemesomeinput:" l<-getLine putStrLn$maptoUpperl (Will indefinitely ask for input and print it back out capitalized.) 17
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. classFunctorfwhere fmap::(a->b)->fa->fb 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. 18
Compare fmap to map: fmap::(a->b)->fa->fb 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: instanceFunctor[]where fmap=map 19
Notice what we wrote: instanceFunctor[]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. 20
Another example: instanceFunctorMaybewhere fmapf(Justx)=Just(fx) fmapfNothing=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. 21
Using it: ghci>fmap(++"HEYGUYSIMINSIDETHE JUST")(Just"Somethingserious.") Just"Somethingserious.HEYGUYSIMINSIDETHEJUST" ghci>fmap(++"HEYGUYSIMINSIDETHE JUST")Nothing Nothing ghci>fmap(*2)(Just200) Just400 ghci>fmap(*2)Nothing Nothing 22