Silence of the λs

Kal Me Maybe

17 Aug 2013

A post just went up on HackerNews recently announcing a new JavaScript-transpiled language named Kal. In the author's own words, the main design goals of Kal are:

  1. Eliminate the yucky parts of JavaScript, but keep the good stuff including the compatibility, and the great server and client runtime support.
  2. Make code as readable as possible and make writing code straightforward. Eliminate the urge (and the need) to be terse and complicated.
  3. Provide an alternative to callbacks (which look weird) and promises (which are weird) while providing excellent, easy-to-use asynchronous support.

Looking over Kal, I thought it was pretty cool. The use-case of making asynchronous control flow easier to reason about sounds great, even if I don't do that much day-to-day async JavaScript programming. As I was looking at the following example in particular...

task getUserFriends(userName)
  wait for user from db.users.findOne {name:userName}
  wait for friends from db.friends.find {userId:user.id}
  if user.type is 'power user'
    for parallel friend in friends
      wait for friendsOfFriend from db.friends.find friend
      for newFriend in friendsOfFriend
        friends.push newFriend unless newFriend in friends
  return friends

...it struck me how much this looked like monadic code. I spent a few minutes throwing together a pseudo-ish Haskell implementation to see how similar they looked, and posted a comment to HN containing the following code:

getUserFriends userName = do
  user <- findUser userName
  friends <- findFriends user
  if (User.type user) == "power user"
    then friends ++ parMap rdeepseq $ (getSecondNodes friends) friends
    else friends
  
getSecondNodes firstNodes friend = do
  secondNodes <- findFriends friend
  diff firstNodes secondNodes

At first, I was satisfied with the conclusion that Kal offers monad-like structures as baked-in language features, but that this sort of control-flow is just as easy to accomplish in a sufficiently powerful language. However, as Sean Bean knows...

Aragorn cautioning: 'One does not simply write a correct Haskell program

In truth, this quick Haskell program doesn't even compile. So, I sat down to actually write a compilable implementation. Swagging the data-gathering functions like so...

data User = User { classification :: String } deriving (Eq,Show)

findUser :: String -> Maybe User
findUser userName = Nothing

findFriends :: User -> Maybe [User]
findFriends user = Nothing

...I ended up with the following real implementation.

getUserFriends :: String -> Maybe [User]
getUserFriends userName = do
  user <- findUser userName
  friends <- findFriends user
  if (classification user) == "power user"
    then Just (nub $ friends ++ concat (parMap rseq (getSecondNodes friends) friends))
    else Just friends

getSecondNodes :: [User] -> User -> [User]
getSecondNodes firstNodes friend = fromMaybe [] $ getSecondNodes' firstNodes friend

getSecondNodes' :: [User] -> User -> Maybe [User]
getSecondNodes' firstNodes friend = do
  secondNodes <- findFriends friend
  Just $ firstNodes \\ secondNodes

Woof. That's a little worse, isn't it? This version is also sub-optimal compared to the Kal version, since there is an additional step ensuring uniqueness at the very end of combining the second-hop nodes. I'd also argue that it's much less clear what's happening. Admittedly I am hardly an experienced Haskeller, so there is probably a cleaner version; if so, I'd love to see it! Check out the full source on GitHub.

Although my initial reaction to Kal may have been "I can just do this with monads", maybe there is some value in dedicating specific syntax to this problem. Besides, as we all know...

Biggie Smalls, stating: Monads mo' problems

Preach it Biggie.