Another Blog

Mostly about computers, generally Linux-related

A Different Kind of Obscurity

Learning Haskell is probably the hardest thing I’ve tried regarding computers. It requires giving up all previous conceptions about programming and adopting a different, more pragmatic and heavily mathematical approach. I’m currently struggling with basic monads and haven’t even touched on monad transformers, arrows, and who knows what more Haskell has in store for me.

You may be wondering why I find Haskell interesting, despite its initial difficulty to understand (and despite a serious lack of time on my part). The main reason is that, after I have written or otherwise fully understood a code snippet, I frequently find myself seemlessly pausing to admire its elegance and simplicity. I value simplicity above all, and clearly distinguish it from easiness. Haskell is simple in that it is governed by simple and few rules, but it is by no means easy to learn or master. I often find myself asking silly, if not outright stupid, questions on #haskell or #xmonad (both are hosted on the Freenode network). The people there are, however, kind enough not to complain about it.

The `obscurity’ in the tile comes from the difference in Haskell’s syntax, compared to what I have used so far (and continue to use and study). C, C#, Java, Python. I’ve been hyped up about all of these at one time so far, and I still consider C and Python to be useful and not broken. Haskell, however, is different in both philosophy and, inherently, syntax. Programs end up being unusually terse, but pack a lot of meaning in every line of code. I estimate that I can understand about 10% of all the Haskell code, based on what I have seen, but that 10% (maybe even less) I understand into such depths that I can easily explain them to another person, if they have the appropriate prior knowledge. Or at least so I hope.

As to writing programs myself, I’ve stuck to simple ones, emphasizing on understanding the various language features. It’s quite common that a program that I would normally write in an hour in C takes up to one or two days in Haskell (allowing time for daily routine and documentation), but ends up being a lot shorter, clearer and more meaningful than the C version. As such, the obscurity I have mentioned is beneficial — people communicate more efficiently when their language is more precisely defined and they think more efficiently when they needn’t consider underlying details (the last part is valid for all high-level programming languages). Which is not to say Haskell won’t let you touch those details, though.

I also have a code snippet to show off; it is probably inefficient and could easily have been written more elegantly by one with more experience, but I am happy that I made it work. Any suggestions or commentary will be very appreciated. But before the code, the problem (it is taken from my Algorithm Analysis course):

Given an undirected graph G = (V, E) and an integer k \le card(V), find all the complete subgraphs of G that have exactly k elements.

The problem was proved to be NP-Complete, and we solved it using C during a class. Now, the interesting part in implementing it in Haskell was using the Writer monad, something I had never done before. The representation of the graph is by all means faulty (vertices are numbered 1 through nrNodes, there may be pairs containing numbers outside that range, etc.), but the algorithm works for correct input, which is clearly the key element. The terseness and expressiveness of the code still amaze me, although I must reiterate: it’s probably faulty and ugly in the eyes of an expert. The lack of I/O is compensated by excellent interactive support (I use ghci). So, without further ado, the code, in all its (perceived) glory:

import Control.Monad.Writer

data Graph = Graph { nrNodes :: Int, edges :: [(Int, Int)] } deriving Show

isConnected :: Graph -> Int -> Int -> Bool
isConnected g a b = or [(a,b) `elem` (edges g), (b,a) `elem` (edges g), a == b]

isComplete :: Graph -> [Int] -> Bool
isComplete g vs = let
    isConnectedToAll x = all (isConnected g x) vs
  in and (map isConnectedToAll vs)

generateCompleteSubgraphs :: Graph -> Int -> [[Int]]
generateCompleteSubgraphs g k = map reverse $ execWriter $ f 1 []
    f n set
      | n == k+1  = if isComplete g set then tell [set] else return ()
      | otherwise = mapM_ (\x -> f (n+1) (x:set)) (filter (not . (`elem` set)) validChoices)
        where validChoices = if n == 1 then [1..(nrNodes g)] else
                if head set == nrNodes g then [] else [1 + (head set) .. (nrNodes g)]

PS: If anyone knows of a better way to include Haskell source code in WordPress, please let me know.


December 14, 2007 Posted by | functional programming, haskell, programming | , | 3 Comments