isSpace

words



words :: String -> [String]

words string = case dropWhile isSpace string of

[] -> []

s -> word : words rest

where (word, rest) = break isSpace s



:f words

s

s

break

s

case



words string = case dropWhile isSpace s of

[] -> []

s:ss -> word : words rest

where (word, rest) = break isSpace (s:ss)



s

not (isSpace s)

break

span



span, break :: (a -> Bool) -> [a] -> ([a],[a])

span p [] = ([],[])

span p xs@(x:xs')

| p x = (x:ys, zs)

| otherwise = ([],xs)

where (ys,zs) = span p xs'

break p = span (not . p)



break



s:ss -> word : words rest

where (word, rest) = span (not . isSpace) (s:ss)



span

xs@(x:xs')

not (isSpace s)



s:ss -> word : words rest

where (word, rest) = (s:ys, zs)

(ys,zs) = span (not . isSpace) ss



word

s:ys

rest

zs

where



s:ss -> (s:ys) : words zs

where (ys,zs) = span (not . isSpace) ss



ys

word

zs

rest



s:ss -> (s:word) : words rest

where (word,rest) = span (not . isSpace) ss



span

break



s:ss -> (s:word) : words rest

where (word,rest) = break isSpace ss





words :: String -> [String]

words string = case dropWhile isSpace string of

[] -> []

s:ss -> (s:word) : words rest

where (word,rest) = break isSpace ss



words

break

(not . isSpace)

span

Haskell is great, as it's alanguage. This means that functions don't have side effects, so can be reordered, rearranged, inlined etc. very easily. All this means that you can do reasoning in Haskell in a similar manner to that which is done in mathematics. Most Haskell programmers will exploit the reasoning properties very often, refactoring their code. Today I used equational reasoning to solve a problem I was having, which makes a nice (and simple!) introduction.This work was all in the context of the Supero project, aiming to optimise a word counting program. I found that in the resultant code, some characters were getting tested for being a space twice (using). An additional test, and then a branch on a value which is already known, harms performance. There are two possible reasons for this duplicated test: either the input code does the test twice; or the optimiser looses some sharing. After a quick look around I decided that the latter was not occuring, so went to look at the source ofIf you are using Hugs, a simplewill show you this code, which I've renamed and reformatted slightly.The key "eureka" moment is to see thathas all it's leading spaces dropped. Therefore, ifhas any characters, the first must be a non-space. We then retest the first character to see if it is a space in. This redundant test is unnecessary, but how to remove it? In Haskell equational reasoning can be used to remove the test, and serves as a proof that the code still has the same functionality.The first step is to instantiatein thealternative. This is safe as the branch above has already examined the constructor, so we do not loose laziness.Now we know thatis not a space, specifically thatis true. From now on we will work only within the second case alternative. We now need the definition ofandto proceed further:Now we can inlineNow looking atwe can see that we match thebranch. Furthermore, we know from earlier thatis True so we will take the first alternative. This lets us rewrite the expression as:Now we are nearly there. We can replacewithandwithusing the firstbinding:Now we can renametoandtoAnd finally, to get us back to something very much like our original, we can fold back the. Just as we were able to replace the left hand side ofwith the right hand side, we can do the same in the other direction:Now putting this all back into the original definition:We now have a more efficient version ofwhich at every stage kept the same behaviour. I have emailed the Haskell libraries mailing list and hopefully this optimisation will be able to be applied to the standard libraries.In reality, when I did this transformation I skipped the inlining ofand relied on what I knew about its behaviour. If a student was to do this transformation in an exam, more detail would probably be required going fromin. Either way, as your experience grows these transformations become more natural, and are a fundamental part of working with Haskell.