notBob
notBob

Reputation: 143

handling different argument types for different instances of typeclass

I have a typeclass C.

class C a where
  changeProperty :: a -> ??? -> a

C has two (or more) instances

data A = A AProperty X
data B = B BProperty Y
instance C A where
   changeProperty (A _ x) ap = A ap x
instance C B where
   changeProperty (B _ y) bp = B bp y

bp and ap are of different types. AProperty is only compatible with A and BProperty is only compatible with B. Is it possible to make some for of type signature, or other solution that allows the method changeProperty to accept only these combinations? The only options I have been able to think of either completely separate the methods (in this case into changeAProperty and changeBProperty), or require me to make AProperty and BProperty the same type, which allows for invalid combinations like an A with a BProperty. Ideally I would like to have a Property class of which AProperty and BProperty are instances, but I don't know how to do this and avoid the invalid combinations.

edit: The context of this problem is a script for building molecules for MD simulation. A, B and so on are connections between atoms. For example A would be a bond, containing the parameters for this bond in AProperty and the Atoms that are involved in X. B Would be an angle and BP would contain the parameters related to that angle, Y the Atoms and so on.

Upvotes: 0

Views: 58

Answers (3)

Daniel Wagner
Daniel Wagner

Reputation: 152737

I like the other solutions for their generality. But in this particular case, I'd like to propose a classless solution.

data Meta properties parameters = Meta properties parameters

changeProperty :: Meta props params -> props -> Meta props params
changeProperty (Meta props params) props' = Meta props' params

type A = Meta AProperty X
type B = Meta BProperty Y

This kind of thing is a very common need; see also this question for a brief overview of the record accessor way of achieving this and the libraries folks have built up for reducing the pains associated with record accessors.

Upvotes: 4

leftaroundabout
leftaroundabout

Reputation: 120711

I reckon you want this:

{-# LANGUAGE TypeFamilies #-}
class C a where
  type CProperty a :: *
  changeProperty :: a -> CProperty a -> a

data A = A AProperty X
data B = B BProperty Y
instance C A where
   type CProperty A = AProperty
   changeProperty (A _ x) ap = A ap x
instance C B where
   type CProperty B = BProperty
   changeProperty (B _ y) bp = B bp y

...or, if indeed AProperty only applies to A, a MPTC solution with functional dependencies:

{-# LANGUAGE FunctionalDependencies #-}

class C a ap | ap -> a, a -> ap where
    changeProperty :: a -> ap -> a

data X = X
data Y = Y

data AProperty = AProperty
data BProperty = BProperty

data A = A AProperty X
data B = B BProperty Y

instance C A AProperty where
    changeProperty (A _ x) ap = A ap x
instance C B BProperty where
    changeProperty (B _ y) bp = B bp y

As you've noticed, this also compiles without the functional dependencies, however that is often cumbersome to use in practice because then for all the compiler knows, A could have any other property and AProperty could apply to any other type, so you end up having to type lots of explicits constraints for the type checker.

Upvotes: 3

notBob
notBob

Reputation: 143

As Willem Van Onsem mentioned, it is possible to use a multi-parameter type class.

{-# LANGUAGE MultiParamTypeClasses #-}
class C a ap where
    changeProperty :: a -> ap -> a

data X = X
data Y = Y

data AProperty = AProperty
data BProperty = BProperty

data A = A AProperty X
data B = B BProperty Y
instance C A AProperty where
    changeProperty (A _ x) ap = A ap x
instance C B BProperty where
    changeProperty (B _ y) bp = B bp y

As for if it is the best solution I don't know.

Upvotes: 1

Related Questions