Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

"Pattern matching" of algebraic type data constructors

Let's consider a data type with many constructors:

data T = Alpha Int | Beta Int | Gamma Int Int | Delta Int

I want to write a function to check if two values are produced with the same constructor:

sameK (Alpha _) (Alpha _) = True
sameK (Beta _) (Beta _) = True
sameK (Gamma _ _) (Gamma _ _) = True
sameK _ _ = False

Maintaining sameK is not much fun, it cannot be checked for correctness easily. For example, when new constructors are added to T, it's easy to forget to update sameK. I omitted one line to give an example:

-- it’s easy to forget:
-- sameK (Delta _) (Delta _) = True

The question is how to avoid boilerplate in sameK? Or how to make sure it checks for all T constructors?


The workaround I found is to use separate data types for each of the constructors, deriving Data.Typeable, and declaring a common type class, but I don't like this solution, because it is much less readable and otherwise just a simple algebraic type works for me:

{-# LANGUAGE DeriveDataTypeable #-}

import Data.Typeable

class Tlike t where
  value :: t -> t
  value = id

data Alpha = Alpha Int deriving Typeable
data Beta = Beta Int deriving Typeable
data Gamma = Gamma Int Int deriving Typeable
data Delta = Delta Int deriving Typeable

instance Tlike Alpha
instance Tlike Beta
instance Tlike Gamma
instance Tlike Delta

sameK :: (Tlike t, Typeable t, Tlike t', Typeable t') => t -> t' -> Bool
sameK a b = typeOf a == typeOf b
like image 569
sastanin Avatar asked Apr 13 '10 08:04

sastanin


1 Answers

Another possible way:

sameK x y = f x == f y
  where f (Alpha _)   = 0
        f (Beta _)    = 1
        f (Gamma _ _) = 2
        -- runtime error when Delta value encountered

A runtime error is not ideal, but better than silently giving the wrong answer.

like image 112
dave4420 Avatar answered Nov 16 '22 03:11

dave4420