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

在Haskell中合并自定义数据类型的记录,haskell,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

我有一些数据类型

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
这里的
st2
Nothing
,因此首选
st1
中的
仅1

对于
b
只需来自
st2
的“世界”
即可覆盖
st1
只需“你好”

我的简单方法是

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 }
实际的类型比这个示例要大,可能其他类型也需要递归合并

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

st1 { b = Just "world" } 
创建具有更新字段的新记录。
不确定这对我的情况是否有帮助。

对于简单的情况,您可以使用
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

类型为
SomeType->SomeType->SomeType
的函数看起来像是
半群
的候选函数,或者至少是可以用
半群
实现的函数。有两种选择

显式合并 如果将
SomeType
保持在OP中,则可以编写如下显式的
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
请注意,这些字段不仅仅是
Maybe
值,而是显式的
Maybe Last
值。这就产生了一个明确的
半群
实例:

instance Semigroup OtherType where
  (OtherType foo1 bar1) <> (OtherType foo2 bar2) =
    OtherType (foo1 <> foo2) (bar1 <> bar2)
(我在GHCi会话中添加了一些换行符,以使其更具可读性…)

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

instance Monoid OtherType where
  mempty = OtherType Nothing Nothing

这可能是有用的,所以您不妨考虑添加这些实例……

,可以使用<代码>替代< /代码>实例,用于<代码>可能类型。

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

你的方法有什么问题?这似乎是可行的。我在那里使用的简单方法似乎有点太乏味了,我不知道如何处理嵌套的自定义类型。
Maybe(Last…
不是有点多余吗?为什么不仅仅是
Last…
?@Carl上面使用的
Last
类型是来自
Data.Semigroup
,而不是来自
Data.Monoid
的类型。虽然声明您可以使用选项(Last a)从Data.Monoid获取Last的行为,但是
选项
包装器将被弃用,因为
半组
现在是
Monoid
的超类。所以,
也许(最后一个a)
似乎是持久的抽象……谢谢你的回答。这非常有效,并向我介绍了更多的Haskell概念。不过还有一件事,如果我有一个具有许多属性的大型数据类型,那么定义
会变得相当长。有什么简写吗?@n.z.如果您定义类型以便它们具有明确的
半群
实例,您可能可以使用GHC扩展名,如
DeriveAnyClass
,但我不确定。您也可以使用
Generic
类型类或Template Haskell使某些内容工作,但这些都不是我的专业领域,因此我可能对这些建议有错误…c1和c2是
可能是其他类型的
,因此
合并c1 c2
将无法工作。需要类似于
实例(可合并a)=>Mergeable(可能a)where的东西;合并一个空=一个空;不合并b=b;合并a b=合并a b
Edit:在注释oops中为无代码块设置格式<代码>合并c1 c2
就足够了。
instance Monoid OtherType where
  mempty = OtherType Nothing Nothing
import Control.Applicative  -- for <|>
merge (SomeType a1 b1 c1) (SomeType a2 b2 c2)
    = SomeType (a2 <|> a1) (b2 <|> b1) (c2 <|> c1)
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) = ...