Search code examples
haskellfunctional-programming

Haskell: A recursive type implementation: RGB, color inversion, color mixing


I have this recursive, algebraic data type for representing colors and mixing/inverting particular RGB channels:


data Color = Red | Green | Blue | Mix Color Color | Invert Color
  deriving Show

rgb :: Color -> [Double]
rgb Red = [1,0,0]
rgb Green = [0,1,0]
rgb Blue = [0,0,1]

rgb (Invert Red) = let cols = rgb Red
                   in [1 - cols !! 0, cols !! 1, cols !! 2]
                   
rgb (Invert Green) = let cols = rgb Red
                     in [cols !! 0, 1 - cols !! 1, cols !! 2]
                   
rgb (Invert Blue) = let cols = rgb Red
                    in [cols !! 0, cols !! 1, 1 - cols !! 2]
                   
rgb (Invert color) = rgb color

rgb (Mix Red Green) = let cols1 = rgb Red
                          cols2 = rgb Green
                          cols3 = rgb Blue
                          ave = (cols1 !! 0 + cols2 !! 1) / 2
                      in [ave, ave, cols3 !! 2]

rgb (Mix Green Red) = rgb (Mix Red Green)

rgb (Mix Green Blue) = let cols1 = rgb Red
                           cols2 = rgb Green
                           cols3 = rgb Blue
                           ave = (cols1 !! 0 + cols2 !! 1) / 2
                       in [ave, ave, cols3 !! 2]

rgb (Mix Blue Green) = rgb (Mix Green Blue)

rgb (Mix Red Blue) = let cols1 = rgb Red
                         cols2 = rgb Green
                         cols3 = rgb Blue
                         ave = (cols1 !! 0 + cols2 !! 1) / 2
                      in [ave, ave, cols3 !! 2]

rgb (Mix Blue Red) = rgb (Mix Red Blue)

When I run the code in

main :: IO ()
main = do
  print (rgb (Mix Red Green))
  print (rgb (Mix Red Green))
  print (rgb (Mix Red (Mix Red Green)))
  print (rgb (Invert Red))
  print (rgb (Invert (Mix Red (Mix Red Green))))
  print (rgb (Mix (Invert Red) (Invert Green)))

I get the runtime exception:

[1.0,1.0,1.0]
[1.0,1.0,1.0]
*** Exception: Main.hs:(158,1)-(195,39): Non-exhaustive patterns in function rgb

What am I doing wrong here?

(I just started with Haskell algebraic datatypes, so, of course, I make mistakes.)


Solution

  • For Mix, you only pattern match on Mix Red Green, Mix Blue Red, etc. Not on Mix (Invert Red) Green, etc. or Mix (Mix Red Green) Red. Making patterns for all these is impossible, since your data structure can produce an infinite amount of values. Indeed, for example rgb (Invert (Invert (… (Invert Red) …))) with an arbitrary number of Inverts.

    You can pick the value(s) wrapped in Invert or Mix and then call rgb on these and invert or mix the end product, like:

    rgb :: Color -> [Double]
    rgb Red = [1, 0, 0]
    rgb Green = [0, 1, 0]
    rgb Blue = [0, 0, 1]
    rgb (Invert x) = map (1 -) (rgb x)
    rgb (Mix x1 x2) = zipWith mid (rgb x1) (rgb x2)
      where
        mid y1 y2 = (y1 + y2) / 2

    For the sample data, this then produces:

    ghci> rgb (Mix Red Green)
    [0.5,0.5,0.0]
    ghci> rgb (Mix Red Green)
    [0.5,0.5,0.0]
    ghci> rgb (Mix Red (Mix Red Green))
    [0.75,0.25,0.0]
    ghci> rgb (Invert Red)
    [0.0,1.0,1.0]
    ghci> rgb (Invert (Mix Red (Mix Red Green)))
    [0.25,0.75,1.0]
    ghci> rgb (Mix (Invert Red) (Invert Green))
    [0.5,0.5,1.0]