Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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!

like image 403
Markus Weninger Avatar asked Jan 23 '16 13:01

Markus Weninger


2 Answers

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.

like image 98
Will Sewell Avatar answered Nov 15 '22 07:11

Will Sewell


Your type definition by itself (i.e data Figure = ...) is introducing partial functions. e.g. even though width is of type width :: Figure -> Integer it can only work on Rect values:

\> width $ Rect 1 2 3 4
3
\> width $ Circle 1 2 3 
*** Exception: No match in record selector width

so, you already have defined functions which can work on one figure but not another (similar to diameter function in the question).

That said, a 3rd solution would be to define Circle, Rectangle etc, as separate types; then, define a Figure type class which defines the common interface of these types.

class Figure a where
    area, perimeter :: a -> Double

instance Figure Circle where
    area = ...
    perimeter = ...

Additionally each type may have their own exclusive functions. Or, you may add more interfaces, (i.e. type classes) which cover some but not all the figure types.

An advantage of type classes is that they are easier to extend; e.g. if one wants to add, say, a Triangle type later on, he can opt-in any type class which applies to a triangle, and define an instance only for those type classes.

Whereas in the data Figure = ... approach, you need to find every function which can take a Figure as argument and make sure it will handle a Triangle as well. If you are shipping a library then you do not have access to all these functions.

>> for the reference, there was a similar recent discussion of data declaration vs type classes on haskell cafe mailing list.

like image 20
behzad.nouri Avatar answered Nov 15 '22 07:11

behzad.nouri