2014-04-02 19 views
9

Mam rekord z polami różnych typów i funkcją, która ma zastosowanie do wszystkich tych typów. Jako małe (głupie) Przykład:Systematyczne stosowanie funkcji do wszystkich pól rekordu haskell

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

powiedzieć, że chcemy zdefiniować funkcję, która dodaje dwa rekordy na pola:

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

Czy istnieje sposób, aby wyrazić to bez powtarzania operacji dla każde pole (może być wiele pól w rekordzie)?

W rzeczywistości mam zapis zawierający wyłącznie pola Maybe i chcę połączyć rzeczywiste dane z rekordem zawierającym wartości domyślne dla niektórych pól, które będą używane, gdy rzeczywiste dane to Nothing.

(chyba powinno być możliwe z szablonu Haskell, ale jestem bardziej zainteresowany w „Portable” realizacji.)

Odpowiedz

5

Można użyć gzipWithT do tego.

Nie jestem ekspertem, więc moja wersja jest trochę głupia. Powinno być możliwe wywołanie gzipWithT tylko raz, np. przy użyciu extQ i extT, ale nie udało mi się znaleźć sposobu, aby to zrobić. Tak czy inaczej, oto moja wersja:

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

wyjściowa:

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

Kod jest niejasny, ale idea jest prosta, gzipWithT zastosowanie określonej funkcji rodzajowe (mergeInt, mergeString, etc) do pary odpowiednie pola.

2

Nie sądzę, istnieje jakikolwiek sposób, aby to zrobić, aby uzyskać wartości z w polach należy podać ich nazwy lub dopasowanie do wzorca - i podobnie ustawić pola, podać ich nazwy lub użyć zwykłej składni konstruktora, aby ustawić je - tam, gdzie liczy się kolejność składni.

Może lekkie uproszczenie byłoby użyć zwykłej składni konstruktora i dodać zamknięcie dla funkcjonowania

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

doAdd ma typ (Num a) => (Rec -> a) -> a. Dodatkowo, jeśli planujesz wykonać więcej niż jedną operację na płycie - na przykład: subR, która robi prawie to samo, ale odejmuje - możesz oddzielić zachowanie do funkcji, używając RankNTypes.

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

Jestem obecnie robi to 'zamknięcie' rzeczy; wciąż jest dużo duplikacji. – crosser

5

Jeszcze innym sposobem jest użycie GHC.Generics:

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

z vinyl (pakiet "rozsuwane zapisy"):

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

type Nums = Rec Identity [Float, Int] 

co jest równoważne

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

który jest samo w sobie jest równoważne z

data Nums'' = Nums'' Float Int 

następnie addR jest po prostu

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

a jeśli dodać nowe pole

type Nums = Rec Identity [Float, Int, Word] 

nie trzeba dotykać addR.

btw, recAdd jest łatwo zdefiniować siebie, jeśli chcesz, aby "podnieść" swoje własne operacje numeryczne, to tylko

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

Dla wygody, można zdefiniować własny konstruktor:

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

a nawet wzór dla obu wartości konstruowania i dopasowanie:

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

Zastosowanie:

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 

Od r3 jest wywnioskować jak

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

można (bezpiecznie) uskok go

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

następnie specjalizować go

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