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,但我对"便携式"实现更感兴趣.)
另一种方法是使用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)
你可以使用gzipWithT.
我不是专家,所以我的版本有点傻.应该可以gzipWithT只调用一次,例如使用extQ和extT,但我没有找到这样做的方法.无论如何,这是我的版本:
{-# 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,等等)配对的相应字段的.