Regarding the problem selecting the right image type, you have enough information in your header to select which type of image to create.
case header of
Nothing -> putStrLn $ source ++ ": Bad image"
Just (header,_) -> case (imageEncoding header) of
ASCII -> putStrLn $ source ++ ": Already an ASCII image"
Binary -> let image = decode content :: Maybe PPMImage
in convertImage image dest
Becomes
image <- case header of
Nothing -> putStrLn $ source ++ ": Bad image"
Just (Header _ ASCII _ _) ->
putStrLn $ source ++ ": Already an ASCII image"
Just (Header PPM _ _ _) -> decode content :: Maybe PPMImage
Just (Header PGM _ _ _) -> decode content :: Maybe PGMImage
convertImage image dest
Besides, this is a check that should really be happening inside your call to decode. Ask yourself what would happen if your user called decode content :: Maybe PGMImage
with PPM encoded content.
Perhaps consider leaving the image format at the value level?
newtype Image pix = MkImg (Array Coord pix) deriving Show
decode PPM content :: Image p
Following up on cdk's answer: if you are worried about speed, you should take care how you represent your data.
data ColorPixel = ColorPixel Int Int Int deriving Show
While this looks like it's cheap, beware that those Int
s are boxed values. This means that they can hold either thunks, or pointers to Int
s. This extra layer of indirection is potentially harmful to performance, so try unpacking the values directly into the record:
{-# LANGUAGE BangPatterns #-}
data ColorPixel = ColorPixel {-# UNPACK #-} !Int {-# UNPACK #-} !Int {-# UNPACK #-} !Int
deriving Show
note: compiling with -funbox-strict-fields allows you to omit the UNPACK pragmas and have the compiler add them for you.
You should also do this for all performance sensitive single field datatypes that you don't convert into newtypes.
EDIT: Incorporated cdk's feedback re: UNPACK
REF: http://www.haskell.org/ghc/docs/7.0.2/html/users_guide/pragmas.html#unpack-pragma