2009-09-13, 22:24
A few days ago I noticed that there were a few Haskell packages on AUR that
had received updates. This was the excuse I had been waiting for to address
the second part of keeping my Haskell packages up-to-date (I’ve written about
the first part before, dealing with an update to GHC).
It’s easy to find the packages with available updates:
Unfortunately it isn’t as easy as just updating the listed packages. Any
package that depends on an updated package really should be re-compiled and
re-installed to guarantee that the entire system behaves as expected after the
upgrade. Of course pacman can handle it:
% pacman -Rcn <all pkgs with updates>
This will list all packages that will be removed. After removing them all,
they can all be re-installed. That is of course not quite as nice as it could
be, since they all then will be explicitly installed, it would be nicer to
just re-install the “top-level packages”. This is one way to achieve this.
I did a bit of refactoring and put the Arch-related functions from the
previous post in their own module, Arch. Then I added a function that takes
a package and recursively inspects Required By until a “top-level package”
(i.e. a package that doesn’t require any other package) is reached:
getTopRequiredBy pkg = let
tops = do
first <- getRequiredBy pkg
if null first
then return [pkg]
else liftM concat $ mapM getTopRequiredBy first
in liftM nub tops
After that it’s straight forward to write up a little tool which offers some
advice on what to do:
main = do
pkgsToUpgrade <- getArgs
pkgsToReinstall <- liftM (nub . concat) $ mapM Arch.getTopRequiredBy pkgsToUpgrade
putStrLn $ "To remove : pacman -Rnc " ++ unwords pkgsToUpgrade
putStrLn $ "To re-install : yaourt -S " ++ unwords pkgsToReinstall
Using it on the packages I wanted to upgrade gave the following output:
% runhaskell PkgUpgrade.hs haskell-{configfile,haxml,json,missingh,safe,testpack,time}
To remove : pacman -Rnc haskell-configfile haskell-haxml haskell-json haskell-missingh haskell-safe haskell-testpack haskell-time
To re-install : yaourt -S haskell-configfile haskell-haxml haskell-json haskell-hsh haskell-safe
Following that advice seemed to work just like I intended.
2009-09-07, 23:17
The previous post was a fairly silly example, unless of course it’s more useful
than I realise
However, here’s something that I can see a bit more use of, a
monad that restricts reading and writing of files to two files, one to read
from and one to write to.
Again, the first step is to create a data type:
newtype TwoFileIO a = TwoFileIO { execTwoFileIO :: (Handle, Handle) -> IO a }
This defines a type wrapping a function that takes a pair of handles (one for
input and one for output) and returns an “IO action”. Turning this into a
monad is straight forward (actually it’s similar to the Reader monad):
instance Monad TwoFileIO where
return v = TwoFileIO $ \ _ -> return v
(>>=) m f = let
fInIO = execTwoFileIO . f
in TwoFileIO $ \ hs ->
execTwoFileIO m hs >>= \v -> fInIO v hs
To return a value we can simply drop the pair of handles and return the value
in IO. Bind (>>=) only looks complicated, what happens is that the first
argument is “executed” with the provided handles, then the second argument is
passed the result and executed with the same pair of handles. Of course the
handles aren’t actually known yet, so an anynmous function is created, and
wrapped in an instance of TwoFileIO. That’s it for the most complicated
part.
In order to avoid having to manually open files and wire everything up I wrote
the following convenience function:
runFileIO m iFn oFn = do
iH <- openFile iFn ReadMode
oH <- openFile oFn WriteMode
res <- execTwoFileIO m (iH, oH)
mapM_ hClose [iH, oH]
return res
Yes, it does lack a bit in exception handling, but it’s good enough for now.
Then I can define the actions/functions that are available inside TwoFileIO.
Reading and writing lines:
fioPutStrLn s = TwoFileIO $ \ (iH, oH) ->
hPutStrLn oH s
fioGetLine = TwoFileIO $ \ (iH, oH) ->
hGetLine iH
Note how it now becomes very hard to mix up the files and accidentally read
from the output file or write to the input file.
As a little test function I used this one, which reads two lines and then
writes them in the reversed order:
get1stN2ndPutLast = do
first <- fioGetLine
second <- fioGetLine
fioPutStrLn second
fioPutStrLn first
I can now test this using ghci:
> h <- openFile "testIn.txt" ReadMode
> hGetContents h
"line 0\nline 1\nline 2\n"
> runFileIO get1stN2ndPutLast "testIn.txt" "testOut.txt"
> h <- openFile "testOut.txt" ReadMode
> hGetContents h
"line 1\nline 0\n"
2009-09-07, 23:04
I’ve many times heard that Haskell can be used to prevent certain kind of
programmer mistakes. In a presentation on Darcs it was explained how
GADTs (especially phantom types) are used in Darcs to make sure that
operations on patches follow certain rules. Another way, and at least it
sounds easier, is to limit the available functions by running code in
some sort of container. This being Haskell, that container is often a
monad. I’ve really never seen this presented, so I thought I’d try to do it, and indeed
it turns out to be very simple.
I started with a data type:
newtype HideIO a = HideIO { runHideIO :: IO a }
which I then made into a Monad in order to make it easy to work with:
instance Monad HideIO where
return = HideIO . return
(>>=) m f = HideIO $ runHideIO m >>= runHideIO . f
Then I can create an IO function that are allowed in the HideIO monad:
hioPutStrLn = HideIO . putStrLn
In ghci I can then do the following:
> runHideIO $ hioPutStrLn "Hello, World!"
Hello, World!
But I can’t do much else.