我有某种数据类型
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 st2
是Nothing
,因此首选Just 1
中的a st1
。
对于b
,Just "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
使用更新的字段创建新记录。 不知道这对我是否有帮助。
答案 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
,因此Last
将toTriple
和a
映射到b
值。但是,它没有映射Maybe Last
,因为在此实现中它假定c
已经是OtherType
实例(见下文)。
由于生成的三元组本身就是Semigroup
实例,因此可以将它们与Semigroup
运算符结合使用。这样就得到了一个三元组,您可以使用<>
将其转换回SomeType
值。
您还可以简单地将类型本身设为toSomeType
实例。我认为如果没有歧义,这是最好的方法。 Semigroup
本身可以是多个Maybe
实例,例如分别支持Semigroup
或First
值。
但是,如果您总是希望使用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会话中添加了一些换行符,以使其更具可读性...)
这些类型也可以是*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 参数的优先级,在每种情况下都应将其组件用作<|>
的第一个参数。
为了同时处理SomeType
和OtherType
,您可能要使用类型类。
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