Record selectors in Haskell's Type Classes
haskell, record, typeclass, types
Solution
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}
Problem
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`.