I want to implement a Type Class
with few default methods, but I'm getting an error, that I cannot use record selectors
inside type classes
definitions.
The following code basically creates type class
which defines add
function, which should add an element to the repr
record of some data type
. Here is the code:
import qualified Data.Graph.Inductive as DG
class Graph gr a b where
empty :: DG.Gr a b
empty = DG.empty
repr :: gr -> DG.Gr a b
-- following function declaration does NOT work:
add :: a -> gr -> gr
add el g = g{repr = DG.insNode el $ repr g}
The compiler throws error:
repr is not a record selector
In the expression: g {repr = DG.insNode el $ repr g}
In an equation for add:
add el g = g {repr = DG.insNode el $ repr g}
Is it possible to declare such methods in Haskell?
Clarification
I need such design because I've got some data types
, which behave in simmilar way. Lets say, we got A
, B
and C
data types
. Each of them should have a record repr :: DG.Gr a b
, where a
and b
are distinct for each of A
, B
and C
.
A
, B
and C
share the same functions, like add
or delete
(which basically add or delete elements to record repr
). If these data types share a lot of functions it make sense to implement the functions in type class
and make instances of this type class
- these functions will be implemented for each of our data type
automatically.
Additional I would love some of these data types
(lets say I want B
) to behave slighty different when calling add
function on it. It is easy to implement this behaviour when making instance
of the type class
for B
.
The record update syntax
<record-instance> { <record-field-name> = ..., ... }
works when <record-instance>
is an instance/term of a known algebraic data type (so that <record-field-name>
is it known field), in your code it is just some (ad-hoc) polymorphic parameter gr
, so you need first to convert gr
to Gr
, then update it, and then...
I think that gr
and Gr
should be equivalent in some sense, i.e. we need an inverse function for repr
, say iface
, to be able to implement add
.
Here is an example:
{-# LANGUAGE MultiParamTypeClasses, TypeSynonymInstances, FlexibleInstances #-}
data Gr a b = Gr { _internal :: [(a, b)] } deriving ( Show, Read )
class Graph gr a b where
repr :: gr -> Gr a b
iface :: Gr a b -> gr
-- iface . repr == id {gr}
-- repr . iface == id {Gr a b}
-- add element via "interface" (get a representation via @repr@, update it, and then
-- return an interface back with @iface@)
add :: (a, b) -> gr -> gr
add el g = let r = repr g in iface r { _internal = el : _internal r }
-- or
add el = iface . insNode el . repr where
insNode x (Gr xs) = Gr (x : xs) -- or whatever
instance Graph String Int Int where
repr = read
iface = show
test :: String
test = add (1 :: Int, 2 :: Int) "Gr { _internal = [] }"
-- test => "Gr {_internal = [(1,2)]}"
If some data types A
and B
aggregate Gr a b
(so that we can't write an inverse for repr
), then we can do something like this:
{-# LANGUAGE MultiParamTypeClasses #-}
data Gr a b = Gr [(a, b)] deriving ( Show )
class Graph gr a b where
repr :: gr -> Gr a b
update :: gr -> (Gr a b -> Gr a b) -> gr
-- 2: update :: gr -> Gr a b -> gr
add :: (a, b) -> gr -> gr
add el g = update g $ insNode el
-- 2: update g (insNode el $ repr g)
where insNode x (Gr xs) = Gr (x : xs)
data A = A { _aRepr :: Gr Char Char, _aRest :: Char } deriving ( Show )
data B = B { _bRepr :: Gr Int Int, _bRest :: Int } deriving ( Show )
instance Graph A Char Char where
repr = _aRepr
update r f = r { _aRepr = f $ _aRepr r }
-- 2: update r g = r { _aRepr = g }
instance Graph B Int Int where
repr = _bRepr
update r f = r { _bRepr = f $ _bRepr r }
-- 2: update r g = r { _bRepr = g }
testA :: A
testA = add ('1', '2') $ A (Gr []) '0'
-- => A {_aRepr = Gr [('1','2')], _aRest = '0'}
testB :: B
testB = add (1 :: Int, 2 :: Int) $ B (Gr []) 0
-- => B {_bRepr = Gr [(1,2)], _bRest = 0}
It is also possible to use lenses here:
{-# LANGUAGE MultiParamTypeClasses, TemplateHaskell #-}
import Control.Lens
data Gr a b = Gr [(a, b)] deriving ( Show )
insNode :: (a, b) -> Gr a b -> Gr a b
insNode x (Gr xs) = Gr (x : xs)
class Graph gr a b where
reprLens :: Simple Lens gr (Gr a b)
add :: Graph gr a b => (a, b) -> gr -> gr
add el = reprLens %~ insNode el
data A = A { _aRepr :: Gr Char Char, _aRest :: Char } deriving ( Show )
data B = B { _bRepr :: Gr Int Int, _bRest :: Int } deriving ( Show )
makeLenses ''A
makeLenses ''B
instance Graph A Char Char where
reprLens = aRepr
instance Graph B Int Int where
reprLens = bRepr
main :: IO ()
main = do
let a = A (Gr []) '0'
b = B (Gr []) 0
print $ add ('0', '1') a
print $ add (0 :: Int, 1 :: Int) b
-- A {_aRepr = Gr [('0','1')], _aRest = '0'}
-- B {_bRepr = Gr [(0,1)], _bRest = 0}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With