Specifying a constraint for a class

157 views Asked by At

I have three data declarations in the different modules:

data Data1 = Data1
  { id :: UUID
  , field2 :: String
  , field3 :: Int
  }

data Data2 = Data2
  { id :: UUID
  , field1112 :: String
  , field4433 :: String
  }


data Data3 = Data3
  { id :: UUID
  , field22 :: Double
  , field344 :: Int
  }

And a class declaration:

class MyClass1 a where
  method1 ....

How can I specify a constraint for MyClass1 so that only Data1, Data2 and Data3 can be instantiated by MyClass1 because they all have the field id? I tried the following, but it didn't seem right to me and I got errors:

-- in the different module
class MyDataBase a where
  id :: a -> UUID

-- in the module Data1.hs
instance MyDataBase Data1 where
  id d1 =  

-- in the module Data2.hs
-- and so on....


class (MyDataBase a) => MyClass1 a where
  method1 .... = -- calling 
2

There are 2 answers

5
Bartek Banachewicz On

so that only Data1, Data2 and Data3 can be instantiated by MyClass1 because they all have the field id?

What about types you don't know about that also have that field?

In general, you can accomplish things like that with Lens (regular Haskell records are IMHO unusable); if you name your fields _Data1Id and _Data2Id (customizable), you'll get a HasId class with id lens and instances for that class for Data1 and Data2. Then you can use that class as a constraint further.

Of course you don't have to use makeFields and you can write the instances yourself. Not sure why you'd do that, though.


You didn't specify what errors you got, but I bumped off UndecidableInstances. Here's how to get around "Constraint is no smaller than the instance head" error:

class A a where aOp :: a -> Int
class B a where bOp :: a -> Int

data C = forall a. B a => C a
instance A C where aOp (C a) = bOp a

This will allow you to use any type that satisifies B in a A context (but as Carsten pointed out, it's also an antipattern of sorts).

5
Bakuriu On

You don't want to do that. The simplest way of achieving what you want is the following:

class HasId a where
    getId :: a -> UUID

class HasId a => MyClass1 a where
    method1 =   -- NOTE: Use "getId" instead of plain "id".

when you define your data types you simply do:

data Data1 = Data1
    { id :: UUID
    , field2 :: String
    , field3 :: Int
    }

instance HasId Data1 where
    getId = id

If you want to add a new data type to the family you simply create a new instance of the HasId class.

You don't need no extensions, nothing fancy, no libraries. Sure you can probably shorten the code or do something more fancy, but I'd prefer to use simple code that works and I understand versus code that I don't understand.