Search code examples
haskellpattern-matchingalgebraic-data-typesoption-typeundefined-function

How to make function useable only for a certain data constructor of an ADT?


I'm currently playing around with ADTs in Haskell and try to build an ADT Figure:

data Figure = Rect { x :: Integer, y :: Integer, width :: Integer, height :: Integer}
            | Circle { x :: Integer, y :: Integer, radius :: Integer}
            | CombiFigure Figure Figure
            deriving (Eq, Show, Read)

Now I came across the question how to implement a function that should not accept every Figure, but e.g. only a Circle.

Do I already have a bad design? Or is there some best-practice how to do this?

As example, think about a diameter function. All that came to my mind (I'm a complete beginner in Haskell) are the following two options, using undefined or Maybe:

1:

diameter :: Figure -> Integer
diameter (Circle _ _ r) = 2 * r
diameter _ = undefined

2:

diameter :: Figure -> Maybe Integer
diameter (Circle _ _ r) = Just (2 * r)
diameter _ = Nothing

Are there more preferable ways on how to accomplish that? Thanks!


Solution

  • You are correct that there is something not right here. The best way of thinking about it would be to start at the function diameter and decide what it's type should ideally be. You would likely come up with

    diameter :: Circle -> Integer
    diameter (Circle _ _ r) = 2 * r
    

    because diameters are only defined for circles.

    This means that you will have to augment your data structure by splitting out Circle (and Rect too):

    data Figure = RectFigure Rect
                | CircleFigure Circle
                | CombiFigure Figure Figure
                deriving (Eq, Show, Read)
    
    data Rect = Rect { rectX :: Integer, rectY :: Integer, rectWidth :: Integer, height :: Integer}
              deriving (Eq, Show, Read)
    
    data Circle = Circle { circleX :: Integer, circleY :: Integer, circleRadius :: Integer}
                deriving (Eq, Show, Read)
    

    which is nice because it is now more flexible: you can write functions that don't care what Figure they are applied to, and you can write functions that are defined on specific Figures.

    Now, if we are in a higher-up function and have a reference to a Figure and we want to compute its diameter if it's a CircleFigure, then you can use pattern matching to do this.

    Note: using undefined or exceptions (in pure code) is likely a code smell. It could probably be solved by rethinking your types. If you have to indicate failure, then use Maybe/Either.