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 potentially hold thunks, or pointers to ints. It would be very rare that you would ever need just one color component, try using strictness annotations here: {-# LANGUAGE BangPatterns #-} data ColorPixel = ColorPixel !Int !Int !Int deriving Show You should also do this for all performance sensitive single field datatypes that you don't convert into newtypes.