2
votes

Fusion d'enregistrements de type de données personnalisé dans Haskell

J'ai un type de données

st1 { b = Just "world" } 

et deux variables du type

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 }

Comment les fusionner en donnant la priorité à la seconde?

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

Ici un st2 est Rien , donc Juste 1 de a st1 est préférable.
Pour b , Just "world" de st2 remplace st1 de Just "hello" code>.

Mon approche simple serait de faire quelque chose comme

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

Le type réel est plus grand que cet exemple et c :: Peut-être OtherType doit également être fusionné de manière récursive.

Modifier : De plus, je suis conscient de la mise à jour du champ d'enregistrement sous la forme suivante

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

pour créer un nouvel enregistrement avec des champs mis à jour. Je ne sais pas si cela aide dans mon cas.


2 commentaires

Quel est le problème de votre approche en question? cela semble réalisable.


L'approche simple que j'ai là-bas semble un peu trop fastidieuse et je ne savais pas comment gérer les types personnalisés imbriqués.


3 Réponses :


0
votes

Pour un cas simple, vous pouvez utiliser ouElse .

Si vous avez besoin de fusionner des données si elles se présentent dans les deux objets, vous pouvez créer un assistant

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


0 commentaires

4
votes

Une fonction de type SomeType -> SomeType -> SomeType ressemble à un candidat pour un Semigroup , ou du moins quelque chose qui peut être implémenté avec Semigroup . Il y a quelques options.

Fusion explicite

Si vous conservez SomeType comme dans l'OP, vous pouvez écrire une fonction merge explicite comme celle-ci :

instance Monoid OtherType where
  mempty = OtherType Nothing Nothing

Ceci convertit chaque instance de SomeType en un triple (trois-tuple), pour lequel une instance de Semigroup existe si les trois éléments ont des instances Semigroup .

Il existe plus d'une instance Semigroup pour Peut-être , mais (à partir de GHC 8.4 ) tout Peut-être qu'un est une instance de Semigroup (et de Monoid ) lorsque a est un Semigroup code> instance.

L'instance Semigroup qui privilégie la dernière des deux valeurs est Last , donc toTriple mappe a et b aux valeurs Maybe Last . Cependant, il ne mappe pas c , car dans cette implémentation, il suppose que OtherType est déjà une instance de Semigroup (voir ci-dessous).

Puisque les triplets résultants sont eux-mêmes des instances de Semigroup , ils peuvent être combinés avec l'opérateur . Cela vous donne un triplet résultant que vous pouvez reconvertir en une valeur SomeType avec toSomeType.

Instances de semi-groupe

Vous pouvez faites également simplement les types eux-mêmes des instances Semigroup . Je pense que c'est mieux s'il n'y a pas d'ambiguïté. En soi, Maybe peut être plusieurs instances de Semigroup , par exemple en favorisant respectivement la valeur First ou Last .

Si vous toujours voulez privilégier le Dernier code> valeur, cependant, vous pouvez rendre cela explicite dans le type. Voici une façon de voir OtherType :

*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"})})}

Notez que les champs ne sont pas seulement des valeurs Peut-être , mais explicitement Valeurs peut-être dernières . Cela donne lieu à une instance Semigroup sans ambiguïté:

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

Vous pouvez également suivre le même principe de conception pour SomeType , qui rendrait la fonction explicite merge redondante.

Exemples

Vous pouvez essayer la fonctionnalité ci-dessus dans GHCi:

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



3
votes

Vous pouvez utiliser l'instance Alternative pour les types Peut-être .

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) = ...

Pour les types Peut-être , () renvoie son premier argument s'il n'est pas Nothing , sinon il renvoie ses seconds arguments. Pour donner la priorité au deuxième argument de merge , utilisez son composant comme premier argument pour dans chaque cas.

Afin de gérer à la fois SomeType et OtherType , vous pouvez utiliser une classe de type.

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


2 commentaires

c1 et c2 sont Maybe OtherType donc merge c1 c2 ne fonctionnerait pas. Besoin de quelque chose comme instance (Mergeable a) => Fusionnable (Peut-être a) où; fusionner un Nothing = a; fusionner Rien b = b; merge a b = merge <$> a <*> b Edit : mise en forme pour aucun bloc de code dans le commentaire


Oops. merge <$> c1 <*> c2 suffirait.