2013-07-20 8 views
5

Chcę zaimplementować Type Class za pomocą kilku domyślnych metod, ale pojawia się błąd, że nie można używać definicji record selectors w definicjach type classes.Selektory rekordów w klasach klasy Haskella

Poniższy kod zasadzie tworzy type class który definiuje add funkcję, do której należy dodać element do rekordu jakiegoś data typerepr. Oto kod:

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} 

kompilator zgłasza błąd:

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} 

Czy jest możliwe aby zadeklarować takich metod w Haskell?

Wyjaśnienie

muszę taką konstrukcję, bo mam trochę data types, które zachowują się w sposób simmilar. Powiedzmy, że mamy A, B i Cdata types. Każdy z nich powinien mieć rekord repr :: DG.Gr a b, gdzie a i b są odrębne dla każdego z A, B i C.

A, B i C te same funkcje, jak add lub delete (która w zasadzie dodać lub usunąć elementy nagrać repr). Jeśli te typy danych mają wiele funkcji, ma to sens, aby zaimplementować funkcje w type class i utworzyć wystąpienia tego type class - te funkcje zostaną automatycznie zaimplementowane dla każdego z naszych data type.

Dodatkowe Chciałbym niektóre z tych data types (powiedzmy, że chcę B) zachowywać się nieco inaczej, wywołując funkcję add na nim. Łatwo jest zaimplementować to zachowanie podczas tworzenia instance z dla dla dla dla.

+2

Odpowiedź brzmi „nie”, ale "w pewnym sensie, używając soczewek", ale co ważniejsze, czuję, że istnieje fundamentalne nieporozumienie dotyczące tego, jakie klasy są tutaj. Pomogłoby to bardzo, gdybyś powiedział, że chcesz mieć taką klasę; możemy zaproponować bardziej idiomatyczną alternatywę. –

+0

@DanielWagner - Dodałem wyjaśnienie problemu, który próbuję rozwiązać - mam nadzieję, że teraz jest jasne, dlaczego próbuję to zrobić :) –

+0

Wyświetl moją zaktualizowaną odpowiedź. W drugim przykładzie używam metody 'update', która wykonuje aktualną aktualizację (może być zaimplementowana za pomocą tej składni aktualizacji rekordów w instancjach), a trzeci przykład używa' Control.Lens'. – JJJ

Odpowiedz

3
  1. Aktualizacja rekord składnia

    <record-instance> { <record-field-name> = ..., ... } 
    

    prace przy <record-instance> jest instancją/term z znanego algebraicznych typu danych (tak, że <record-field-name> jest ona znana dziedzina), w kodzie jest to tylko niektóre (ad-hoc) polimorficzny parametr gr, więc trzeba najpierw przekonwertować gr do Gr, następnie zaktualizować go, a następnie ...

  2. Myślę, że gr i Gr powinny być w pewnym sensie równoważne, tj. Potrzebujemy funkcji odwrotnej do repr, powiedzmy iface, aby móc zaimplementować add.

Oto przykład:

{-# 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 @[email protected], update it, and then 
    -- return an interface back with @[email protected]) 
    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)]}" 

Jeśli niektóre typy danych A i BkruszywoGr a b (tak, że nie możemy napisać odwrotność dla repr), wtedy możemy zrób coś takiego:

{-# 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} 

Możliwe jest również użycie lenses tutaj:

{-# 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} 
0

Można spróbować coś takiego (który używa listy krotka jako przykład zamiast DG)

{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies, TypeSynonymInstances #-} 

class MyClass g a b | g -> a b where 
     extract :: g -> [(a,b)] 
     construct :: [(a,b)] -> g 

     empty :: g 
     empty = construct [] 

     add :: (a,b) -> g -> g 
     add i d = construct $ [i] ++ (extract d) 

data A = A {reprA :: [(Int,Int)]} 

instance MyClass A Int Int where 
     extract = reprA 
     construct = A 

data B = B {reprB :: [(String,String)]} 

instance MyClass B String String where 
     extract = reprB 
     construct = B