基于枚举类型概括数据字段的正确方法
Proper way to generalize data fields based on enumerated type
使用一个固定的结构我们可以写
data Stats = Stats { lines :: !Int, words :: !Int }
instance Num Stats where
fromInteger x = Stats x x
(Stats a b) + (Stats a' b') = Stats (a + a') (b + b')
我们可以创建一些动态结构来实现通用版本
newtype Stats a = Stats { unStats :: [Int] } -- or Map, Vector, ...
instance forall a . (Enum a, Bounded a) => Num (Stats a) where
fromInteger = Stats . replicate sz
where sz = fromEnum (maxBound::a) - fromEnum (minBound::a) + 1
(Stats a) + (Stats b) = Stats $ zipWith (+) a b
(¨) :: forall a . (Eq a, Enum a, Bounded a) => Int -> a -> Stats a
x ¨ u = Stats $ map (\k -> if k == u then x else 0) [minBound .. maxBound :: a]
并用作
data TextStats = Lines | Words deriving (Eq, Ord, Enum, Bounded)
someTextStats :: Stats TextStats
someTextStats = 1 ¨Lines + 5 ¨Words
前一种方式是静态的(例如,单位测量函数是静态的)但后者不是在运行时遍历定义的结构的意义上。
与模板 Haskell 存在某种差异?谢谢!
如果您使用 RankNTypes
、ScopedVariables
并且您不尝试使用双引号作为运算符名称,则您的方法有效:
{-# LANGUAGE RankNTypes, ScopedTypeVariables #-}
newtype Stats a = Stats { unStats :: [Integer] } -- or Map, Vector, ...
deriving (Show)
instance forall a . (Enum a, Bounded a) => Num (Stats a) where
fromInteger = Stats . replicate sz
where sz = fromEnum (maxBound::a) - fromEnum (minBound::a) + 1
(Stats a) + (Stats b) = Stats $ zipWith (+) a b
(€) :: forall a . (Eq a, Enum a, Bounded a) => Integer -> a -> Stats a
x € u = Stats $ map (\k -> if k == u then x else 0) [minBound .. maxBound :: a]
data TextStats = Lines | Words deriving (Eq, Ord, Enum, Bounded)
test = 3 € Lines + 5 € Words
我还更改了列表以包含整数而不是整数。这是您要找的吗?
正如我五年前所问的那样,我一直在寻找一种方法让聚合复合函数在编译时完全形成,而不是在 运行 时迭代。
一种方法可以是:
(一些序言)
{-# LANGUAGE MultiParamTypeClasses
, FlexibleInstances
, FunctionalDependencies
, ExistentialQuantification #-}
import Text.Printf
累加器是为每个交付的新值更新的值
class Accumulator a v | a -> v where
accumulate :: a -> v -> a
因为我们要组成多个累加器,所以定义一个终止符似乎很有用
data Nop v = Nop
instance Accumulator (Nop v) v where
accumulate _ _ = Nop
任何累加器都将包含一个投影和一个聚合函数。那么为了方便我们可以定义
data Prj a v n = Accumulator a v =>
Prj { prjValue :: n
, prjAcc :: n -> n -> n
, prjPrj :: v -> n
, prjNext :: a
}
instance Accumulator a v => Accumulator (Prj a v n) v where
accumulate (Prj n acc prj a) v = Prj (acc n (prj v)) acc prj $ accumulate a v
现在,作为示例,我们可以定义以下通用累加器
countAcc :: Accumulator a v => a -> Prj a v Int
countAcc = Prj 0 (+) (const 1)
minAcc, maxAcc :: (Bounded n, Ord n, Accumulator a v) => (v -> n) -> a -> Prj a v n
minAcc = Prj maxBound min
maxAcc = Prj minBound max
sumAcc, sum2Acc :: (Num n, Accumulator a v) => (v -> n) -> a -> Prj a v n
sumAcc = Prj 0 (+)
sum2Acc = Prj 0 (\u u' -> u + u' * u')
例如,假设我们有一个仓库
data Item = Item { itemName :: String
, itemStock :: Int
, itemWeight :: Double
}
有以下数据
warehouse = [ Item "apple" 12 0.1
, Item "orange" 21 0.12
, Item "melon" 9 2.23
]
我们可以定义以下统计信息(这将是一个编译函数,即内部操作可以优化,因为它不会迭代)
myStats = countAcc
$ minAcc itemStock $ maxAcc itemStock
$ sumAcc itemStock $ sum2Acc itemStock
$ sumAcc itemWeight $ Nop
然后提取我们数据的具体值(可以定义更方便的模式匹配投影)
Prj items _ _ (
Prj minItems _ _ (Prj maxItems _ _ (
Prj sumItems _ _ (Prj sum2Items _ _ (
Prj sumWeights _ _ _))))) =
foldr (flip accumulate) myStats warehouse
和运行宁
main = do
let avg, std :: Double
avg = fromIntegral sumItems / fromIntegral items
std = sqrt ( fromIntegral sum2Items / fromIntegral items + avg**2 )
printf (unlines [ "%i item types"
, "min availability is %i"
, "max availability is %i"
, "there are a total of %i items"
, "average availability is %0.2f +/- %0.2f items"
, "the total weight of the goods is %0.2f" ])
items minItems maxItems sumItems avg std sumWeights
我们得到
3 item types
min availability is 9
max availability is 21
there are a total of 42 items
average availability is 14.00 +/- 5.10 items
the total weight of the goods is 2.45
(撇开,更好的表示法)
添加 UndecidableInstances 和 TypeOperators 我们可以编写一个类型运算符来连接报告结果
data r :% r' = r :% r'
infixr 7 :%
class Values a r | a -> r where
values :: a -> r
instance Values a n' => Values (Prj a v n) (n :% n') where
values (Prj n _ _ a) = n :% values a
instance Values (Nop v) () where
values _ = ()
现在,我们可以按照定义的方式编写预期的聚合值
items :% minItems :% maxItems :% sumItems :% sum2Items :% sumWeights :% () =
values $ foldr (flip accumulate) myStats warehouse
使用一个固定的结构我们可以写
data Stats = Stats { lines :: !Int, words :: !Int }
instance Num Stats where
fromInteger x = Stats x x
(Stats a b) + (Stats a' b') = Stats (a + a') (b + b')
我们可以创建一些动态结构来实现通用版本
newtype Stats a = Stats { unStats :: [Int] } -- or Map, Vector, ...
instance forall a . (Enum a, Bounded a) => Num (Stats a) where
fromInteger = Stats . replicate sz
where sz = fromEnum (maxBound::a) - fromEnum (minBound::a) + 1
(Stats a) + (Stats b) = Stats $ zipWith (+) a b
(¨) :: forall a . (Eq a, Enum a, Bounded a) => Int -> a -> Stats a
x ¨ u = Stats $ map (\k -> if k == u then x else 0) [minBound .. maxBound :: a]
并用作
data TextStats = Lines | Words deriving (Eq, Ord, Enum, Bounded)
someTextStats :: Stats TextStats
someTextStats = 1 ¨Lines + 5 ¨Words
前一种方式是静态的(例如,单位测量函数是静态的)但后者不是在运行时遍历定义的结构的意义上。
与模板 Haskell 存在某种差异?谢谢!
如果您使用 RankNTypes
、ScopedVariables
并且您不尝试使用双引号作为运算符名称,则您的方法有效:
{-# LANGUAGE RankNTypes, ScopedTypeVariables #-}
newtype Stats a = Stats { unStats :: [Integer] } -- or Map, Vector, ...
deriving (Show)
instance forall a . (Enum a, Bounded a) => Num (Stats a) where
fromInteger = Stats . replicate sz
where sz = fromEnum (maxBound::a) - fromEnum (minBound::a) + 1
(Stats a) + (Stats b) = Stats $ zipWith (+) a b
(€) :: forall a . (Eq a, Enum a, Bounded a) => Integer -> a -> Stats a
x € u = Stats $ map (\k -> if k == u then x else 0) [minBound .. maxBound :: a]
data TextStats = Lines | Words deriving (Eq, Ord, Enum, Bounded)
test = 3 € Lines + 5 € Words
我还更改了列表以包含整数而不是整数。这是您要找的吗?
正如我五年前所问的那样,我一直在寻找一种方法让聚合复合函数在编译时完全形成,而不是在 运行 时迭代。
一种方法可以是:
(一些序言)
{-# LANGUAGE MultiParamTypeClasses
, FlexibleInstances
, FunctionalDependencies
, ExistentialQuantification #-}
import Text.Printf
累加器是为每个交付的新值更新的值
class Accumulator a v | a -> v where
accumulate :: a -> v -> a
因为我们要组成多个累加器,所以定义一个终止符似乎很有用
data Nop v = Nop
instance Accumulator (Nop v) v where
accumulate _ _ = Nop
任何累加器都将包含一个投影和一个聚合函数。那么为了方便我们可以定义
data Prj a v n = Accumulator a v =>
Prj { prjValue :: n
, prjAcc :: n -> n -> n
, prjPrj :: v -> n
, prjNext :: a
}
instance Accumulator a v => Accumulator (Prj a v n) v where
accumulate (Prj n acc prj a) v = Prj (acc n (prj v)) acc prj $ accumulate a v
现在,作为示例,我们可以定义以下通用累加器
countAcc :: Accumulator a v => a -> Prj a v Int
countAcc = Prj 0 (+) (const 1)
minAcc, maxAcc :: (Bounded n, Ord n, Accumulator a v) => (v -> n) -> a -> Prj a v n
minAcc = Prj maxBound min
maxAcc = Prj minBound max
sumAcc, sum2Acc :: (Num n, Accumulator a v) => (v -> n) -> a -> Prj a v n
sumAcc = Prj 0 (+)
sum2Acc = Prj 0 (\u u' -> u + u' * u')
例如,假设我们有一个仓库
data Item = Item { itemName :: String
, itemStock :: Int
, itemWeight :: Double
}
有以下数据
warehouse = [ Item "apple" 12 0.1
, Item "orange" 21 0.12
, Item "melon" 9 2.23
]
我们可以定义以下统计信息(这将是一个编译函数,即内部操作可以优化,因为它不会迭代)
myStats = countAcc
$ minAcc itemStock $ maxAcc itemStock
$ sumAcc itemStock $ sum2Acc itemStock
$ sumAcc itemWeight $ Nop
然后提取我们数据的具体值(可以定义更方便的模式匹配投影)
Prj items _ _ (
Prj minItems _ _ (Prj maxItems _ _ (
Prj sumItems _ _ (Prj sum2Items _ _ (
Prj sumWeights _ _ _))))) =
foldr (flip accumulate) myStats warehouse
和运行宁
main = do
let avg, std :: Double
avg = fromIntegral sumItems / fromIntegral items
std = sqrt ( fromIntegral sum2Items / fromIntegral items + avg**2 )
printf (unlines [ "%i item types"
, "min availability is %i"
, "max availability is %i"
, "there are a total of %i items"
, "average availability is %0.2f +/- %0.2f items"
, "the total weight of the goods is %0.2f" ])
items minItems maxItems sumItems avg std sumWeights
我们得到
3 item types
min availability is 9
max availability is 21
there are a total of 42 items
average availability is 14.00 +/- 5.10 items
the total weight of the goods is 2.45
(撇开,更好的表示法)
添加 UndecidableInstances 和 TypeOperators 我们可以编写一个类型运算符来连接报告结果
data r :% r' = r :% r'
infixr 7 :%
class Values a r | a -> r where
values :: a -> r
instance Values a n' => Values (Prj a v n) (n :% n') where
values (Prj n _ _ a) = n :% values a
instance Values (Nop v) () where
values _ = ()
现在,我们可以按照定义的方式编写预期的聚合值
items :% minItems :% maxItems :% sumItems :% sum2Items :% sumWeights :% () =
values $ foldr (flip accumulate) myStats warehouse