May 6, 2018

Real World Haskell -- Chapter 5

This is the first of two JSON chapters. First my advice. Find the source for the book and figure out how to select the right files and build a working program. That is what this page is about, so follow along with me below. Here is a link so you can download the final result of my tweaking on the source files for this chapter.

This chapter takes neither a top down nor bottom up approach. It takes a "dive into the middle" approach and the reader gets lost and loses sense of where everything is going. In short, the authors lose sight of the forest for the trees, and of course the reader cannot help but do so also. Some people call this an "analytical approach". I that is what you call it, a different approach is called for.

One approach the reader can take is to skim over the details of the JSON parser (which is a silly example anyway, although as good as any I guess). The main point of the chapter is to illustrate the methods for breaking a bigger Haskell program into modules, using Haskell as a compiled language, and things of that sort. So focus on those aspects and let the JSON stuff sort of go blurry and you may get as much as anyone can expect to from this chapter.

However, the authors make a big mistake by not presenting an entire working program. A makefile would be handy too given that we are working now with a handful of source files and typing in the commands to compile and link them is a bit of a chore. Just presenting code fragments without a larger context is a recipe for confusion.

So that is my self appointed challenge. Stop chasing around in the chapter for all the code that fits together to make something resembling a working program, and go to the online files and see if we can cobble something together.

First the simple example

Here we have two source files, Main.hs and SimpleJSON.hs and things are a little tricky because compiling a Haskell source files yields both a .o and a .hi file. We cannot compile Main.hs until we have compiled SimpleJSON.hs because to compile Main.hs requires the SimpleJSON.hi file.
# Makefile to build something from the Chapter 5 source files.

OBJS = Main.o SimpleJSON.o

simple:	$(OBJS)
	ghc -o simple $(OBJS)

Main.o:	Main.hs SimpleJSON.hi
	ghc -c Main.hs

SimpleJSON.hi: SimpleJSON.hs
	ghc -c SimpleJSON.hs

SimpleJSON.o: SimpleJSON.hs
	ghc -c SimpleJSON.hs

clean:
	rm -f *.o *.hi simple
Note that the business of the .hi files makes setting up a line like the following problematic, if not impossible.
.hs.o:
	ghc -c $<
We also need to slightly modify Main.hs since it must export the "main" entry point these days. The modified main looks like:
module Main (
    main
    ) where

import SimpleJSON

main = print (JObject [("foo", JNumber 1), ("bar", JBool False)])
This compiles and runs, producing:
 ./simple
JObject [("foo",JNumber 1.0),("bar",JBool False)]
OK, bravo for a small victory. A heads up is in order though. Using make for Haskell is out of vogue for some inexplicable reason in the current Haskell culture. My claim is that programmers these days are lazy imbeciles and don't understand how to use make, but that is just my opinion.

And another example, still basic

This is the code from page 116 in the book, namely PutJSON.hs There is nothing here that is not straightforward, so I won't say much. If you download my files, I call this the "Basic" example. The output from this looks like the following:
./basic
{"query": "awkward squad haskell", "estimatedCount": 3920.0, "moreResults": true, "results": [{"title": "Simon Peyton Jones: papers", "snippet": "Tackling the awkward ...", "url": "http://.../marktoberdorf/"}]}

And now the final example

There is quite a clutter of files to try to make sense of if you want to continue on with the book from page 118 or 120 with what the call "Pretty Printing a String".

The first thing I do is to merge SimpleResult.hs into Main.hs. This generates a somewhat complex chunk of JSON to use as a test case. I merge this into Main.hs and then change the definition of main to be the following:

module Main (
    main
    ) where

import SimpleJSON
import PrettyJSON
import Prettify

-- from SimpleResult.hs
result :: JValue
result = JObject [
  ("query", JString "awkward squad haskell"),
  ("estimatedCount", JNumber 3920),
  ("moreResults", JBool True),
  ("results", JArray [
     JObject [
      ("title", JString "Simon Peyton Jones: papers"),
      ("snippet", JString "Tackling the awkward ..."),
      ("url", JString "http://.../marktoberdorf/")
     ]])
  ]

-- main = print $ compact $ renderJValue result
main = print $ pretty 40 $ renderJValue result
Also the file SimpleJSON.hs contains a bunch of accessor functions that are just dead wood and never used. they can be deleted, yielding just this:
module SimpleJSON
    (
      JValue(..)
    ) where

data JValue = JString String
            | JNumber Double
            | JBool Bool
            | JNull
            | JObject [(String, JValue)]
            | JArray [JValue]
              deriving (Eq, Ord, Show)
Simple is always good when you are trying to learn something new. Dead wood is just a source of needless confusion.

The file Concat.hs is unnecessary and another source of confusion. It defines a concat function in terms of foldr. At any event, the code never uses concat, the book simply uses it as a stepping stone to motivate the definitions of hcat, fold, and fsep. This file is just more useless dead wood, and can be ignored and/or deleted.

The following is the Makefile I use to compile the pruned down set of files.

# Makefile to build something from the Chapter 5 source files.

OBJS = Main.o SimpleJSON.o PrettyJSON.o Prettify.o

pretty:	$(OBJS)
	ghc -o pretty $(OBJS)

Main.o:	Main.hs SimpleJSON.hi PrettyJSON.hi Prettify.hi
	ghc -c Main.hs

# --
SimpleJSON.hi: SimpleJSON.hs
	ghc -c SimpleJSON.hs

SimpleJSON.o: SimpleJSON.hs
	ghc -c SimpleJSON.hs

# --
PrettyJSON.hi: PrettyJSON.hs Prettify.hi SimpleJSON.hi
	ghc -c PrettyJSON.hs

PrettyJSON.o: PrettyJSON.hs Prettify.hi SimpleJSON.hi
	ghc -c PrettyJSON.hs

# --
Prettify.hi: Prettify.hs
	ghc -c Prettify.hs

Prettify.o: Prettify.hs
	ghc -c Prettify.hs

clean:
	rm -f *.o *.hi pretty
Using the above makefile, the build goes like so:
make
ghc -c SimpleJSON.hs
ghc -c Prettify.hs
ghc -c PrettyJSON.hs
ghc -c Main.hs
ghc -o pretty Main.o SimpleJSON.o PrettyJSON.o Prettify.o
Running this yields the following:
./pretty
"{\"query\": \"awkward squad haskell\",\n\"estimatedCount\": 3920.0,\n\"moreResults\": true,\n\"results\": [{\"title\": \"Simon Peyton Jones: papers\",\n\"snippet\": \"Tackling the awkward ...\",\n\"url\": \"http:\\/\\/...\\/marktoberdorf\\/\" }\n] }"

Bravo! Now with a complete working program and lots of confusing dead wood eliminated, we are in a position to study working code and perhaps benefit from chapter 5. More cleanup can be done first. There are all kinds of "snippet" comments that may have served some purpose for the writers, but are just visual noise for the reader. Also it is worth noting that several functions are stubs and are never used (nest and fill). More dead wood that should just be deleted.

Another pass through the chapter

With actual working code in hand, I find it much easier to make sense of the book. The following are some key points that I found illuminating.

On page 112, the JValue data type is introduced. You might ask, why a custom datatype. It only makes sense, but Haskell makes it essential since the language only allows arrays of a homogenous type. So we wrap strings, bools, and whatever with type constructors so they are all JValues of different sorts, and this makes Haskell happy with dealing with them as arrays. Ignore all the accessor functions like getBool, they never get used after being defined.


Feedback? Questions? Drop me a line!

Tom's Computer Info / tom@mmto.org