繁体   English   中英

在Haskell中合并自定义数据类型的记录

[英]Merging records of custom data type in Haskell

我有一些数据类型

data SomeType = SomeType { a :: Maybe Int
                         , b :: Maybe String
                         , c :: Maybe OtherType }

和两个类型的变量

st1 = SomeType (Just 1) (Just "hello") Nothing
st2 = SomeType Nothing (Just "world") Nothing

如何合并它们优先考虑第二个?

merged = SomeType (Just 1) (Just "world") Nothing

这里a st2Nothing ,所以从a st1 Just 1是首选。
对于b ,来自st2 Just "world"会覆盖st1Just "hello"

我的简单方法是做类似的事情

merge :: SomeType -> SomeType -> SomeType
merge (SomeType a1 b1 c1) (SomeType a2 b2 c2) = 
  SomeType { a = maybe a1 pure a2 
           , b = maybe b1 pure b2
           , c = maybe c1 pure c2 }

实际类型大于此示例,并且c :: Maybe OtherType需要递归合并。

编辑 :此外,我知道以下形式的记录字段更新

st1 { b = Just "world" } 

使用更新的字段创建新记录。 不确定这对我的情况是否有帮助。

同类型功能SomeType -> SomeType -> SomeType看起来像一个候选人Semigroup ,或至少一些可以与实现Semigroup 有几种选择。

显式合并

如果在OP中保留SomeType ,则可以编写一个显式merge函数,如下所示:

merge :: SomeType -> SomeType -> SomeType
merge x y = toSomeType $ toTriple x <> toTriple y
  where
    toTriple (SomeType a b c) = (Last <$> a, Last <$> b, c)
    toSomeType (a, b, c) = SomeType (getLast <$> a) (getLast <$> b) c

此将每个SomeType实例一个三元组(三元组)中,其中Semigroup如果所有三个元件具有实例存在Semigroup实例。

有不止一个Semigroup实例Maybe ,但(从GHC 8.4)任何Maybe a是一个Semigroup (和Monoid )例如,当a是一个Semigroup实例。

Semigroup有利于最后两个值的情况下是Last ,所以toTriple映射abMaybe Last的值。 这不,但是,地图c ,因为在此实现它假定OtherType已经是一个Semigroup实例(见下文)。

由于生成的三元组本身是Semigroup实例,因此它们可以与<>运算符组合。 这为您提供了一个结果三元组,您可以使用toSomeType将其转换回SomeType值。

半群实例

您也可以简单地将类型设置为Semigroup实例。 我认为如果没有歧义,这是最好的。 就其本身而言, Maybe可以是多个Semigroup实例,例如分别支持FirstLast值。

但是,如果您总是希望使用Last值,则可以在类型中明确显示。 这是OtherType看起来的一种方式:

data OtherType =
  OtherType { foo :: Maybe (Last Int), bar :: Maybe (Last String) } deriving (Eq, Show)

请注意,这些字段不仅仅是Maybe值,而是显式为Maybe Last values。 这产生了一个明确的Semigroup实例:

instance Semigroup OtherType where
  (OtherType foo1 bar1) <> (OtherType foo2 bar2) =
    OtherType (foo1 <> foo2) (bar1 <> bar2)

您也可以遵循SomeType的相同设计原则,这将使显式merge功能变得多余。

例子

您可以在GHCi中试用上述功能:

*Q54068475> merge st1 st2
SomeType {a = Just 1, b = Just "world", c = Nothing}
*Q54068475> ot1 = OtherType (Just (Last 42)) (Just (Last "foo"))
*Q54068475> ot2 = OtherType (Just (Last 1337)) Nothing
*Q54068475> merge (SomeType (Just 1) (Just "hello") (Just ot1))
                  (SomeType Nothing (Just "world") (Just ot2))
SomeType {a = Just 1,
          b = Just "world",
          c = Just (OtherType {foo = Just (Last {getLast = 1337}),
                               bar = Just (Last {getLast = "foo"})})}

(我在GHCi会话中添加了一些换行符,使其更具可读性......)

这些类型也可以是Monoid实例:

instance Monoid OtherType where
  mempty = OtherType Nothing Nothing

这可能会有用,所以你也可以考虑添加这些实例......

您可以使用Maybe类型的Alternative实例。

import Control.Applicative  -- for <|>
merge (SomeType a1 b1 c1) (SomeType a2 b2 c2)
    = SomeType (a2 <|> a1) (b2 <|> b1) (c2 <|> c1)

对于Maybe类型,如果它不是Nothing ,则(<|>)返回它的第一个参数,否则返回它的第二个参数。 要优先考虑merge第二个参数,请在每种情况下将其组件用作<|>的第一个参数。

为了处理SomeTypeOtherType ,您可能希望使用类型类。

class Mergeable a where
    merge a1 a2 :: a -> a -> a

instance Mergeable SomeType where
    merge (SomeType a1 b1 c1) (SomeType a2 b2 c2)
    = SomeType (a2 <|> a1) (b2 <|> b1) (merge <$> c1 <*> c2)  -- not merge c2 c1

instance Mergeable OtherType where
    merge (OtherType a1 b1) (OtherType a2 b2) = ...

对于简单的情况,您可以使用orElse

如果需要合并数据(如果它存在于两个对象中),则可以创建帮助程序

mergeHelper :: (a -> a-> a) -> Maybe a -> Maybe a -> Maybe a
mergeHelper _ None x = x
mergeHelper _ (Maybe x) _ = Maybe x
mergeHelper f (Maybe x) (Maybe y) = Maybe $ f x y

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM