Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Displaying IO arrays

Tags:

arrays

io

haskell

As I've been learning haskell I've enjoyed the pure parts but now I am stumbling through the monadic and IO parts and probably experiencing what some people find truly infuriating about the language. I solving a project euler problem and I simple want a mutable array because I have to update elements frequently by index. I tried Vectors but couldn't get them working so I tried Data.Array.IO. I can read and write elements fine but I can't display the array in terminal the way I want. So far I have this.

test = do
    arr <- newArray (1,10) 37 :: IO (IOArray Int Int)
    a <- readArray arr 1
    writeArray arr 1 64
    b <- readArray arr 1
    dispArray arr 
    return ()

dispArray arr = do
    (a,b) <- getBounds arr
    printf "["
    dispArray' arr a
    printf "]\n"
        where dispArray' arr i = do
                (a,b) <- getBounds arr
                if i < a || i > b
                    then return ()
                    else do
                        v <- readArray arr i
                        print v
                        dispArray' arr (i+1)

The ouput of this as you would expect is this:

[64
37
37
37
37
37
37
37
37
37
]

But this is inconvenient and I want this [64,37,37,37.... like this. I've seen functions that are something like toList, but I don't want this. I don't want to convert to a list everytime I display. So I figured I would need to use printf. So I replaced print v with printf " %s," (show v). But this doesn't compile. I don't know why. I thought it would because print :: Show a => a -> IO () and show :: Show a => a -> String so why wouldn't it work because %s signifies a string? So I then put to calls next to each other. To see if printf would even work.

printf " %s," "hello"
print v

Which compiles and displays:

[ hello,64
 hello,37
 hello,37
 hello,37
 hello,37
 hello,37
 hello,37
 hello,37
 hello,37
 hello,37
]

Why can I not use show v? Why is haskell IO so infuriating to beginners?

like image 335
DiegoNolan Avatar asked Dec 15 '22 12:12

DiegoNolan


1 Answers

This is an interesting type-checking puzzle.

The error message that the call to printf produces is

Could not deduce (PrintfType (m a0))
  arising from the ambiguity check for `dispArray'

The phrases Could not deduce and ambiguity typically hint at the fact that GHC has insufficient type information in order to conclude how this program should be typed. This might be a real type error, but it's also possible that it can be fixed simply by providing more type information (and this is the case here).

The culprit here is really printf, combined with the flexibility of the mutable array interface, and not so much Haskell's IO system. The type of printf is an ingenious hack, but still a hack. In order to know a flexible number of parameters of various types that depend on just the format string, printf has a type that isn't very safe nor very informative:

printf :: PrintfType r => String -> r

So all we really know for sure is that the first argument is of type String. The rest can be any type r that is in the type class PrintfType.

The details of the instances do not matter. What's interesting is that show produces a String, and if we apply printf to a format string and then a show-produced second string, we are still left with a rather uninformative type:

> :t printf "%s," (show 2)
printf "%s," (show 2) :: PrintfType t => t

In particular, there's no indication here that the result is to be in the IO monad.

This normally wouldn't be a problem, if GHC could conclude from the context that you're in IO. But within dispArray', the only other functions you are calling are readArray, getBounds, return (and dispArray' recursively). None of these functions specifies that it lives in IO either. In particular, all of the array functions are overloaded over the monad, for example:

getBounds :: (Ix i, MArray a e m) => a i e -> m (i, i)

(And indeed, getBounds could, for example, also work in an ST monad context.) So there's simply nothing in dispArray' that determines that you live in IO. And that in turn means that GHC cannot resolve the type of printf.

As I said, it's a consequence of the desired flexibility of printf that printf itself cannot provide this information, and it has to be available externally.

The solution is easy enough. As suggested in one of the comments, it is sufficient to annotate the result type of the call to printf:

printf "%s," (show v) :: IO ()

As you're using printf anyway (and if you're actually only interested in arrays of decimal numbers), you could also use:

printf "%d," v :: IO ()

It would also sufficient (but less clear to the reader) to give a type signature for anything else within the definition of dispArray' so that it fixes the return type to be IO (). For example, you could annotate the return () in the then-branch of the if expression:

return () :: IO ()
like image 159
kosmikus Avatar answered Dec 30 '22 03:12

kosmikus