reset password
Author Message
rabbott
Posts: 1649
Posted 09:19 Sep 29, 2016 |

A few people have used foldl in project 5a. I'm glad to see you exploring your options.  

Here's a nice way to do sumDigits using foldl.

sumDigits = foldl (\acc i -> acc + i `div` 10 + i `mod` 10) 0

It's worth understanding how to read the type of foldl.

> :t foldl
foldl :: Foldable t => (b -> a -> b) -> b -> t a -> b

For now forget about Foldable t and imagine the type is

> :t foldl
foldl :: (b -> a -> b) -> b -> [a] -> b

This gives you a lot of information about foldl. Why are some of the elements of type a and some of type b? (Both a and b are type variables. They are not specific types.) How do the types in the first argument (the function) connect to the other types? We can talk about it in class if you wish.
 

Last edited by rabbott at 11:40 Sep 29, 2016.
meishu
Posts: 6
Posted 10:07 Sep 29, 2016 |

Anyone interested in learning more about Haskell's performance and laziness might also want to take a look at strictness (basically the opposite of laziness). It's not needed for any of the projects in this course, so if you don't care, you can skip this entire post, especially since it's going to get pretty technical.

In particular, often times (but not always) you can simply use foldl' instead of foldl. A very brief explanation is that by default, Haskell's laziness means that at each step of foldl, Haskell doesn't actually figure out the result of applying the given function yet. It keeps track of it with what's called a thunk, stored on the heap and only evaluated when anything actually needs the result—which you usually do, or else why would you be doing the fold? By instead using foldl', Haskell will apply the function "strictly", or in other words, it will immediately evaluate it and use the result instead of storing a thunk to calculate it later. That means we don't have to push a long chain of thunks onto the stack to evaluate it, potentially overflowing the stack for large lists! The result is a much more efficient calculation.

This page has more information about the differences between foldr, foldl, and foldl', including an example under section 4 ("Conclusion") where foldl' may not be used in place of foldl.

You can force this strictness in general with the strict application operator $!, which is like $ except everything on the right side is immediately evaluated. To go further, both foldl' and $! are defined using seq (more info here), but you probably won't need to go that far anytime soon.

Last edited by meishu at 10:09 Sep 29, 2016.
rabbott
Posts: 1649
Posted 10:11 Sep 29, 2016 |

Also, the function doubleEveryOther has also been simplified and pairs has been eliminated.

doubleEveryOther :: [Int] -> [Int] 
doubleEveryOther ds = zipWith (\m d -> m * d) cycle121 ds
   where cycle121 = case even (length ds) of
                    True  -> cycle [2,1]
                    False -> cycle [1,2]

rabbott
Posts: 1649
Posted 18:28 Sep 29, 2016 |

Here's a bit more about fold.  As you know it's possible to fold from left or right. We can fold from the right and do the doubling at the same time as we take the sum.

In this case, the function passed to foldr takes the accumulator as its second argument rather than its first argument. We use the accumulator to keep track of both the accumulated sum so far as well as whether we are up to an even or odd digit (counting from the right). So the second argument to the foldr function is (acc, even), where acc is the sum so far and even is True or False depending on whether we are up to an even or odd digit. When we're done we use fst in checksum to retrieve the sum. Note that the function passed to foldr returns the pair (acc + nextStep even i, not even) to be passed on to the next step.

doubleAndSum :: [Int] -> (Int, Bool)
doubleAndSum = foldr (\i (acc, even) -> (acc + nextStep even i, not even))  (0, False)
  where 
     nextStep even i
        | even      = (2*i) `div` 10 + (2*i) `mod` 10
        | otherwise = i

checkSum :: String -> Int
checkSum = fst . doubleAndSum . toDigits 

isValid :: String -> Bool
isValid' n = checkSum' n `mod` 10 == 0

testCC :: [Bool]
testCC = map isValid' ["79927398713", "079927398713", "79927398714"] 
-- => [True, True, False]

 

Last edited by rabbott at 19:00 Sep 29, 2016.