Skip to content

Instantly share code, notes, and snippets.

@paulcc
Forked from anonymous/gist:4477082
Last active December 10, 2015 18:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save paulcc/4477087 to your computer and use it in GitHub Desktop.
Save paulcc/4477087 to your computer and use it in GitHub Desktop.
-- 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.
@paulcc
Copy link
Author

paulcc commented Jan 7, 2013

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.

@johnbender
Copy link

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