系统地将函数应用于haskell记录的所有字段

cro*_*ser 9 haskell record data-structures

我有一个包含不同类型字段的记录,以及一个适用于所有这些类型的函数.作为一个小(愚蠢)的例子:

data Rec = Rec  { flnum :: Float, intnum :: Int } deriving (Show)
Run Code Online (Sandbox Code Playgroud)

说,我想定义一个每个字段添加两个记录的函数:

addR :: Rec -> Rec -> Rec
addR a b = Rec { flnum = (flnum a) + (flnum b), intnum = (intnum a) + (intnum b) }
Run Code Online (Sandbox Code Playgroud)

有没有办法表达这一点而不重复每个字段的操作(记录中可能有很多字段)?

实际上,我有一个仅由Maybe字段组成的记录,我想将实际数据与包含某些字段的默认值的记录结合起来,以便在实际数据时使用Nothing.

(我想应该可以使用模板haskell,但我对"便携式"实现更感兴趣.)

Ed'*_*'ka 7

另一种方法是使用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
Run Code Online (Sandbox Code Playgroud)


Yur*_*ras 5

你可以使用gzipWithT.

我不是专家,所以我的版本有点傻.应该可以gzipWithT只调用一次,例如使用extQextT,但我没有找到这样做的方法.无论如何,这是我的版本:

{-# 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
Run Code Online (Sandbox Code Playgroud)

输出:

Test {test1 = 4, test2 = 3.3000002, test3 = 6, test4 = "t1t2", test5 = "t11t22"}
Run Code Online (Sandbox Code Playgroud)

该代码是模糊,但这个想法是简单,gzipWithT应用指定的通用函数(mergeInt,mergeString,等等)配对的相应字段的.