-
-
Save paulcc/4477087 to your computer and use it in GitHub Desktop.
Haskell version of code from http://blog.8thlight.com/uncle-bob/2013/01/07/FPBE3-Do-the-rules-change.html
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- see below for commentary | |
makeUpTo :: Int -> [String] -> [String] | |
makeUpTo n = mlu [] | |
where | |
mlu pre [] = [pre] | |
mlu [] (w:ws) = mlu w ws | |
mlu pre (w:ws) | length pre + 1 + length w <= n = mlu (pre ++ ' ' : w) ws | |
| otherwise = pre : mlu [] (w:ws) | |
t2 n i = putStrLn | |
$ unlines | |
$ makeUpTo n | |
$ [ frag | w <- words i, frag <- splitIntoSize n w ] | |
-- basically a library function, and useful for other kinds of list | |
splitIntoSize :: Int -> [a] -> [[a]] | |
splitIntoSize n = map (take n) . takeWhile (not . null) . iterate (drop n) | |
------------------------------------------------------------------------------------------------------ | |
{- | |
Responding (partially and selectively) to a few points from Bob's blog post | |
http://blog.8thlight.com/uncle-bob/2013/01/07/FPBE3-Do-the-rules-change.html | |
"My point is that not all functional programs need to be sequences of transformations." | |
True. | |
"The old rule of “simpler is better” still applies." | |
Quite agree, but not necessarily on which is simpler (-: | |
Style | |
===== | |
"Still, it’s hard for me to believe that my first solution to the word wrap is inferior to the above code." | |
I prefer the above because IMHO it is easier to see how the calculation is done. So quite deliberately, | |
I'm breaking the problem into a few independent pieces and then joining them together. | |
* splitting the input into words | |
* ensuring that no word is longer than n by splitting it into blocks of n (and one final one of length between 1 and n) | |
* taking the list of pieces and packing them into a line | |
* formatting the end result | |
Bob's "break-long-words" is a bit more complex than it needs to be because it merges two or more of the above | |
when (as shown above) it can be simplified. Similarly, "make-lines-up-to" could also be broken down a bit more. | |
It's a matter of taste and experience for how much to decompose. I've had a bit of practice (setting too many | |
undergrad exams...), but I also like to spin off small functions that might be useful elsewhere. Eg splitting | |
a list into length-N chunks is a common thing, so useful to have. Such things might also be in one of the | |
libraries - and identifying such simple operations also gives clues on where to look if I didn't already | |
know what it was called. | |
Articulacy | |
========== | |
I think the Haskell code above is clearer than the clojure version. Yes? No? | |
Bob's code (both versions) can be written in Haskell, and that would be an interesting comparison. | |
Note that I am deliberately aiming for a "Obviously no errors" style of coding, ie to use the | |
language to explain what the transformation as clearly as I can. (It could be better.) | |
Testing in Haskell | |
================== | |
It's a thing, and there's several options - including fairly classic unit testing frameworks and QuickCheck. | |
Left as an exercise for the reader. | |
Type support | |
============ | |
I suggest this is useful in various places. As discussed in my second prag pub article, types provide a | |
good language for expressing what each piece should do, and also a good check at compile time that the | |
code matches those intentions. It's also a good way to encourage thought about reuse/generalisation - eg | |
Int -> [a] -> [[a]] says something of what the split-into-size-N operation does, and what it assumes. | |
Clearly, Haskell types don't cover everything. Dependent types allow us to say a lot more, and cover | |
some of the properties that we are keen to be very sure about. Eg, can we be sure that the line packing | |
code never generates an over-long line? I'll show the code for that later (it's late here). | |
Summary | |
======= | |
This Haskell version tries to reflect my understanding of the problem more transparently. Moreover, I'm trying | |
to think less - IMHO it takes a bit more work to understand code when it's combining several aspects which can | |
be separated. I prefer to work on small pieces at a time and then join them together. | |
There's plenty more to say, like understanding how the TDD-grown version compares to the types-oriented version, | |
and seeing where dependent types can plug the gaps. | |
Pedantry | |
======== | |
The informal spec mentions inserting newlines but the test specs are replacing spaces too... | |
I'm not sure whether the handling of long words has been tied down fully - eg "foo cake" with width 6: should it be "foo ca\nke" or "foo\ncake" ? | |
My code follows the latter since that's the behaviour in Bob's pipeline version. |
Would be interesting to see the properties defined in his tests extracted for quick check properties.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
IMHO the aim is to write as clearly as we can, and splitting up the transformations is one way to do it.
Will expand on this later. Might talk dependent types too.