Haskell:有没有一种方法可以“映射”代数数据类型?

yun*_*yun 8 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 Object = Object { color  :: Colour
                     , width  :: Width 
                     , height :: Height } deriving (Show)
Run Code Online (Sandbox Code Playgroud)

给定一个对象列表,我想测试这些属性是否都是不同的。为此,我有以下功能(使用sortfrom Data.List

allDifferent = comparePairwise . sort
  where comparePairwise xs = and $ zipWith (/=) xs (drop 1 xs)

uniqueAttributes :: [Object] -> Bool
uniqueAttributes objects = all [ allDifferent $ map color  objects 
                               , allDifferent $ map width  objects
                               , allDifferent $ map height objects ]
Run Code Online (Sandbox Code Playgroud)

这可行,但相当不满意,因为我必须手动输入每个字段(颜色、宽度、高度)。在我的实际代码中,还有更多字段!有没有一种方法可以“映射”函数

\field -> allDifferent $ map field objects
Run Code Online (Sandbox Code Playgroud)

Object在像?这样的代数数据类型的字段上 我想将其视为Object其字段的列表(这在例如 javascript 中很容易),但这些字段具有不同的类型......

kos*_*kus 6

这是使用generics-sop的解决方案:

pointwiseAllDifferent
  :: (Generic a, Code a ~ '[ xs ], All Ord xs) => [a] -> Bool
pointwiseAllDifferent =
    and
  . hcollapse
  . hcmap (Proxy :: Proxy Ord) (K . allDifferent)
  . hunzip
  . map (unZ . unSOP . from)

hunzip :: SListI xs => [NP I xs] -> NP [] xs
hunzip = foldr (hzipWith ((:) . unI)) (hpure [])
Run Code Online (Sandbox Code Playgroud)

这假设您要比较的类型Object是记录类型,并要求您使该类型成为该类的实例Generic,这可以使用 Template Haskell 来完成:

deriveGeneric ''Object
Run Code Online (Sandbox Code Playgroud)

让我们通过一个具体的例子来看看这里发生了什么:

objects = [Object Red Thin Short, Object Green Fat Short]
Run Code Online (Sandbox Code Playgroud)

该行将map (unZ . unSOP . from)每个列表转换Object为异构列表(在库中称为 n 元乘积):

GHCi> map (unZ . unSOP . from) objects
[I Red :* (I Thin :* (I Short :* Nil)),I Green :* (I Fat :* (I Short :* Nil))]
Run Code Online (Sandbox Code Playgroud)

然后hunzip将这个产品列表转换为一个产品,其中每个元素都是一个列表:

GHCi> hunzip it
[Red,Green] :* ([Thin,Fat] :* ([Short,Short] :* Nil))
Run Code Online (Sandbox Code Playgroud)

现在,我们应用allDifferent到产品中的每个列表:

GHCi> hcmap (Proxy :: Proxy Ord) (K . allDifferent) it
K True :* (K True :* (K False :* Nil))
Run Code Online (Sandbox Code Playgroud)

该产品现在实际上是同质的,因为每个位置都包含Bool,因此hcollapse再次将其转换为正常的同质列表:

GHCi> hcollapse it
[True,True,False]
Run Code Online (Sandbox Code Playgroud)

最后一步就适用and于此:

GHCi> and it
False
Run Code Online (Sandbox Code Playgroud)