说我有一个像这样的数据类型:
data CpuStatRow a = CpuStatRow
{ user :: a
, nice :: a
, system :: a
, idle :: a
, ioWait :: a
, irq :: a
, softIrq :: a
, steal :: a
}
来自解析Linux操作系统/proc/stat的CPU信息,其中每个数字字段都是自启动以来的累积值。因此,如果我想找出某个时期的值,我需要抓住before :: CpuStatRow Int
和after :: CpuStatRow Int
并进行现场差异(假设适当的语言扩展):
-- let's not worry about distinction between a raw value and a difference for now ...
type Diff = Int
getDiff :: CpuStatRow Int -> CpuStatRow Int -> CpuStatRow Diff
getDiff after before = CpuStatRow {..}
where
diffOn prj = prj after - prj before
user = diffOn user
nice = diffOn nice
... -- basically same for each field
我想知道有什么更好的办法:
f :: a -> b -> c -> d
提升到某些f' :: F a -> F b -> F c -> F d
/proc
中有许多累加值,因此应用上述相同方法并不有趣。我有Applicative
的想法,但ghc似乎并不认为它“可推导” 答案 0 :(得分:4)
如果要泛化为任何Arity和任何参数类型的函数,我认为Applicative是最好的选择。确实,您将无法派生它,但是至少可以实现一次pure
和(<*>)
,然后将其重用于所有功能,而不必为每个功能执行繁琐的工作。您也可以要求GHC为您派生Functor实例。
如果要使用“累积值”而不是常规函数,则可以考虑添加以下形式的Monoid(或仅Semigroup)实例
instance Semigroup a => Semigroup (CpuStatRow a) where
(CpuStatRow x y z ...) <> (CpuStatRow x' y' z') = CpuStatRow (x <> x') (y <> y') (z <> z') ...
您可以想象这会很快使人疲倦,但是幸运的是,您可以根据应用程序来实现它-我们已经在节省时间!
instance Semigroup a => Semigroup (CpuStatRow a) where
(<>) = liftA2 (<>)
instance Monoid a => Monoid (CpuStatRow a) where
mempty = pure mempty