Haskell 哈斯克尔:有没有一种';映射';在代数数据类型上?
假设我有一些简单的代数数据(本质上是枚举)和另一种类型,它将这些枚举作为字段Haskell 哈斯克尔:有没有一种';映射';在代数数据类型上?,haskell,records,algebraic-data-types,Haskell,Records,Algebraic Data Types,假设我有一些简单的代数数据(本质上是枚举)和另一种类型,它将这些枚举作为字段 data Color = Red | Green | Blue deriving (Eq, Show, Enum, Ord) data Width = Thin | Normal | Fat deriving (Eq, Show, Enum, Ord) data Height = Short | Medium | Tall deriving (Eq, Show, Enum, Ord) data Obje
data Color = Red | Green | Blue deriving (Eq, Show, Enum, Ord)
data Width = Thin | Normal | Fat deriving (Eq, Show, Enum, Ord)
data Height = Short | Medium | Tall deriving (Eq, Show, Enum, Ord)
data Object = Object { color :: Colour
, width :: Width
, height :: Height } deriving (Show)
给定一个对象列表,我想测试属性是否都是不同的。为此,我有以下功能(使用数据列表中的排序)
这是可行的,但相当令人不满意,因为我必须手动键入每个字段(颜色、宽度、高度)。在我的实际代码中,有更多的字段!是否有“映射”函数的方法
\field -> allDifferent $ map field objects
在代数数据类型的字段上,如对象
?我想将对象
视为其字段列表(在javascript中很容易),但这些字段有不同的类型…以下是使用以下方法的解决方案:
这假定要比较的类型对象
是记录类型,并要求将此类型设置为类泛型
的实例,这可以使用Template Haskell完成:
deriveGeneric ''Object
让我们看一个具体的例子,看看这里发生了什么:
objects = [Object Red Thin Short, Object Green Fat Short]
行映射(unZ.unSOP.from)
将每个对象
转换为异构列表(称为库中的n元乘积):
然后,hunzip
将此产品列表转换为一个产品,其中每个元素都是一个列表:
GHCi> hunzip it
[Red,Green] :* ([Thin,Fat] :* ([Short,Short] :* Nil))
现在,我们对产品中的每个列表应用allDifferent
:
GHCi> hcmap (Proxy :: Proxy Ord) (K . allDifferent) it
K True :* (K True :* (K False :* Nil))
该产品现在实际上是同质的,因为每个位置都包含一个Bool
,所以hcollapse
再次将其转换为一个普通的同质列表:
GHCi> hcollapse it
[True,True,False]
最后一步仅对其应用和:
GHCi> and it
False
对于这种非常特殊的情况(使用0-arity构造函数检查一组简单求和类型的属性),可以使用Data.Data
generics使用以下构造:
{-# LANGUAGE DeriveDataTypeable #-}
module Signature where
import Data.List (sort, transpose)
import Data.Data
data Color = Red | Green | Blue deriving (Eq, Show, Enum, Ord, Data)
data Width = Thin | Normal | Fat deriving (Eq, Show, Enum, Ord, Data)
data Height = Short | Medium | Tall deriving (Eq, Show, Enum, Ord, Data)
data Object = Object { color :: Color
, width :: Width
, height :: Height } deriving (Show, Data)
-- |Signature of attribute constructors used in object
signature :: Object -> [String]
signature = gmapQ (show . toConstr)
uniqueAttributes :: [Object] -> Bool
uniqueAttributes = all allDifferent . transpose . map signature
allDifferent :: (Ord a) => [a] -> Bool
allDifferent = comparePairwise . sort
where comparePairwise xs = and $ zipWith (/=) xs (drop 1 xs)
这里的关键是函数signature
,它接受一个对象,并通过其直接子对象计算每个子对象的构造函数名。因此:
*Signature> signature (Object Red Fat Medium)
["Red","Fat","Medium"]
*Signature>
如果存在除这些简单求和类型之外的任何字段(例如,类型为数据权重=权重Int
的属性,或者如果您向对象添加了名称::字符串
字段),则此操作将突然失败
(编辑为添加:)注意,您可以使用constrIndex。toConstr
代替显示。toConstr
使用Int
值的构造函数索引(基本上,在数据
定义中以构造函数的1开头的索引),如果这感觉不那么间接的话。如果由toConstr
返回的Constr
有一个Ord
实例,则根本没有间接寻址,但不幸的是…可以使用废弃的样板文件。对于这个简单的例子,我不确定这是否更好。您可以在没有泛型的情况下考虑它:uniqueAttributes objects=和[go color,go width,go height]其中go::(Ord a)=>(Object->a)->Bool;go f=allDifferent(映射f对象)
尽管将构造函数转换为字符串并进行比较感觉有些间接,但此解决方案的优点是非常简单。新的泛型方法似乎风靡一时,但是Data.Data
可以做到这一点!我添加了一个关于使用constrIndex
获取Int
值索引的注释。这是我最初做的,但是使用show
给出了一个更漂亮的签名值(尽管不太直接,也不太有效)。
GHCi> and it
False
{-# LANGUAGE DeriveDataTypeable #-}
module Signature where
import Data.List (sort, transpose)
import Data.Data
data Color = Red | Green | Blue deriving (Eq, Show, Enum, Ord, Data)
data Width = Thin | Normal | Fat deriving (Eq, Show, Enum, Ord, Data)
data Height = Short | Medium | Tall deriving (Eq, Show, Enum, Ord, Data)
data Object = Object { color :: Color
, width :: Width
, height :: Height } deriving (Show, Data)
-- |Signature of attribute constructors used in object
signature :: Object -> [String]
signature = gmapQ (show . toConstr)
uniqueAttributes :: [Object] -> Bool
uniqueAttributes = all allDifferent . transpose . map signature
allDifferent :: (Ord a) => [a] -> Bool
allDifferent = comparePairwise . sort
where comparePairwise xs = and $ zipWith (/=) xs (drop 1 xs)
*Signature> signature (Object Red Fat Medium)
["Red","Fat","Medium"]
*Signature>