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.