Four days ago I uploaded omnicodec to Hackage. It’s a collection of two simple command-line utilities for encoding and decoding data built on the dataenc package. I’m planning on building a Debian package for it as soon as I find the time.
Recently I’ve had reason to write a few tools for Windows. Nothing complicated, and since there was nothing “mission critical” (there hardly ever is for me ) I decided to try to write them in Haskell rather than Python or C/C++. This post contains some sort of recollection of my experience so far.
First off I should probably disclose that I don’t particularly like Windows. Besides feeling more at home on a Unix (Linux) system I find that Windows also upsets me on a very basic level. If I spend a whole day at work in Windows I tend to go home more stressed and irritated than after a day in Linux. Part of this is probably due to my familiarity with Linux. My hope was that the combination of the immense joy of programming Haskell and my loath of the Windows platform I would end up with something that at least was bearable. It turns out I did.
My setup
Besides installing the pre-built GHC I also installed Visual Studio Express more later to explain the need for it. I don’t like the Visual Studio environment so I installed CMake and jEditi. Last but not least I installed Console, it addresses the fact that Windows’ “DOS-box” is an amazing piece of crap.
FFI on Windows
First, GHC comes with a number of Windows-specific modules and they cover an impressive number of the Win32 API functions. The Win32 API is simply huge and one will sooner or later come find the need to add to what those modules offer. Second, GHC is able to compile C code by delegation to MinGW, however MinGW doesn’t completely cover the Win32 API either and if one is unlucky MinGW isn’t enough for the task at hand. Of course that’s exactly what happened to me. So, I installed Visual Studio Express, but since I don’t like VS that much and I didn’t want VS solutions in my darcs repository I decided to use CMake to build a library that I then instruct GHC, through a Cabal file, to link with my Haskell code. The end result is that building requires a few extra steps before the familiar configure/build sequence. It works remarkably well really, and I’ll definately use that strategy again if I need to.
There is a benign warning thrown when linking the final executable though:
It seems that it might be possible to skip the extra steps in the future if Cabal ticket #229 is addressed.
Windows System modules
Admittedly I haven’t been using much of the functionality in these modules, but it happens that the only function I needed had a bug that results in a segmentation fault, sometimes. See GHC ticket #2097 for some details. I guess this confirms my impression of most Haskell hackers being Linux/BSD/Unix users.
Conclusions
I won’t drop Haskell on Linux anytime soon, but I’ve found Haskell on Windows to be possible. As I suspected when I started out, Haskell does make Windows somewhat easier to bear.
ordinarily I would install Vim but I thought I’d try out another editor. Vim on Windows is somewhat “leaky”, i.e. it sometimes shows behaviour that betrays its Unix roots.[back]
I have to say rotonyms seem kind of pointless, but since there is a post with a Python solution I thought I’d make an attempt at a solution in Haskell. Of course I didn’t manage on my own, I received some much needed help from Cale and dmwit on #haskell.
First some rotation functions, as far as I know there’s no built-in rot13 encoding in Haskell:
rot13Char c = chr ( (ord c - ord 'a' + 13) `mod` (ord 'z' - ord 'a' + 1) + ord 'a')
rot13 = map rot13Char
In order to ease working with rotonyms I introduced a type to represent one. This turned out to pay off later on.
data Rotonym = R String
It would be easy to let the compiler derive Show, but I wanted to mimick the output produced by Will’s solution in Python:
instance Show Rotonym where
show (R w) =
let r = rot13 w
in (min w r) ++ "\t" ++ (max w r)
Now I’m ready for the meat of the matter, reading the word list, pick out the rotonyms and finally print them. Following Will’s cue I represent the word list as a set of strings (importing Data.Set as S), this also eliminates duplicates.
main = do
words <- liftM lines $ readFile "WORD.LST"
let wordset = S.fromList words
isRotonym w = S.member (rot13 w) wordset
rots = S.fromList [R w | w <- words, isRotonym w]
mapM_ (putStrLn . show) (S.elems rots)
Sticking Rotonym instances in a set requires them to be instances of Ord and Eq:
instance Eq Rotonym where
(R w1) == (R w2) =
let r1 = rot13 w1
r2 = rot13 w2
in (w1 == w2 && r1 == r2) || (w1 == r2 && w2 == r1)
instance Ord Rotonym where
compare (R w1) (R w2) =
let r1 = rot13 w1
r2 = rot13 w2
in compare (length w1, (min w1 r1)) (length w2, (min w2 r2))
dmwit pointed out that what is actually going on here is taking an intersection of the the words and their rotated counterparts. This means the main could be written
main = readFile "WORD.LST" >>=
mapM_ (putStrLn . show) . S.toList . S.map R . findRotonyms . S.fromList . lines
where findRotonyms ws = ws `S.intersection` (S.map rot13 ws)
Another idea that dmwit came up with was to introduce a class for rot13:
class Rot13 a where
rot13 :: a -> a
Char is an instance of that class with the expected implementation of rot13:
instance Rot13 Char where
rot13 c = chr ((ord c - ord 'a' + 13) `mod` (ord 'z' - ord 'a' + 1) + ord 'a')
If a is an instance of Rot13 then list of a is as well, again with the expected implementation:
instance (Rot13 a) => Rot13 [a] where
rot13 = map rot13
It is possible to intersect lists (Data.List.intersect) but it turned out to be painfully slow, so back to sets again. A set of a is an instance of Rot13:
instance (Ord a, Rot13 a) => Rot13 (S.Set a) where
rot13 = S.map rot13
Finding all rotonyms is then straight forward:
main = readFile "WORD.LST" >>=
mapM_ (putStrLn . show) . S.toList . S.map R . findRotonyms . S.fromList . lines
where findRotonyms ws = ws `S.intersection` (rot13 ws)
I just began listening to episode 95 of pauldotcom and was glad to hear that they enjoyed my email. Here’s the complete email I sent them:
Well, something must have changed since I last communicated with you
(see http://therning.org/magnus/archives/257). I’m not sure what
though. I heard you when you were on the Linux Reality audio cast and
thought I’d check you out again, just to see what you were up to. Well,
episode 92 (both parts) was bloody brilliant, episode 93 was good too,
and now I’m halfway into episode 94. I have no recollection of the
earlier episodes being this organised and good. At some point when I
wasn’t listening you must have learnt to rock!
I enjoy the tech segment. The amount of banter is down and the episodes
move along a lot more than I remember. No offence to Twitchy, but I’m
not sad he isn’t as involved any more, you know, Kramer is brilliant but
Seinfeld just wouldn’t be a good show if he were in each and every
scene. Twitchy has more of a “celebrity guest” personality… The only
criticism I have, and this is pushing it I know; given my walk to work
I’d prefer each episode to be around 60 minutes, rather than 80-90
Keep it up!
/M
PS I’m planning on posting this email on my blog. I’ll put any reply
from you on there as well.
Reading my email on the show sure beats any reply they could have sent by email At some point I have to go back and check out the other podcast I stopped listening to…
Today I had a long and interesting discussion about what is “more free”, GPL or the BSD license. I did a little searching on the internet afterwards and found the following posts quite illuminating: