Sunday, 31 August 2008
nzb almost working again
Thursday, 21 August 2008
nzb broken
Sunday, 10 August 2008
Haskell refactoring
I was doing a "routine patrol" of my Haskell code and found a function with a TODO note screaming for refactoring. When writing it, I must have thought it was too verbose expression, but didn't know how to do better at the time. Richer in Haskell experience I decided to give it a try.
getApiKeyFromNozbe :: String -> String -> IO (Maybe String)
getApiKeyFromNozbe email password = do
response <- requestApiKey email password
case response of
Just rsp -> case jRead rsp of --TODO shorten
[("key", Nothing)] -> return Nothing
[("key", Just apiKey)] -> return $ Just apiKey
_ -> return Nothing
The function returns an api key or nothing, boxed in IO monad. The code inside the function uses requestApiKey to get the response from nozbe.com, which happens to be a JSON description of a map that contains one entry. This entry holds api-key in the value or null if the credentials provided were not correct.
requestApiKey :: String -> String -> IO (Maybe String)
First step is to remove the first case expression, instead of it I use >>= operator, which is defined in Monad typeclass (Maybe is a Monad). That saves me from specifying what the value should be in case the response is Nothing.
getApiKeyFromNozbe email password = do
response <- requestApiKey email password
return $ response >>= \txt -> case jRead txt of
[("key", Nothing)] -> Nothing
[("key", Just apiKey)] -> Just apiKey
So far so good, one less line. Next I don't like that return at the beginning of the third line, let's map it.
getApiKeyFromNozbe email password =
(flip fmap) (requestApiKey email password) $ \rsp ->
rsp >>= \txt -> case jRead txt of
[("key", Nothing)] -> Nothing
[("key", Just apiKey)] -> Just apiKey
No fewer lines, but no return now! response renamed to rsp. Next I don't like that rsp appears twice. Partial operator application to the rescue.
getApiKeyFromNozbe email password =
(flip fmap) (requestApiKey email password) $ (>>=
\txt -> case jRead txt of
[("key", Nothing)] -> Nothing
[("key", Just apiKey)] -> Just apiKey)
Let's add a cherry on the top by getting rid of the remaining case expression. It is obvious that it could return the value of the entry without checking what it is.
getApiKeyFromNozbe email password =
(flip fmap) (requestApiKey email password) $ (>>=
\txt -> let [("key", maybeKey)] = jRead txt in maybeKey)
From 7 to 3 lines, not bad. I wonder how quickly will I understand that code next time I see it :).
Bonus
This is another possible step at minimizing the code. This one however goes to far I think (I could have trouble understanding it next time), so I post it only as a bonus. I would consider leaving it, if something like dot2 was in standard library, but I couldn't find anything.
getApiKeyFromNozbe = fmap extractKey `dot2` requestApiKey
where extractKey = (>>= \t -> let [("key", k)] = jRead t in k)
dot2 f g = \x y -> f (g x y)
Saturday, 2 August 2008
introducing nzb
The Haskell app I am developing is a command line interface to nozbe.com. Nozbe.com is a web application implementing Getting Things Done system. For people who are not familiar with GTD, it is a popular and complete system of organizing things to be done. My app's name is nzb.
At the moment nzb has less then 500 lines of code, including tests. I have some unit tests, but they probably give only about 20% coverage. On the other hand I have many functional tests. I do think that functional tests are more important than unit tests.
The functional tests run against the real web application, and therefore are pretty unreliable; one in three runs of the suite something will fail. Moreover they require an account set up, so anybody wanting to run FTs needs to create such an account and set it up with data. Everything is in the build guide, should anyone want to do this.
Currently it lives in code.google.com/p/nzb-cli. Someday I might move it to github, which is praised by many.
Saturday, 3 May 2008
combining functions
contexts <- getContexts api_key - case find (\x -> contextName x == ctx) contexts of + case find ((==ctx) . contextName) contexts of Just context -> do let ctx_id = contextID context createAction api_key prj_id ctx_id name
It is better to combine existing functions than define a new one. It maybe harder to read to people new to Haskell, but I still like it better.
Tuesday, 15 April 2008
$ operator
This is hopefully something worth sharing about Haskell. The $ operator.
simpleHTTP $ buildRequest req_text simpleHTTP ( buildRequest req_text )
It is an application operator, it takes a function and an argument, and ... applies the function to the argument. It's purpose is to save typing parentheses. It is all about operator precedence.
head . words $ config_file_contents ( head . words ) config_file_contents
Application, f a (f applies to a), binds stronger than any operator. If it was an operator, think about multiplication operator which people often omit, it would have precedence set to 10. $ has precedence set to 0, which is the lowest value of precedence possible.
The . is my another favourite. (f . g) a == f (g a) it set to 9, and therefore binds almost as strong as application.
listActions :: String -> [Action] listActions = filter notDone . map actionFromMap . parseJSON
Thursday, 3 April 2008
Learning Haskell
It will be learning by doing. Learning Haskell, a language in which stream of Fibonacci numbers can be defined as:
fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
I have chosen Haskell because of compactness and composability.
