Reputation: 143
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
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
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
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