[英]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 st2
是Nothing
,所以从a st1
Just 1
是首选。
对于b
,来自st2
Just "world"
会覆盖st1
的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
需要递归合并。
编辑 :此外,我知道以下形式的记录字段更新
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
映射a
和b
来Maybe Last
的值。 这不,但是,地图c
,因为在此实现它假定OtherType
已经是一个Semigroup
实例(见下文)。
由于生成的三元组本身是Semigroup
实例,因此它们可以与<>
运算符组合。 这为您提供了一个结果三元组,您可以使用toSomeType
将其转换回SomeType
值。
您也可以简单地将类型设置为Semigroup
实例。 我认为如果没有歧义,这是最好的。 就其本身而言, Maybe
可以是多个Semigroup
实例,例如分别支持First
或Last
值。
但是,如果您总是希望使用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
的第二个参数,请在每种情况下将其组件用作<|>
的第一个参数。
为了处理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) = ...
对于简单的情况,您可以使用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.