2010-04-13 11 views
13

Werfen wir einen Datentyp mit vielen Konstrukteuren betrachten:„Pattern Matching“ von algebraischen Typ Daten Konstrukteurs

data T = Alpha Int | Beta Int | Gamma Int Int | Delta Int 

Ich möchte eine Funktion schreiben, zu überprüfen, ob zwei Werte mit dem gleichen Konstruktor erzeugt werden:

sameK (Alpha _) (Alpha _) = True 
sameK (Beta _) (Beta _) = True 
sameK (Gamma _ _) (Gamma _ _) = True 
sameK _ _ = False 

Wartung sameK macht nicht viel Spaß, es kann nicht leicht auf Korrektheit überprüft werden. Wenn zum Beispiel neue Konstruktoren zu T hinzugefügt werden, ist es leicht zu vergessen, sameK zu aktualisieren. Ich weggelassen eine Zeile ein Beispiel zu geben:

-- it’s easy to forget: 
-- sameK (Delta _) (Delta _) = True 

Die Frage ist, wie Textvorschlag zu vermeiden, in sameK? Oder wie stellt man sicher, dass es nach allen T Konstruktoren sucht?


Die Abhilfe, die ich gefunden ist für jeden des Konstrukteurs separate Datentypen zu verwenden, Data.Typeable abzuleiten, und eine gemeinsame Typklasse erklärt, aber ich weiß nicht, diese Lösung gefällt, weil es viel weniger lesbar ist und ansonsten nur eine einfache algebraische Art funktioniert für mich:

{-# LANGUAGE DeriveDataTypeable #-} 

import Data.Typeable 

class Tlike t where 
    value :: t -> t 
    value = id 

data Alpha = Alpha Int deriving Typeable 
data Beta = Beta Int deriving Typeable 
data Gamma = Gamma Int Int deriving Typeable 
data Delta = Delta Int deriving Typeable 

instance Tlike Alpha 
instance Tlike Beta 
instance Tlike Gamma 
instance Tlike Delta 

sameK :: (Tlike t, Typeable t, Tlike t', Typeable t') => t -> t' -> Bool 
sameK a b = typeOf a == typeOf b 
+0

Vielen Dank für Ihre Antwort. Alle deine Antworten waren nützlich. – sastanin

Antwort

10

Sehen Sie sich das Data.Data-Modul an, insbesondere die Funktion toConstr. Zusammen mit {-# LANGUAGE DeriveDataTypeable #-} erhalten Sie eine 1-Linien-Lösung, die für jeden Typ funktioniert, der eine Instanz von Data.Data ist. Sie müssen nicht alle SYB herausfinden!

Wenn aus irgendeinem Grund (mit Hugs stecken?), Das ist keine Option, dann ist hier ein sehr hässlich und sehr langsam Hack. Es funktioniert nur, wenn Ihr Datentyp fähig ist (z. B. mit deriving (Show) - was bedeutet, dass z. B. keine Funktionstypen vorhanden sind).

constrT :: T -> String 
constrT = head . words . show 
sameK x y = constrT x == constrT y 

constrT erhält die Stringdarstellung des äußersten Konstruktor eines T Wert durch Zeigen, Zerhacken es in Worte und dann die ersten bekommen. Ich gebe eine explizite Typ-Signatur, so dass Sie nicht versucht sind, sie für andere Typen zu verwenden (und die Monomorphie-Einschränkung zu umgehen).

Einige bemerkenswerte Nachteile:

  • Diese schrecklich bricht, wenn Ihre Art Infix Konstrukteuren (wie data T2 = Eta Int | T2 :^: T2)
  • Wenn einige Ihrer Konstrukteure haben ein gemeinsames Präfix hat, das langsamer erhalten wird, da ein größerer Teil der Saiten verglichen werden muss.
  • Funktioniert nicht bei Typen mit einer benutzerdefinierten show, wie viele Bibliothekstypen.

Das heißt, es Haskell ist 98 ... aber das ist auch schon das einzig gute, was ich darüber sagen kann!

+1

+1 für' toConstr' –

+1

Wow! Es ist einfach magisch. Danke vielmals! – sastanin

14

Eine andere Möglichkeit:

sameK x y = f x == f y 
    where f (Alpha _) = 0 
     f (Beta _) = 1 
     f (Gamma _ _) = 2 
     -- runtime error when Delta value encountered 

Ein Laufzeitfehler ist nicht ideal, aber besser als leise die falsche Antwort zu geben.

+3

Ich mag es. GHC mit '-Wall' meldet in der Definition von' f 'nicht-exaustische Musterübereinstimmungen, so dass Laufzeitfehler vermieden werden können. Danke für die Idee. – sastanin

10

Sie müssen eine Generika-Bibliothek wie Scrap Your Boilerplate oder Uniplate verwenden, um dies im Allgemeinen zu tun.

Wenn Sie nicht wollen, so plump sein, Sie Dave Hinton-Lösung nutzen können, zusammen mit der leeren Datensatz Abkürzung

... 
where f (Alpha {}) = 0 
     f (Beta {}) = 1 
     f (Gamma {}) = 2 

So müssen Sie nicht wissen, wie viele args je Konstruktor hat. Aber es lässt offensichtlich noch etwas zu wünschen übrig.

+2

Schöner Trick mit '{}'. Ich wusste es nicht. Vielen Dank. – sastanin

+2

Record Klammern binden stärker als alles andere, so dass Sie technisch die Klammern nicht benötigen. 'f Alpha {} = 0 'wird gut funktionieren, obwohl ich mir nicht sicher bin, wie lesbar das ist, es sieht so aus, als hätte' f' zwei Argumente. Ich betare mich manchmal mit 'f Alpha {} = 0' ... –

1

Sie können definitiv die Generika verwenden, um den Textbaustein zu beseitigen. Ihr Code ist ein Lehrbuch Beispiel, warum ich (und viele andere nie den _ Wildcard auf oberster Ebene verwenden). Während es mühsam ist, alle Fälle aufzuschreiben, ist es weniger mühsam als mit den Fehlern fertig zu werden.

In diesem glücklichen Beispiel würde ich nicht nur Dave Hintons Lösung verwenden, sondern würde ein INLINE-Pragma auf die Hilfsfunktion f schlagen.

+0

Ja, darum frage ich. Danke für Deinen Ratschlag. – sastanin