Data Constructors and Readability

Thursday, February 5th, 2009

I think I’ve put my finger on one reason I’m finding Haskell hard to read. Consider this algebraic data type definition from Real World Haskell:

data Doc = Empty
         | Char Char
         | Text String
         | Line
         | Concat Doc Doc
         | Union Doc Doc
         deriving (Show, Eq)

There’s no distinction between the value constructor and the components of the type. This is especially critical when the constructor and the components have the same name as in the Char constructor above. It also doesn’t help that they have the same naming convention (mixed camel case, initial uppercase letter).

A Java class with multiple factory methods or multiple constructors would be much more verbose, but much more readable. Humans need redundancy, even if it’s logically superfluous.

I suspect a language that retained Haskell’s semantics, but completely replaced the syntax with something more usable, legible, and familiar would have a much greater chance of success.

unlines with fold

Tuesday, January 27th, 2009

Real World Haskell, Exercise 10, p. 99: This was an easy one:

myUnlines :: [String] -> String
myUnlines xs = foldr (addLine) "" xs 
    where addLine c acc = acc ++ c ++ "\n"

It’s pretty much straight down the path of exactly what folds are meant for, though the inferred types still managed to surprise me, and my initial implementation was about twice this size.

words with fold

Monday, January 26th, 2009

Real World Haskell, Exercise 10, p. 98: Write the words function using list folds:

myWords :: String -> [String]
myWords s = reverse (foldr step [] (trim s))

step :: Char -> [String] -> [String]
step c [] = [c:[]]
step c acc 
    | (isSpace c) = acc ++ [""]
    | otherwise = (take ((length acc) - 1) acc) ++ [c:(last acc)]

isSpace :: Char -> Bool
isSpace ' '  = True
isSpace '\n' = True
isSpace '\r' = True
isSpace '\t' = True
isSpace _    = False

lstrip :: String -> String
lstrip [] = ""
lstrip (x:xs) 
  | (isSpace x) = lstrip xs
  | otherwise = x:xs

rstrip :: String -> String
rstrip s = reverse (lstrip (reverse s))

trim :: String -> String
trim = lstrip . rstrip

Moral of this exercise: you can only pass a list to ++. You can’t use ++ to append a Char to a String. You have to append [c] instead.

Hypothesis: cycles cannot be implemented only with folds

Saturday, January 24th, 2009

The cycle function turns a list into an infinite list by repeating it. For instance, cycle [1,2,3] is [1,2,3,1,2,3,1,2,3...]. I could be wrong but I don’t think you can do this one purely with folds and here’s why:

any with foldr

Friday, January 23rd, 2009
myAny :: (a -> Bool) -> [a] -> Bool
myAny f [] = False
myAny f (x:xs) = foldr step False xs
     where step x acc = acc || f x 

I’m beginning to hate type inference. Yes the compiler can figure out the types, but the human (i.e. me) often can’t without running the compiler. Redundancy and verbosity are not bugs. They are features. Human language (e.g. English) is verbose and redundant for good reason. Redundancy helps humans understand.

The source code is not just for the compiler. Otherwise we’d write in machine language. User interface factors need to be considered in the design of programming languages.

foldr Starts at the Head

Wednesday, January 21st, 2009

It took me long enough to realize that foldr still moves from the beginning to the end of a list. Somehow I thought it started at the right (i.e. the tail) of the list. Once I realized that Exercise 7 was easy:

Write your own definition of the standard takeWhile function, first using explicit recursion, and then foldr.