Gabriel Crispino
Gabriel Crispino

Reputation: 45

Two-level type hierarchy with Haskell

I want to model 4 kinds of directions in my application: right, left, up and down. However, I want to be able to have a function that takes only the horizontal ones for a variable and the vertical ones for other variable.

To solve this, I can have two types: HorizontalDirection and VerticalDirection:

data HorizontalDirection = RightDir | LeftDir
data VerticalDirection = UpDir | DownDir

foo :: HorizontalDirection -> VerticalDirection -> String
foo hDir vDir = "This works" 

However, I also would like to be able to have a function that can take one type as well as the other, something like this:

bar :: Direction -> String
bar (HorizontalDirection _) = "Horizontal!"
bar (VerticalDirection _)   = "Vertical!"

but this wouldn't work since HorizontalDirection and VerticalDirection aren't data constructors.

I know I can use Either and make it work, like this:

bar :: (Either HorizontalDirection VerticalDirection) -> String
bar (Left _) = "Horizontal!"
bar (Right _) = "Vertical!"

however, I wonder if I can do this without the Either type.

I also tried using typeclasses:

data HorizontalDirection = RightDir | LeftDir
data VerticalDirection = UpDir | DownDir

class Direction a

instance Direction HorizontalDirection
instance Direction VerticalDirection

baz :: Direction d => d -> String
baz RightDir = "Horizontal"

but that gives me the following compiler error:

Direction.hs:21:5: error:
    • Couldn't match expected type ‘d’
                  with actual type ‘HorizontalDirection’
      ‘d’ is a rigid type variable bound by
        the type signature for:
          baz :: forall d. Direction d => d -> String
        at Direction.hs:20:1-33
    • In the pattern: RightDir
      In an equation for ‘baz’: baz RightDir = "Horizontal"
    • Relevant bindings include
        baz :: d -> String (bound at Direction.hs:21:1)

Is my way of thinking totally wrong here? Or am I just missing something?

Upvotes: 3

Views: 163

Answers (2)

willeM_ Van Onsem
willeM_ Van Onsem

Reputation: 477533

You are quite close, but you need to define the function in the class:

class Direction a where
    baz :: a -> String

instance Direction HorizontalDirection where
    baz _ = "Horizontal"

instance Direction VerticalDirection where
    baz _ = "Vertical"

Note however that Haskell is statically typed, and the types are known at compile time.

Upvotes: 3

Li-yao Xia
Li-yao Xia

Reputation: 33519

Instead of using Either you can declare a new data type with more meaningful names.

data Direction
  = Horizontal HorizontalDirection
  | Vertical   VerticalDirection

bar :: Direction -> String
bar (Horizontal _) = "Horizontal!"
bar (Vertical _) = "Vertical!"

Upvotes: 9

Related Questions