2010-10-24 16 views
5

Ich würde gerne sehen, ob es möglich ist, eine Klasse für die Umwandlung einer Sache in eine andere und wieder zurück von einer Zuordnung von [(a,b)] haben.Allgemeine Umwandlung Typ Klasse

sollte diesem Beispiel veranschaulichen, was ich tun möchte:

zu machen, diese Arbeit
data XX = One | Two | Three deriving (Show, Eq) 
data YY = Eno | Owt | Eerht deriving (Show, Eq) 

instance Convert XX YY where 
    mapping = [(One, Eno), (Two, Owt), (Three, Eerht)] 

-- // How can I make this work?: 
main = do print $ (convert One :: YY) -- Want to output: Eno 
      print $ (convert Owt :: XX) -- Want to output: Two 

Hier ist mein Stich:

{-# LANGUAGE MultiParamTypeClasses #-}  
import Data.Maybe(fromJust) 

lk = flip lookup 
flipPair = uncurry $ flip (,) 

class (Eq a, Eq b) => Convert a b where 

    mapping :: [(a, b)] 
    mapping = error "No mapping defined" 

    convert :: a -> b 
    convert = fromJust . lk mapping 

-- // This won't work: 
instance (Convert a b) => Convert b a where 
    convert = fromJust . lk (map flipPair mapping) 

Es ist leicht, das für die mit der Definition zwei Instanzen zu tun Konvertierung in beide Richtungen, aber ich möchte nur eine wie im ersten Beispiel zu deklarieren. Irgendeine Idee, wie ich das machen könnte?

Edit: Durchführbar meine ich, kann dies ohne überlappende Instanzen irgendwelche andere böse Erweiterungen erfolgen?

Antwort

4

Ich, ähm ... Ich hasse es fast, dies vorzuschlagen, weil es irgendwie schrecklich ist, aber ... funktioniert dein Code nicht so wie es ist?

{-# LANGUAGE MultiParamTypeClasses #-} 
{-# LANGUAGE FlexibleInstances #-} 
{-# LANGUAGE UndecidableInstances #-} 
{-# LANGUAGE OverlappingInstances #-} 
import Data.Maybe(fromJust) 

lk x = flip lookup x 
flipPair = uncurry $ flip (,) 

class (Eq a, Eq b) => Convert a b where 
    mapping :: [(a, b)] 
    mapping = error "No mapping defined" 
    convert :: a -> b 
    convert = fromJust . lk mapping 

instance (Convert a b) => Convert b a where 
    convert = fromJust . lk (map flipPair mapping) 

data XX = One | Two | Three deriving (Show, Eq) 
data YY = Eno | Owt | Eerht deriving (Show, Eq) 

instance Convert XX YY where 
    mapping = [(One, Eno), (Two, Owt), (Three, Eerht)] 

main = do print $ (convert One :: YY) 
      print $ (convert Owt :: XX) 

Und:

[1 of 1] Compiling Main    (GeneralConversion.hs, interpreted) 
Ok, modules loaded: Main. 
*Main> main 
Eno 
Two 
*Main> 

Ich bin nicht sicher, wie nützlich eine solche Typklasse ist, und alle Standard-Ausschlussklauseln über zweifelhafte Erweiterungen anwenden, aber das scheint viel zu arbeiten. Jetzt, wenn Sie etwas tun wollen Züchter ... wie Convert a a oder (Convert a b, Convert b c) => Convert a c ... Dinge könnten peinlich werden.


Ich glaube, ich könnte genauso gut ein paar Gedanken über lassen, warum ich die Nützlichkeit dieser Zweifel:

  • Um die Konvertierung zu verwenden, müssen beide Typen eindeutig bekannt sein; Ebenso hängt die Existenz einer Konvertierung von beiden Typen ab. Dies begrenzt, wie nützlich die Klasse für das Schreiben sehr generischen Codes im Vergleich zu Dingen wie fromIntegral sein kann.

  • Die Verwendung von error fehlenden Umwandlungen zu handhaben, mit dem oben kombiniert bedeutet, dass jede angeblich generische Funktion convert verwendet, wird warten eine brodelnde Grube von Laufzeitfehlern wird nur passieren.

  • Um das Ganze abzurunden, ist die generische Instanz, die für das umgekehrte Mapping verwendet wird, tatsächlich eine universelle Instanz, die nur durch überlappende, spezifischere Instanzen verdeckt wird. Das (Convert a b) im Kontext? Dadurch kann das umgekehrte Mapping zwar funktionieren, es wird jedoch nicht darauf beschränkt, nur explizit definierte Instanzen umzukehren.

+0

Der Einfachheit halber könnten Sie alle "LANGUAGE" Pragmas in eine durch Kommas getrennte Liste falten: '{- # LANGUAGE MultiParamTypeClasses, ... # -}'. –

+0

@Antal S-Z: Yeah, die separaten Zeilen Sache ist nur eine Angewohnheit von mir. Ich verwende einen ziemlich einfachen Editor für Haskell und das Hinzufügen/Entfernen von Pragmas, während das Refactoring von Code einfacher ist, wenn sie in sich abgeschlossen sind. –

+0

Das ist fair; Ich hatte nie wirklich mehr als ein oder zwei in einem Projekt, also war das Refactoring kein großes Problem. Ich kann den Vorteil sehen, jetzt, wo ich darüber nachdenke. –