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>