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: data ColorPixel = ColorPixel {-# UNPACK #-} Int {-# UNPACK #-} Int {-# UNPACK #-} Int deriving Show 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