2017-01-07 4 views
3

Angenommen, dass ich einige einfache algebraische Daten (im Wesentlichen enums) und einen anderen Typ, welche diese Aufzählungen als Felder hat.Haskell: Gibt es eine Möglichkeit, einen algebraischen Datentyp zu "mappen"?

data Color = Red | Green | Blue deriving (Eq, Show, Enum, Ord) 
data Width = Thin | Normal | Fat deriving (Eq, Show, Enum, Ord) 
data Height = Short | Medium | Tall deriving (Eq, Show, Enum, Ord) 

data Object = Object { color :: Colour 
        , width :: Width 
        , height :: Height } deriving (Show) 

Angesichts einer Liste von Objekten, möchte ich testen, dass die Attribute alle unterschiedlich sind. Dazu habe ich folgende Funktionen

allDifferent = comparePairwise . sort 
    where comparePairwise xs = and $ zipWith (/=) xs (drop 1 xs) 

uniqueAttributes :: [Object] -> Bool 
uniqueAttributes objects = all [ allDifferent $ map color objects 
           , allDifferent $ map width objects 
           , allDifferent $ map height objects ] 

(sort von Data.List verwenden) Dies funktioniert, aber ist nicht zufrieden stellend, weil ich jedes Feld geben musste (Farbe, Breite, Höhe) manuell. In meinem eigentlichen Code gibt es mehr Felder! Gibt es eine Möglichkeit von ‚Mapping‘ die Funktion

\field -> allDifferent $ map field objects 

über die Felder eines algebraischen Datentypen wie Object? Ich möchte Object als eine Liste der Felder behandeln (etwas, das in zB Javascript leicht sein würde), aber diese Felder haben verschiedene Arten ...

+0

Man könnte Schrott-your-Boilerplate verwenden. Für diesen einfachen Fall bin ich mir nicht sicher, dass es viel besser ist. – chi

+1

Sie könnten etwas ohne Generika einbeziehen: 'uniqueAttributes objects = und [go color, go width, go height] wobei go :: (Ord a) => (Objekt -> a) -> Bool; go f = allDifferent (map f objects) ' –

Antwort

4

Hier wird eine Lösung mit generics-sop:

pointwiseAllDifferent 
    :: (Generic a, Code a ~ '[ xs ], All Ord xs) => [a] -> Bool 
pointwiseAllDifferent = 
    and 
    . hcollapse 
    . hcmap (Proxy :: Proxy Ord) (K . allDifferent) 
    . hunzip 
    . map (unZ . unSOP . from) 

hunzip :: SListI xs => [NP I xs] -> NP [] xs 
hunzip = foldr (hzipWith ((:) . unI)) (hpure []) 

Diese geht davon aus, dass der Typ Object Sie vergleichen möchten ein Record-Typ ist und erfordert, dass Sie diese Art eine Instanz der Klasse Generic, machen die Template Haskell getan werden kann, mit:

deriveGeneric ''Object 

ist zu sehen Lassen Sie versuchen, was geht hier um ein konkretes Beispiel suchen:

objects = [Object Red Thin Short, Object Green Fat Short] 

Die Linie map (unZ . unSOP . from) jedes Object in eine heterogene Liste konvertiert (ein n-stelliges Produkt in der Bibliothek genannt):

GHCi> map (unZ . unSOP . from) objects 
[I Red :* (I Thin :* (I Short :* Nil)),I Green :* (I Fat :* (I Short :* Nil))] 

Die hunzip dann abwechselnd diese Liste der Produkte in ein Produkt, wobei jedes Element eine Liste ist:

GHCi> hunzip it 
[Red,Green] :* ([Thin,Fat] :* ([Short,Short] :* Nil)) 

Jetzt wenden wir allDifferent zu jeder Liste in dem Produkt:

GHCi> hcmap (Proxy :: Proxy Ord) (K . allDifferent) it 
K True :* (K True :* (K False :* Nil)) 

Das Produkt nun in der Tat ist homogen, da jede Position ein Bool enthält, so hcollapse wendet es eine normale homogene Liste in wieder:

GHCi> hcollapse it 
[True,True,False] 

Den letzten Schritt nur gilt and es:

GHCi> and it 
False 
0

Für diese sehr spezifische Situation (eine Reihe von Attributen Überprüfung, die einfache Summe typ sind es mit 0-arity Bauer), können Sie die folgende Konstruktion mit Data.Data Generika verwenden:

{-# LANGUAGE DeriveDataTypeable #-} 

module Signature where 

import Data.List (sort, transpose) 
import Data.Data 

data Color = Red | Green | Blue deriving (Eq, Show, Enum, Ord, Data) 
data Width = Thin | Normal | Fat deriving (Eq, Show, Enum, Ord, Data) 
data Height = Short | Medium | Tall deriving (Eq, Show, Enum, Ord, Data) 

data Object = Object { color :: Color 
        , width :: Width 
        , height :: Height } deriving (Show, Data) 

-- |Signature of attribute constructors used in object 
signature :: Object -> [String] 
signature = gmapQ (show . toConstr) 

uniqueAttributes :: [Object] -> Bool 
uniqueAttributes = all allDifferent . transpose . map signature 

allDifferent :: (Ord a) => [a] -> Bool 
allDifferent = comparePairwise . sort 
    where comparePairwise xs = and $ zipWith (/=) xs (drop 1 xs) 

Der Schlüssel hier ist die Funktion signature, die berechnet Namen jedes Kind den Konstruktor ein Objekt und allgemein über seine unmittelbare Kinder nimmt.Also:

*Signature> signature (Object Red Fat Medium) 
["Red","Fat","Medium"] 
*Signature> 

Wenn es irgendwelche anderen Bereichen als diese einfache Summe Typen (wie sagen ein Attribut vom Typ data Weight = Weight Int oder wenn Sie ein name :: String Feld Object hinzugefügt), dann wird dies plötzlich versagen.

(Edited :) Nachricht hinzuzufügen, die Sie constrIndex . toConstr anstelle von show . toConstr zu verwenden, um einen Int -wertigen Konstruktor Index (im Grunde verwenden können, wird der Index mit 1 des Konstrukteurs in der data Definition beginnen), wenn das fühlt sich weniger indirekt . Wenn die Constr zurückgegeben von toConstr eine Ord Instanz hätte, gäbe es überhaupt keine Umleitung, aber leider ...

+0

Obwohl das Konvertieren von Konstruktoren in Strings und das Vergleichen von ihnen als etwas indirekt erscheint, hat diese Lösung den Vorteil, dass sie relativ einfach ist. Es scheint, dass neuere Herangehensweisen an Generika der letzte Schrei sind, aber 'Data.Data' macht den Job! – yun

+0

Ich habe eine Notiz über die Verwendung von 'constrIndex' hinzugefügt, um stattdessen Int-bewertete Indizes zu erhalten. Dies ist, was ich ursprünglich tat, aber die Verwendung von 'show' ergab einen schöneren (wenn auch weniger direkten und weniger effizienten) Signaturwert. –

Verwandte Themen