系统地将函数应用于haskell记录的所有字段
我有一个包含不同类型字段的记录,以及一个适用于所有这些类型的函数。举个小(愚蠢)的例子: 比方说,我想定义一个函数,每个字段添加两条记录:系统地将函数应用于haskell记录的所有字段,haskell,data-structures,record,Haskell,Data Structures,Record,我有一个包含不同类型字段的记录,以及一个适用于所有这些类型的函数。举个小(愚蠢)的例子: 比方说,我想定义一个函数,每个字段添加两条记录: addR :: Rec -> Rec -> Rec addR a b = Rec { flnum = (flnum a) + (flnum b), intnum = (intnum a) + (intnum b) } 是否有一种方法可以表达这一点,而不必对每个字段重复操作(记录中可能有许多字段) 实际上,我有一个专门由可能字段组成的记录,我想将
addR :: Rec -> Rec -> Rec
addR a b = Rec { flnum = (flnum a) + (flnum b), intnum = (intnum a) + (intnum b) }
是否有一种方法可以表达这一点,而不必对每个字段重复操作(记录中可能有许多字段)
实际上,我有一个专门由可能
字段组成的记录,我想将实际数据与包含某些字段默认值的记录相结合,以便在实际数据为无
时使用
(我想使用template haskell应该是可能的,但我更感兴趣的是“可移植”的实现。)我认为没有任何方法可以做到这一点,要从字段中获取值,您需要指定字段名称,或在字段上进行模式匹配-同样,要设置字段,您需要指定字段名称,或者使用常规构造函数语法来设置它们-语法顺序很重要 也许稍微简化一下,可以使用常规构造函数语法并为操作添加闭包
addR' :: Rec -> Rec -> Rec
addR' a b = Rec (doAdd flnum) (doAdd intnum)
where doAdd f = (f a) + (f b)
doAdd
的类型为(Num a)=>(Rec->a)->a
此外,如果您计划对记录执行多个操作(例如,subR
,其执行的操作几乎相同,但会进行减法),则可以使用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 (-)
你可以用它
我不是专家,所以我的版本有点傻。应该可以只调用一次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
输出:
Test {test1 = 4, test2 = 3.3000002, test3 = 6, test4 = "t1t2", test5 = "t11t22"}
代码是模糊的,但想法很简单,gzipWithT
将指定的通用函数(mergeInt
,mergeString
,等等)应用于一对相应的字段。另一种方法是使用:
使用乙烯基
(一个“可扩展记录”包):
这相当于
data Nums' = Nums' (Identity Float) (Identity Int)
data Nums'' = Nums'' Float Int
这本身就相当于
data Nums' = Nums' (Identity Float) (Identity Int)
data Nums'' = Nums'' Float Int
然后,addR
就是
-- vinyl defines `recAdd`
addR :: Nums -> Nums -> Nums
addR = recAdd
如果您添加了一个新字段
type Nums = Rec Identity [Float, Int, Word]
您不需要触摸addR
顺便说一句,recAdd
很容易定义您自己,如果您想“提升”您自己的自定义数字操作,只需
-- 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
为方便起见,您可以定义自己的构造函数:
nums :: Float -> Int -> Num
nums a b = Identity a :& Identity b :& RNil
甚至还有一种构造和匹配值的模式:
-- with `-XPatternSynonyms`
pattern Nums :: Float -> Int -> Num
pattern Nums a b = Identity a :& Identity b :& RNil
用法:
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
因为r3
被推断为
(Num a, Num b, Num c) => Rec Identity [a, b, c]
你可以(安全地)把它向上抛到
rcast r3 :: (Num a, Num b) => Rec Identity [a, b]
然后你把它专门化
rcast r3 :: Nums
我目前正在做这个“关闭”的事情;仍然有很多重复。
rcast r3 :: Nums