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.
3 Réponses :
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
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.
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.
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.
Vous pouvez essayer la fonctionnalité ci-dessus dans GHCi:
data OtherType =
OtherType { foo :: Maybe (Last Int), bar :: Maybe (Last String) } deriving (Eq, Show)
Maybe (Last ...) n'est-il pas un peu redondant? Pourquoi pas simplement Last ... ?
@Carl Le type Last utilisé ci-dessus est celui de Data.Semigroup , pas celui de Data.Monoid . Alors que la documentation pour ce type a> indique que vous pouvez utiliser l'option (Last a) pour obtenir le comportement de Last from Data.Monoid , le wrapper Option sera obsolète car Semigroup code> est maintenant une superclas de Monoid . Donc Maybe (Last a) semble être l'abstraction durable ...
Merci d'avoir répondu. Cela fonctionne très bien et me présente plus de concepts Haskell. Une dernière chose cependant, si j'ai un type de données volumineux avec de nombreux attributs, la définition du <> devient assez longue. Y a-t-il un raccourci pour cela?
@ n.z. Si vous définissez les types afin qu'ils aient des instances Semigroup non ambiguës, vous pourrez peut-être utiliser une extension GHC comme DeriveAnyClass , mais je ne suis pas sûr. Il est également possible que vous puissiez faire fonctionner quelque chose avec la classe de type Generic , ou avec Template Haskell, mais aucun de ceux-ci n'est mon domaine d'expertise, donc je me trompe peut-être sur ces suggestions ...
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)
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.
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.