Haskell APIs for Hackers
April 13, 2012TL;DR
Haskell APIs are often unwieldy for hackers so let's get to work. APIs need to compete on elegance, especially for simple use cases like HTTP GET. In Ruby you can get the string representation of a resource located at a URL in 1 line and a complete working program looks like this:
require 'net/http' html = Net::HTTP.get('harold.hotelling.net', '/') puts html
Haskell doesn't seem to have this, so I've created a module that simplifies HTTP GET in Haskell to a 1 liner (documentation here, source code here):
-- This does IO and returns a String get "http://harold.hotelling.net/"
allowing you to write this complete working program which is competitive:
module Main where import EZHTTP main = do html <- get "http://harold.hotelling.net/" putStrLn html
If enough hackers jump in and add this layer of polish then Haskell has a real chance at widespread adoption. There are no major language flaws holding us back, there is just some work to do.
Introduction
After reading a blog post criticizing Haskell I couldn't quite get it out of my head. The author makes two arguments against Haskell. First, that you can't do printf-style debugging. This is just a lack of exposure because the System.IO.Unsafe module lets you add debugging output in the middle of pure functional code. There are no guarantees that your message will be printed and it may be printed more than once, but in my experience it works well as a temporary debugging tool. I've seen several variations of this helper function:
andDebugPrint value message = unsafePerformIO (do printStrLn message ; return value)
which you can use as an operator in Haskell via backticks. The function type remains pure with no IO monad that would require the entire call stack to use the IO monad, but prints out the debugging message. For example:
pureFunction :: Int -> Int -> Int -> Int pureFunction x y z = (f x y z) `andDebugPrint` "this is a debug message"
Second, he argues that the different DSLs for doing common tasks in Haskell have too great a learning curve. This isn't a problem with DSLs instead of APIs. It's a problem of overly complex DSLs versus APIs with some very simple examples that are well documented. The Haskell community is heavily academic and is very good at making hard things possible. But for day-to-day programming it's more important to find a function that does what you want and keep moving. I don't expect Ph.D. candidates to stop their work at the edge of known type systems to focus on "Hello, world" so it is up to those of us who like Haskell and work in other languages to bridge this gap and make easy things easy.
Designing an API
The first task I'm taking on is making it trivial to make HTTP requests. For small projects it doesn't make sense to grab control of every header and decide what to do with redirects by default. I took a look at this sample code for performing an HTTP GET and see too much complexity.
- You need to parse the URL and handle invalid URLs using the Maybe monad before you can make a request.
- You need to handle communications errors using the Either monad.
- You need to cehck the status code from the response using a case statement, where the status code is a 3-tuple of digits instead of an Int.
- There are just too many steps involved.
And if you think that's too much variety in how errors are handled, take a look at this review of 8 common error handling strategies and then go read more detailed coverage. I decided to use exceptions for all 3 error scenarios because you don't need any error handling code when you don't want to catch them. This gives my API a very clean feel and matches the use of exceptions for errors in most other languages.
-- You can just ignore the error case and let your program shut down: do html <- get "http://harold.hotelling.net/" ... -- Or catch the exceptions with Control.Exception.catch: do html <- get "http://harold.hotelling.net/" `E.catch` errorHandler ...
Next up, POST
I've also added a post function for easily making HTTP POST requests.
-- POST a String as text/plain: post url "Some text" -- POST some form inputs with the proper Content-Type: post url [("a", "1"), ("b", "2")]
There's also support for JSON and XML, check out the documentation for details. The nice thing here is that you can add support for POSTing some new data type without touching the code for EZHTTP or the other library. Haskell allows you to say "this data type I like is Postable: here's how" and you're all set.
Wrap up
Coming soon: following redirects (up to some reasonable limit and throwing an exception if there's a loop), which is pretty critical for real use. I also need to write some tests, but it's time for bed.
I think there's a lot of potential for using Haskell to write scripts, or do things from the REPL. Being able to make HTTP requests in 1 line of code from ghci has been fun to build and play with. And I'm excited about the possibilities if more simple commands like these are written to wrap some of the other powerful Haskell modules out there.