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

时间:2019-01-07 04:12:43

标签: 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,因此首选Just 1中的a st1
对于bJust "world"中的st2会覆盖st1的{​​{1}}。

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

Just "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

使用更新的字段创建新记录。 不知道这对我是否有帮助。

3 个答案:

答案 0 :(得分:4)

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

显式合并

如果您将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

这会将每个SomeType实例转换为三元组(三元组),如果所有三个元素都具有Semigroup实例,则存在一个Semigroup实例。

Semigroup有一个以上的Maybe实例,但是(根据GHC 8.4),任何Maybe a都是Semigroup(和Monoid)实例,当{ {1}}是一个a实例。

偏爱两个值中最后一个的Semigroup实例是Semigroup,因此LasttoTriplea映射到b值。但是,它没有映射Maybe Last,因为在此实现中它假定c已经是OtherType实例(见下文)。

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

半组实例

您还可以简单地将类型本身设为toSomeType实例。我认为如果没有歧义,这是最好的方法。 Semigroup本身可以是多个Maybe实例,例如分别支持SemigroupFirst值。

但是,如果您总是希望使用Last值,则可以在类型中使其明确。 Last可能是这样的一种方式:

OtherType

请注意,这些字段不仅是data OtherType = OtherType { foo :: Maybe (Last Int), bar :: Maybe (Last String) } deriving (Eq, Show) 值,而且是明确的Maybe值。这样就产生了一个明确的Maybe Last实例:

Semigroup

对于instance Semigroup OtherType where (OtherType foo1 bar1) <> (OtherType foo2 bar2) = OtherType (foo1 <> foo2) (bar1 <> bar2) ,您还可以遵循相同的设计原则,这将使显式SomeType函数变得多余。

示例

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

merge

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

Monoids

这些类型也可以是*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"})})} 实例:

Monoid

这可能很有用,因此您也可以考虑添加这些实例...

答案 1 :(得分:3)

您可以将Alternative实例用于Maybe类型。

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

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

为了同时处理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) = ...

答案 2 :(得分:0)

在简单情况下,您可以使用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