New Erlang Book

Paul Brown @ 2007-03-04T01:11:00Z

With the default Erlang book now over a decade old, a new one was sorely needed, and it looks like Joe Armstrong has stepped up (again). (Hat-tip to Bill de Hóra for the pointer to the book, since I haven't been tracking the Erlang space lately.) It's not in the beta PDF yet, but I'm looking forward to what is currently slated for Chapter 19:

How to structure applications for programming multi-core CPUs. Increasing parallelism. Deciding the granularity of concurrency.

Good stuff.

(comment bubbles) 0 comments

Valley, Schmalley: Be Close to Your Customer

Paul Brown @ 2007-02-28T17:19:55Z

I ran across Alan Warms's response to a question from Ross Mayfield to Dick Costolo:

How does an Internet entrepreneur overcome not being in the Silicon Valley?

(Dick, by the way, has been publishing lots of good stuff on his Ask the Wizard blog.) The answer is: it depends on how far you are from your customer.

Before moving West to wetter pastures, I lived in Chicago for 8 years, five of which I spent as an entrepreneur, and Chicago is a great place to build a business. My experience matches Alan's:

  1. You can make a late morning meeting anywhere in the country — other than the East Coast — without staying overnight. For the East Coast (e.g., NY, Boston, Atlanta), early afternoon works better, and you can still be home for a late dinner. Chicago is a hub for United and Southwest, and commuting to major cities in the Midwest (like Nashville, St. Louis, Minneapolis, Detroit, and Milwaukee) is as convenient as commuting to the Chicago suburbs. (Unfortunately, that doesn't say all that much...)
  2. There are plenty of smart young people around. Within Chicago or close-in suburbs, there are: University of Chicago, Northwestern, and University of Illinois Chicago. Within a reasonable distance, there are University of Illinois Urbana-Champaign, Purdue, University of Michigan,...
  3. There are plenty of experienced industry practitioners in the area, thanks to the shippers/haulers, banks, financial services companies, pharmaceutical companies, healthcare companies, insurance companies, manufacturers, heavy industries, and other companies in the immediate area. The first few that come to mind are Motorola, State Farm, Citadel, Allstate, Baxter Healthcare, Caterpillar, UBS, McDonalds, BankOne, Wrigley, and Boeing. If you cast a slightly wider net, say a ninety-minute plane flight, you pick up Harley Davidson, GM, Ford, Chrysler, Schneider National, Anheuser-Busch, Smurfit-Stone, AG Edwards, and Northwestern Mutual.
  4. Corporate necessities are affordable. For example, we rented industrial loft space for $10 per square foot (per year). During the Bubble.

If you're looking to raise venture capital, there are some top-shelf people in Chicago. (Based on personal experience, I have good things to say about Apex, JK&B, and OCA.) Valley VCs may be reluctant to sign up for years of plane flights for Board meetings when they have plenty of action close to home, and I can't really blame them for that.

For the first four years of its life, FiveSight sold traditionally-licensed middleware to end-users (like those companies that I listed above), and all of the advantages that I've just enumerated worked in our favor. As our model shifted to one where our customers, partners, and potential acquirers were software companies, not being in the Valley left us at a disadvantage competing with companies that had a presence there. Flying out to get customer face-time became both a necessity and a distraction for the core team both because of the travel time and because of the temporary loss of the close communication and collaboration that made the team work best. I can recall one acquisition opportunity where our competitor could literally walk to the offices of the acquirer. Even in the most objective head-to-head competition, the local candidate is going to be able to more effectively gather and apply information to influence the deal. Also, being unknown is a disadvantage. The environment in the Valley doesn't actively exclude outsiders, but it tends to include insiders via "X worked with Y at company Z funded by W" relationships.

(This reminds me that I need to write an entry about when to go "all in".)

Open source, blogging, and professional/social use of the Internet are helping information to travel faster and farther than it did previously, but these things only make a difference when they bring you into closer and better communication with your customer.

(comment bubbles) 2 comments

Really Simple Atom Syndication

Paul Brown @ 2007-02-27T04:06:00Z

Herein post 4 of n on my hobby project to rewrite my personal publishing software in Haskell. Herein, I create a economically-driven approach to Atom syndication format for entries and comments. By citing economics as the prime motivator, I mean that I'm aiming neither for the most complete nor the most elegant implementation except where those two overlap with exigency.

Required Reading

To make sure that I knew enough about Atom, I read through the Atom Syndication Format RFC (I prefer the plain text to the pretty version), the introduction at AtomEnabled.org, and Mark Pilgrim's note on How to make a good ID in Atom. After reading so many specifications that either use flabby XML Schema (like WSDL) or ad hoc XML (like RSS 2.0), the use of RELAX NG compact syntax in the RFC was a breath of fresh air.

Really Simple Atom Data Model

The first set of design decisions I made were what to throw out from Atom. I decided to omit the atom:contributor construct entirely as well as atom:author is sufficient for my purposes, and atom:source and attributes on atom:link other than @rel and @href weren't going to be any use to me, either. I decided to omit the @scheme and @label attributes on atom:category since all of my @term values will be human readable and don't plan on using any scheme other than my own. I decided to omit any specific constraints on components that might otherwise be URIs, RFC3339-formatted date/time, or other — String will do for now, and I'll make sure that properly formatted data (including escaping as necessary) is used in the first place. I also decided to leave any of the sequencing and quantity constraints out of the model, as this will be an internal model only.

Here's the way it looks, and if you squint at it just right, it doesn't look that different from the RELAX NG compact schema:

data AtomElement = Feed [AtomElement]
                 | Entry [AtomElement]
                 | Content AtomContent
                 | Author { author_name :: String,
                            author_uri :: Maybe String,
                            author_email :: Maybe String }
                 | Category String 
                 | Generator { gen_name :: String,
                               gen_uri :: String,
                               gen_version :: String }
                 | Id String
                 | Icon String
                 | Link { rel :: String,
                          href :: String }
                 | Logo String
                 | Published String
                 | Rights AtomContent
                 | Subtitle AtomContent
                 | Summary AtomContent
                 | Title AtomContent
                 | Updated String
                   deriving (Show)

The Maybe String for the author_uri and author_email components of the Author representation are intended to allow for comments where the author may omit an email address or link. (Of course, I may just omit their comment under those circumstances...) Next, one more type for AtomContent, where I elected to eliminate the possibility of HTML content:

data ContentType = XHTML | TEXT
                 deriving (Eq,Show,Enum)

data AtomContent = AtomContent { contentType :: ContentType,
                                 body :: String }
                 deriving (Show)

XML Output

With a few (non-limiting) assumptions, getting XML out is simple. First up, the Atom URI, my choice to bind it to the atom prefix and my assumption that XHTML will always in the default namespace:

_prefix :: String
_prefix = "atom"

_uri :: String
_uri = "http://www.w3.org/2005/Atom"

start_div :: String
start_div = "<div xmlns=\"http://www.w3.org/1999/xhtml\">"

end_div :: String
end_div =  "</div>"

It's worth noting that the XHTML specification (via either the transitional DTD or the strict DTD) requires that the XHTML namespace be the default namespace, but there is no requirement that an XHTML fragment in an Atom feed use the default namespace.

Next, some really simple functions to wrap content in elements:

-- Format a clopen element with a list of attributes.
clopen :: String -> [(String,String)] -> String
clopen s [] = "<" ++ (prefix s) ++ "/>"
clopen s xs = "<" ++ (prefix s) ++ (nv_to_s "" xs) ++ "/>"

-- Wrap a string in an element.
wrap :: String -> String -> String
wrap s t = "<" ++ (prefix s) ++ ">" ++ t ++ "</" ++ (prefix s) ++ ">"

-- If a value is present (i.e., not Nothing), wrap it in an element.
wrap_m :: String -> Maybe String -> String
wrap_m _ Nothing = ""
wrap_m s (Just t) = wrap s t

-- Wrap an element with attributes around a string.
wrap_ :: String -> [(String,String)] -> String -> String
wrap_ s [] t = wrap s t
wrap_ s xs t = "<" ++ (prefix s) ++ (nv_to_s "" xs) ++ ('>':t)
               ++ "</" ++ (prefix s) ++ ">"

wrap_ns :: String -> String -> String
wrap_ns s t = wrap_ s [(_prefix,_uri)] t

-- Format a list of name-value pairs as attributes.
nv_to_s :: String -> [(String,String)] -> String
nv_to_s = foldl att

att :: String -> (String,String) -> String
att s (n,v) = s ++ (' ':(n ++ "=\"" ++ v ++ "\""))

And then just map the various shades of AtomElement onto the functions:

toXml :: AtomElement -> String
toXml (Feed xs) = wrap_ns "feed" (content_ xs)
toXml (Entry xs) = wrap_ns "entry" (content_ xs)

toXml' :: AtomElement -> String
toXml' (Entry xs) = wrap "entry" (content_ xs)
toXml' (Category s) = clopen "category" [("term",s)]
toXml' (Id s) = wrap "id" s
toXml' (Icon s) = wrap "icon" s
toXml' (Link r h) = clopen "link" [("rel",r),("href",h)]
toXml' (Logo s) = wrap "logo" s
toXml' (Published s) = wrap "published" s
toXml' (Updated s) = wrap "updated" s
toXml' (Author s u e) = wrap "author" ((wrap "name" s)
                                       ++ (wrap_m "uri" u)
                                       ++ (wrap_m "email" e))
toXml' (Generator n u v) = wrap_ "generator" [("uri",u),("version",v)] n
toXml' (Content a) = atom_text "content" a
toXml' (Rights a) = atom_text "rights" a
toXml' (Subtitle a) = atom_text "subtitle" a
toXml' (Summary a) = atom_text "summary" a
toXml' (Title a) = atom_text "title" a

content_ :: [AtomElement] -> String
content_ = concat.(map toXml')

-- Render an Atom text construct as XML.
atom_text :: String -> AtomContent -> String
atom_text s (AtomContent XHTML t) = wrap_ s [("type","xhtml")] (start_div ++ t ++ end_div)
atom_text s (AtomContent TEXT t) = wrap s t

(The Atom spec allows @type="text" to be omitted.) The toXml function and the AtomElement, AtomContent, and ContentType types are all that would be exported from the module.

A quick check with ghci shows that this does the right thing:

[...]
*Text.Atom> let entry = Entry [Title (AtomContent TEXT "Atom-Powered Robots Run Amok"), Id "urn:uuid:foo", Updated "2003-12-12T18:30Z", Author John Doe" Nothing Nothing, Content (AtomContent XHTML "

Some text.

")] *Text.Atom> toXml entry "<atom:entry atom=\"http://www.w3.org/2005/Atom\"><atom:title type=\"text\">Atom-Powered Robots Run Amok</atom:title><atom:id>urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a</atom:id><atom:updated>2003-12-12T18:30Z</atom:updated><atom:author><atom:name>John Doe</atom:name></atom:author><atom:content type=\"xhtml\"><div xmlns=\"http://www.w3.org/1999/xhtml\"><p>Some text.</p></div></atom:content></atom:entry>"

The let entry=... line makes more sense with some whitespace thrown in:

let entry = Entry [
  Title (AtomContent TEXT "Atom-Powered Robots Run Amok"),
  Id "urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a",
  Updated "2003-12-12T18:30Z",
  Author "John Doe" Nothing Nothing,
  Content (AtomContent XHTML "<p>Some text.</p>")
]

Other Available XML Wheels

While the above is a small wheel, it is a wheel nonetheless, and I looked at three Haskell XML libraries before reinventing it:

  • Haskell XML Toolbox, a.k.a., HXT, (link) appears to be under active development and supports my basic requirements of XML output and namespace support. The API looks agreeable, and there is RSS aggregator in 50 lines as an example. If I choose to implement the Atom Publishing Protocol, HXT is the way I'll go to get atom:entry turned into the right kind of internal structure.
  • HaXml (link) appears to lack namespace support, so I dismissed it without looking deeply at it.
  • HXML (link) lacks namespace support, so I dismissed it without looking deeply at it. That said, the validation concept in HXML has the same heritage as the one used in the RELAX NG validator Jing.

What's Left?

There's enough real work left for at least three more blog entries: storage/state management for entries (probably STM with persistence via the filesystem), a commenting facility, human-facing content display and navigation (probably Haskell via the Text.XHtml package), and making sure that the FCGI wrapper works well multi-threaded. (I want a multi-threaded FCGI handler so that STM can serve as the concurrency control for the application; otherwise, the persistence layer will need to provide that functionality.)

State's the next one I'll tackle.

(comment bubbles) 2 comments

An Omagawd Omakase

Paul Brown @ 2007-02-26T00:50:00Z

As our monthly attempt to get a good meal, the wife and I went to Nishino for a "deluxe" eight-course omakase on Saturday, and other than the dessert, it did not disappoint. The dessert could be described as a tempura banana split, and while that sounds like it could be good, it wasn't. The rest of the meal was so good that we didn't care about dessert. Most of the courses had a Japanese-French mash-up feel to it with amazing sashimi paired with good sauces (red wine reduction with tuna, foie gras, and shitake mushroom) or simple garnishes (amberjack with jalapeno, ginger salsa, and a garlic chip). The straight-ahead nigiri course was excellent, with literally the best o-toro that I've ever had and some sawara that was even smoother and butterier. (I'm a little unclear on the sawara (spanish mackerel) versus aji (horse mackerel), since our server said both "aji" and "spanish mackerel" — either way, those were really amazing pieces of fish, mercury risk aside.) Speaking as someone who used to regularly travel to Japan, this was great sushi; we're definitely going back.

(comment bubbles) 1 comment

Maven and Rakes in the Grass

Paul Brown @ 2007-02-26T00:00:00Z

When you first experience Maven, it's like the cinematic cliché of two lovers running across a grassy field to embrace: It takes care of getting the right JARs for you, keeps test and build dependencies separate, and generates configuration artifacts for your favorite IDE, too. Even though it's XML, it's still a sweet five-minute user experience, but then you start stepping on the rakes in the grass:

"Hmmm. Doesn't look like Emma is supported by Maven2, so I'll give Cobertura a shot." THWAP! The current version of the plugin is broken, but downgrading to an earlier version appears to work.

"Looks like TestNG support only extends to version 4.7, but maybe my tests written against 5.5 will still work." THWAP! It turns out that up to date support for TestNG is a bit of a can of worms. (I would have tried the 2.8-SNAPSHOT version, but the pom was broken.)

"Oh well. I can switch to JUnit4 and do without data providers, make @BeforeClass methods static, and replace dependency-driven order of execution by some hand-coded magic." THWAP! The documentation doesn't explicitly state that the current version of the Surefire plugin is for JUnit 3.8 only, but it is. Adding the Apache plugin snapshot repository and using the 2.3-SNAPSHOT version of the plugin gets things working. (Of course, then it also turns out that IDEA 6 doesn't support JUnit 4.2.)

This is no worse than dealing with dependencies and working directories in Ant, so I'm happy enough with Maven in the relatively green-field environment that I'm working in. I can see where trying to bend an existing project, especially a large one, to Maven could quickly become a fool's errand.

I have to admit that I looked closely at Maven in its version 1 incarnation, but it flunked the five minute test. (Among other things, I was never able to grok how the "reactor" worked for multi-module builds.) Maven 2, thanks to the Apache site and the book and examples from Mergere, gets over that hump. (FiveSight had a home-grown multi-module build system built on top of Ant, but it didn't do an especially good job of rolling-up reporting or of managing conflicts in transitive dependencies (lib A version C requires lib B version D, but lib E requires lib B version F).)

I do like where Matthieu is headed with raven or Russel Winder is headed with gant, but I'd really like the two together: non-XML syntax, dependency management, fully-stocked and up-to-date repositories, well-supported by continuous integration systems, and works with everything that Ant works with. Maybe gant plus the Maven2 Ant tasks or Ivy is the most silver-ish bullet for the time being, but seeing as I don't need to shoot any werewolves, Maven fits the bill for the moment.

(comment bubbles) 3 comments

Review of "Programming in Haskell"

Paul Brown @ 2007-02-25T05:54:48Z

I pre-ordered a copy of Programming in Haskell by Graham Hutton from Amazon last year, and once it showed up last month, its ~150 pages were a quick and pleasant read.

For an experienced programmer who's happy with dirt under their nails, materials for learning Haskell are available on line: the classic Gentle Introduction to Haskell, the Haskell Wiki, and the Haskell Report (language specification and source code for core libraries). Also, the folks on Haskell Cafe and the Haskell IRC channel are generally receptive to beginner questions that aren't obviously homework...

And this brings me to the first strength of the book: the stated target audience for Programming in Haskell is the "absolute beginner", and the book does a good job playing to that audience. Recursion, higher-order functions, and laziness all appear in the book but relatively late and with a good amount of supporting exposition and some rules of thumb. Formal reasoning about Haskell programs (proof by computation, induction, etc.) appear only in the last chapter, and IO is presented without more general discussion of monads but with adequate discussion of the conflict between side-effects and a purely functional language.

The book's other major strength is in the examples. Each chapter includes compact, uncontrived, and interesting examples, which is a major feat for any textbook. I particularly liked the Caesar cipher and Countdown examples. I can see this being a good textbook for an introductory course, and with lecture slides available from the site for the book and supporting materials available from the publisher, the author has clearly gone out of his way to make it a good choice as a textbook.

As for non-positives, I've already spent time mucking through the online materials about Haskell and on a few other books on the topic (The Haskell Road to Logic, Maths, and Programming, The Haskell School of Expression, and Purely Functional Data Structures), so I didn't take any new knowledge about Haskell away from the book. Nonetheless, I did find a few things to take away from the book, e.g., a technique for compactly generating the powerlist (all sublists) of a list that was part of the Countdown example.

Other than the price seeming a bit much on a per-page basis, the only other thing that bugged me about the book — and only because I used to be a working mathematician — was this statement about the Game of Life on p. 94:

That is, we can think of the board as really being a torus, a three-dimensional doughnut-shaped object.

The game board is indeed a torus, but a torus is a two-dimensional object. One representation of a torus embedded in three-dimensional space would be as the surface of a doughnut, but that representation is not what defines a torus. A torus is a two-dimensional surface obtained as the cartesian product of two circles. An equivalent construction is to create a torus by gluing opposite sides of a square, like so:

Living on a torus with the usual metric is like playing Asteroids; if you fly off the {top,bottom,left,right} of the screen at some angle, you fly on the {bottom,top,right,left} at the same angle. Or, to put it another way, if you were a two-dimensional creature, it would be like being on a plane where there were infinitely many copies of you, placed at points with two integer coordinates. While this is admittedly a bit of a niggle, I emailed Graham, so hopefully this shows up as an erratum.

(comment bubbles) 2 comments

Yup-Yupping for Startupping

Paul Brown @ 2007-02-22T18:10:00Z

Several people have pointed to Startupping as a new resource for entrepreneurs, and I've signed up and subscribed to the feeds. It looks to have plenty of traction (in the form of content, participation, and buzz), and I'll throw in my $0.02 where it makes sense.

(comment bubbles) 2 comments

All Posts contains 397 items in 57 pages of 7 items each:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57