Académique Documents
Professionnel Documents
Culture Documents
Sergei Winitzki
February 16, 2014
1 Introduction
I wanted to implement a simple, purely functional algorithm involving a lot of
small memory allocations, to see how different functional languages will handle
that kind of task. By intention, I wanted to keep the programming style as naive
as possible: pure functional programming without any kind of optimization by
hand.
I studied Haskell theoretically (just reading papers and tutorials), but I have
very little actual experience writing Haskell code. I wanted to get my feet wet
with an actual, although very simple, programming task. Here Id like to write
down a little bit about my experience.
1
3 Implementation
3.1 Sorted bag
This was the easiest part. The code is a pleasure to write and to read.
import System.Environment(getArgs)
main = do
args <- getArgs
let m = read $ head args
let n = read $ head $ tail args
Lets see if this even compiles. Since I almost never write anything in Haskell,
I dont trust my syntax perhaps Ive made some mistakes already.
It doesnt compile, but not because of syntax errors:
2
No instance for (Read a0) arising from a use of `read'
The type variable `a0' is ambiguous
Possible fix: add a type signature that fixes these type variable(s)
Note: there are several potential instances:
instance Read System.Random.StdGen
-- Defined in `System.Random'
instance Read Day
-- Defined in `time-1.4.0.1:Data.Time.Format.Parse'
instance Read LocalTime
-- Defined in `time-1.4.0.1:Data.Time.Format.Parse'
...plus 32 others
In the expression: read
In the expression: read $ head args
In an equation for `m': m = read $ head args
You are kidding me, dear Haskell! You say there are thirty-five instances of
Read a0, and I dont even know what a0 means in this context!
Lets try another approach. I recall that Haskell is lazy and wont actually
read m and n unless I do something with these values. Maybe if I print them,
things will work out better? So I add these lines before return ():
print $ show m
print $ show n
Compile...? Nope: now I get four times the same error message about no
instance for read and show. What is wrong?
Suddenly I realize what could be the problem. The functions read and show
are both polymorphic; I never told Haskell that m and n must be integers. Of
course it cannot figure out how to read or to print values of unknown types.
Maybe thats what it means when saying the type variable a0 is ambiguous.
So let me tell Haskell that m and n are integers:
...
let (m :: Int) = read $ head args
let (n :: Int) = read $ head $ tail args
...
Save, compile...
Illegal type signature: `Int'
Perhaps you intended to use -XScopedTypeVariables
In a pattern type-signature
Now I really have no idea what this is saying! I cant even put a type signature
on an integer ?? Why is Int an illegal type signature???
Well, certainly m::Int is a legal type signature, but maybe Im using it at a
wrong place...
So I kept writing m::Int at various places in the program (in the head,
within main, etc.) until finally this worked:
3
print $ show (m :: Int)
print $ show (n :: Int)
The program finally compiled to an 1.3MB executable that prints its command-
line arguments.
import System.Random
and...
Oh no: Module System.Random not found. The fun continues!
Another SO search: there is a whole separate SO question about why Sys-
tem.Random cannot be found, and what to do on Ubuntu! Well, the module is
not found because I have to install it by hand (even though its standard).
4
-- make_random_junk will return a single, random Junk value
make_random_junk :: IO Junk
type JBag = SBag.T Junk -- a sorted bag of junk
-- make_random_sbag n will return a bag with n random Junk values
make_random_sbag :: Int -> IO JBag
make_random_junk = do
r <- randomRIO (0, 10000)
return B r "abc"
I am loving this... The function is applied to three arguments, but its type has
only four ...
OK, admittedly Haskell gave a hint as to where my mistake might be. I
forgot parentheses around a constructed value...
return (B r "abc")
Continue to make_random_sbag.
5
[Junk]. So, lets see... maybe I can use some clever library function that lifts
functions into the IO monad.
Indeed: this is fmap.
It remains to apply this function to make random junk, and we get our list.
The types are correct, so the program must work correctly, right...?
Oh woe! Wrong, I was! Mistake, I did make!
While debugging the completed code, I printed the result of computing
io_list_of_junk; all the random integers were the same!
And replicateM is not equal to fmap replicate! The types are the same,
but the computational effects are quite different. Replacing replicate by
replicateM cured the problem of repeating random integers.
Finally, I was on my way to completing the task. To make the code self-
documenting, I split the computation into intermediate steps and wrote explicit
type signatures.
The types of the functions match; the program works correctly... this time.
4 Conclusion
I omit further steps in the implementation; creating a list of n random bags
and timing each operation was another matter of juggling the monads. After
the initial blunders documented above, I had no more surprises; the program
worked correctly without debugging.
What are my impressions from this brief experience?
6
Haskell is industry-ready, in the sense that it can be installed straight-
forwardly, is free of bugs, has lots of libraries, and all questions are already
on Stackoverflow.
Haskell forces you to organize your computation in a very specific way.
You need to know your way very well around the Prelude, monadic com-
binators, zipWithM, liftM, and such. There is just no other way to,
say, make something n times in Haskell, except if you apply a monadic
replicateM to the corresponding IO function.
The code is beautiful, and once the types match, the program runs cor-
rectly. (Except if you mess up with monads, like I did...)