2013-07-20 3 views
5

Ich möchte ein Type Class mit einigen Standardmethoden implementieren, aber ich erhalte einen Fehler, den ich innerhalb type classes Definitionen nicht verwenden kann.Datensatz-Selektoren in Haskell's Type-Klassen

Der folgende Code im Grunde schafft die type classadd Funktion definiert, die ein Element zum repr Aufzeichnung einiger data type hinzufügen sollten. Hier ist der 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} 

Der Compiler wirft Fehler:

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} 

Ist es möglich, solche Verfahren in Haskell zu erklären?

Klärung

Ich brauche solches Design, weil ich einige data types habe, die in simmilar Art und Weise verhalten. Sagen wir, wir haben A, B und Cdata types. Jede von ihnen sollte eine Aufzeichnung repr :: DG.Gr a b haben, wobei a und b für jede von A, B und C unterschiedlich sind.

A, B und C teilen die gleichen Funktionen, wie add oder delete (die im Grunde Elemente hinzuzufügen oder löschen repr aufnehmen). Wenn diese Datentypen viele Funktionen gemeinsam haben, ist es sinnvoll, die Funktionen in type class zu implementieren und Instanzen dieser type class zu machen - diese Funktionen werden automatisch für jede unserer data type implementiert.

Weitere Ich würde gerne einige dieser data types (lassen Sie uns sagen, ich möchte B) verhalten sich geringfügig anders beim Aufruf add Funktion darauf. Es ist einfach, dieses Verhalten zu implementieren, wenn instance der type class für B gemacht wird.

+2

Die Antwort „nein“ ist, aber "irgendwie, mit Linsen", aber noch wichtiger, ich fühle, dass es ein grundlegendes Missverständnis darüber gibt, wofür Klassen hier irgendwo sind. Es würde viel helfen, wenn Sie sagten, warum Sie solch eine Klasse haben wollen; wir können vielleicht eine idiomatische Alternative vorschlagen. –

+0

@DanielWagner - Ich habe eine Klärung für das Problem hinzugefügt, das ich versuche zu lösen - ich hoffe, es ist jetzt genug klar, warum ich versuche, dies zu tun :) –

+0

Siehe meine aktualisierte Antwort. Im zweiten Beispiel verwende ich 'update' Methode, die die eigentliche Aktualisierung durchführt (kann unter Verwendung dieser Datensatzaktualisierungssyntax in Instanzen implementiert werden), und das dritte Beispiel verwendet' Control.Lens'. – JJJ

Antwort

3
  1. Der Rekord Update Syntax

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

    funktioniert, wenn <record-instance> ist eine Instanz/Laufzeit eines bekannt algebraischen Datentypen (so dass <record-field-name> ist es bekannt, Feld), ist es in Ihrem Code einfach ist einige (Ad-hoc) polymorphe Parameter gr, so müssen Sie zuerst gr zu Gr konvertieren, dann aktualisieren Sie es, und dann ...

  2. Ich denke, dass gr und Gr in gewisser Hinsicht äquivalent sein sollten, d.h. wir brauchen eine inverse Funktion für repr, sagen wir iface, um add implementieren zu können.Hier

ist ein Beispiel:

{-# 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)]}" 

Wenn einige Datentypen A und BAggregatGr a b (so, dass wir nicht eine inverse für repr schreiben), dann können wir tun Sie etwas wie das:

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

Es ist auch möglich lenses hier zu verwenden:

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

Sie so etwas wie dies ausprobieren können (die Tupel-Liste als Beispiel verwendet anstelle von 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 
Verwandte Themen