liveBook · Manning

Validation

As explained in Learn Haskell by Example

Validation is the process of checking data against specified conditions to determine whether it is acceptable or well-formed.

Validation turns parsed PNM data into an image value that is safe to process. Raw input may have the wrong number of pixels, a pixel shape that disagrees with its header, or color values outside the declared maximum. A validator checks these conditions while converting raw pixels into suitable image pixels. section 13.1.1 section 13.2

A PNM validation procedure checks that:

  • The pixel count equals the header width multiplied by height. section 13.1.1 section 13.2
  • Pixel constructors match the format identified by the magic number: bitmap and graymap use single values, while pixmap uses RGB values. section 13.1.1 section 13.2
  • Graymap and pixmap component values do not exceed the header’s maximum value. section 13.1.1 section 13.2
  • Bitmap images do not carry a maximum value, and their raw values must be 0 or 1. section 13.2

Use Either String to make failure explicit: a successful check returns Right with a converted pixel or image, while a failed check returns Left with an error. This lets validation stop at the first invalid element instead of constructing a partially trustworthy image. section 13.2 section 13.2

Validating a graymap pixel

For a graymap pixel represented as Single g, first reject any value greater than maxVal. Otherwise, compute the ratio g / maxVal, scale it to the desired output representation, and round it. A non-Single value is rejected because it cannot represent a graymap pixel. section 13.2

Validation can be organized as small conversion functions for bitmap, graymap, and pixmap pixels, plus helpers that classify magic numbers and verify image size. A top-level validatePnm function chooses the appropriate conversion from the header and applies it across all pixels with mapM. section 13.2 section 13.2

Listing 13.11 The validation function for PNM parse results
import qualified Data.Vector as V

...

validatePnm :: RawPnm -> Either String DynamicPnmImage
validatePnm img@(RawPnm {header, imageData})
  | not $ imageSizeCorrect img =
    Left $                                                    #1
      "The number of pixels given does not match "
        <> "the expected size inferred from the header"
  | indicatesBitmap magicNum =
    case maxVal header of
      Nothing -> PnmGray8Bit `withTransform`
 bitmapPixel  #2
      Just _ ->
        Left $                                               #1
          "Image seems to be a bitmap image "
            <> "but has a maximum value set"
  | indicatesGraymap magicNum =
    case maxVal header of
      Nothing ->
        Left $                                               #1
          "Image seems to be a graymap image "
            <> "but has no maximum value set"
      Just maxVal ->
        if maxVal <= 255
          then
            PnmGray8Bit              #3
              `withTransform` graymapPixel maxVal
          else
            PnmGray16Bit            #3
              `withTransform` graymapPixel maxVal
  | indicatesPixmap magicNum =
    case maxVal header of
      Nothing ->
        Left $                                               #1
          "Image seems to be a pixmap image "
            <> "but has no maximum value set"
      Just maxVal ->
        if maxVal <= 255
          then
            PnmColor8Bit                   #4
              `withTransform` pixmapPixel maxVal P.PixelRGB8
          else
            PnmColor16Bit                 #4
              `withTransform` pixmapPixel maxVal P.PixelRGB16    
  | otherwise = Left "Image seems to be of unknown type"       #5
  where
    magicNum = magicNumber header
    width' = fromIntegral $ width header
    height' = fromIntegral $ height header

    withTransform ::
      (Int -> Int -> V.Vector px -> PnmImage px) ->
      (RawPixel -> Either String px) ->
      Either String DynamicPnmImage
    withTransform c f =
      let mConvertedData = mapM f $ pixels imageData  #6
       in fmap mkRes mConvertedData  #7
      where
        mkRes = DynamicPnmImage . c width' height'
 . V.fromList  #8

The complete validator shows the order of global checks, header-based format selection, and error cases before pixel data is accepted. section 13.2

How Learn Haskell by Example covers it

How deep it goes
Works through per-pixel conversion, bounds checks, helper predicates, and top-level error propagation with Either and mapM.
Examples
Uses bitmap, graymap, and RGB pixmap pixels, including invalid constructors and values exceeding a maximum.
What it assumes
Introduces validation after parsing RawPnm values and uses basic Haskell constructors, type signatures, and Either results.
What you can do after
Apply it via JuicyPixels pixel types to reject malformed PNM parse results before later image processing.
How it connects
Connects RawPnm, magic numbers, bitmap, graymap, pixmap, Either, mapM, and JuicyPixels pixel types.

Also explained in

Compare how 30 books explain validation →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Learn Haskell by Example. The section links point to the parts of the book it draws on.