2014-04-02 8 views
9

Ich habe einen Datensatz mit Feldern unterschiedlicher Typen und eine Funktion, die für alle diese Typen gilt. Als kleines (albern) Beispiel:Systematische Anwendung einer Funktion auf alle Felder eines Haskell Datensatzes

data Rec = Rec { flnum :: Float, intnum :: Int } deriving (Show) 

Sprich, ich mag eine Funktion definieren, die zwei Datensätze pro-Feld fügt hinzu:

addR :: Rec -> Rec -> Rec 
addR a b = Rec { flnum = (flnum a) + (flnum b), intnum = (intnum a) + (intnum b) } 

Gibt es eine Möglichkeit, dies zum Ausdruck zu bringen, ohne den Betrieb zu wiederholen für jedes Feld (es kann viele Felder im Datensatz geben)?

In Wirklichkeit habe ich einen Datensatz ausschließlich aus Maybe Felder besteht, und ich möchte die tatsächlichen Daten mit einem Datensatz mit Standardwerten für einige der Felder kombinieren, verwendet werden, wenn die tatsächlichen Daten Nothing war.

(Ich denke, es mit Vorlage Haskell möglich sein soll, aber ich bin mehr daran interessiert, in einer „tragbaren“ Implementierung.)

Antwort

5

Sie können gzipWithT dafür verwenden.

Ich bin kein Experte, also meine Version es ein bisschen albern. Es sollte möglich sein, gzipWithT nur einmal anzurufen, z. mit extQ und extT, aber ich habe den Weg nicht gefunden, um das zu tun. Wie auch immer, hier ist meine Version:

{-# LANGUAGE DeriveDataTypeable #-} 

import Data.Generics 

data Test = Test { 
    test1 :: Int, 
    test2 :: Float, 
    test3 :: Int, 
    test4 :: String, 
    test5 :: String 
    } 
    deriving (Typeable, Data, Eq, Show) 

t1 :: Test 
t1 = Test 1 1.1 2 "t1" "t11" 

t2 :: Test 
t2 = Test 3 2.2 4 "t2" "t22" 

merge :: Test -> Test -> Test 
merge a b = let b' = gzipWithT mergeFloat a b 
       b'' = gzipWithT mergeInt a b' 
      in gzipWithT mergeString a b'' 

mergeInt :: (Data a, Data b) => a -> b -> b 
mergeInt = mkQ (mkT (id :: Int -> Int)) (\a -> mkT (\b -> a + b :: Int)) 

mergeFloat :: (Data a, Data b) => a -> b -> b 
mergeFloat = mkQ (mkT (id :: Float -> Float)) (\a -> mkT (\b -> a + b :: Float)) 

mergeString :: (Data a, Data b) => a -> b -> b 
mergeString = mkQ (mkT (id :: String -> String)) (\a -> mkT (\b -> a ++ b :: String)) 

main :: IO() 
main = print $ merge t1 t2 

Ausgang:

Test {test1 = 4, test2 = 3.3000002, test3 = 6, test4 = "t1t2", test5 = "t11t22"} 

Der Code ist unklar, aber die Idee ist einfach, gzipWithT die angegebene generische Funktion gilt (mergeInt, mergeString, usw.) zu paaren von entsprechende Felder.

2

Ich glaube nicht, dass es eine Möglichkeit, dies zu tun ist, wie die Werte von erhalten In den Feldern müssen Sie ihre Namen angeben, oder sie müssen mit dem Muster übereinstimmen - und ähnlich wie die Felder festlegen, geben Sie ihre Namen an oder verwenden Sie die reguläre Konstruktorsyntax, um sie festzulegen - wo die Reihenfolge der Syntax wichtig ist.

Vielleicht eine leichte Vereinfachung wäre die regelmäßige Konstruktor Syntax zu verwenden und einen Verschluss für den Betrieb

addR' :: Rec -> Rec -> Rec 
addR' a b = Rec (doAdd flnum) (doAdd intnum) 
    where doAdd f = (f a) + (f b) 

doAdd hat den Typ (Num a) => (Rec -> a) -> a hinzufügen.

Wenn Sie mehr als eine Operation für den Datensatz ausführen möchten, z. B. subR, die fast dasselbe tut, aber subtrahiert, können Sie das Verhalten mithilfe von RankNTypes in eine Funktion abstrahieren.

{-# LANGUAGE RankNTypes #-} 

data Rec = Rec { flnum :: Float, intnum :: Int } deriving (Show) 

opRecFields :: (forall a. (Num a) => a -> a -> a) -> Rec -> Rec -> Rec 
opRecFields op a b = Rec (performOp flnum) (performOp intnum) 
    where performOp f = (f a) `op` (f b) 

addR = opRecFields (+) 

subR = opRecFields (-) 
+0

ich zur Zeit machte diese 'Schließung' Sache; es ist immer noch eine Menge Doppelarbeit. – crosser

5

Noch eine andere Art und Weise GHC.Generics verwenden:

{-# LANGUAGE FlexibleInstances, FlexibleContexts, 
UndecidableInstances, DeriveGeneric, TypeOperators #-} 

import GHC.Generics 


class AddR a where 
    addR :: a -> a -> a 

instance (Generic a, GAddR (Rep a)) => AddR a where 
    addR a b = to (from a `gaddR` from b) 


class GAddR f where 
    gaddR :: f a -> f a -> f a 

instance GAddR a => GAddR (M1 i c a) where 
    M1 a `gaddR` M1 b = M1 (a `gaddR` b) 

instance (GAddR a, GAddR b) => GAddR (a :*: b) where 
    (al :*: bl) `gaddR` (ar :*: br) = gaddR al ar :*: gaddR bl br 

instance Num a => GAddR (K1 i a) where 
    K1 a `gaddR` K1 b = K1 (a + b) 


-- Usage 
data Rec = Rec { flnum :: Float, intnum :: Int } deriving (Show, Generic) 

t1 = Rec 1.0 2 `addR` Rec 3.0 4 
2

mit vinyl (ein "extensible records" Paket):

import Data.Vinyl 
-- `vinyl` exports `Rec` 

type Nums = Rec Identity [Float, Int] 

die zu

entspricht
data Nums' = Nums' (Identity Float) (Identity Int) 

welches selbst entspricht

data Nums'' = Nums'' Float Int 

dann ist addR einfach

-- vinyl defines `recAdd` 
addR :: Nums -> Nums -> Nums 
addR = recAdd 

und wenn Sie ein neues Feld

hinzufügen
type Nums = Rec Identity [Float, Int, Word] 

Sie nicht brauchen addR zu berühren.

btw, recAdd leicht ist, sich selbst zu definieren, wenn Sie wollen „heben“ Ihre eigenen numerischen Operationen, es ist nur

-- the `RecAll f rs Num` constraint means "each field satisfies `Num`" 
recAdd :: RecAll f rs Num => Rec f rs -> Rec f rs -> Rec f rs 
recAdd RNil RNil = RNil 
recAdd (a :& as) (b :& bs) = (a + b) :& recAdd as bs 

Der Einfachheit halber können Sie Ihre eigenen Konstruktor definieren:

nums :: Float -> Int -> Num 
nums a b = Identity a :& Identity b :& RNil 

und sogar ein Muster für beide Bau und passende Werte:

-- with `-XPatternSynonyms` 
pattern Nums :: Float -> Int -> Num 
pattern Nums a b = Identity a :& Identity b :& RNil 

Nutzung:

main = do 
let r1 = nums 1 2 
let r2 = nums 3 4 
print $ r1 `addR` r2 

let (Nums a1 _) = r1 
print $ a1 

let r3 = i 5 :& i 6 :& i 7 :& z -- inferred 
print $ r1 `addR` (rcast r3) -- drop the last field 

Seit r3 als

(Num a, Num b, Num c) => Rec Identity [a, b, c] 

Sie können (sicher) upcast es

rcast r3 :: (Num a, Num b) => Rec Identity [a, b] 

geschlossen wird spezialisieren Sie dann

rcast r3 :: Nums 

https://hackage.haskell.org/package/vinyl-0.5.2/docs/Data-Vinyl-Class-Method.html#v:recAdd

https://hackage.haskell.org/package/vinyl-0.5.2/docs/Data-Vinyl-Tutorial-Overview.html

Verwandte Themen