42 votes

Existe-t-il un idiome Haskell pour la mise à jour d'une structure de données imbriquée?

Disons que j'ai les données suivantes modèle, pour garder la trace des statistiques de baseball, les joueurs, les équipes et les entraîneurs:

data BBTeam = BBTeam { teamname :: String, 
                       manager :: Coach,
                       players :: [BBPlayer] }  
     deriving (Show)

data Coach = Coach { coachname :: String, 
                     favcussword :: String,
                     diet :: Diet }  
     deriving (Show)

data Diet = Diet { dietname :: String, 
                   steaks :: Integer, 
                   eggs :: Integer }  
     deriving (Show)

data BBPlayer = BBPlayer { playername :: String, 
                           hits :: Integer,
                           era :: Double }  
     deriving (Show)

Maintenant, disons que les gestionnaires, qui sont généralement bifteck de fanatiques, envie de manger encore plus de steak -- nous devons donc être en mesure d'augmenter le steak contenu d'un gestionnaire de l'alimentation. Voici deux implémentations possibles pour cette fonction:

1) Il utilise beaucoup de pattern matching et je dois l'obtenir tout de l'argument de la commande pour tous les constructeurs de droite ... deux fois. Il me semble qu'il ne serait pas très bien ou très maintenable/lisible.

addManagerSteak :: BBTeam -> BBTeam
addManagerSteak (BBTeam tname (Coach cname cuss (Diet dname oldsteaks oldeggs)) players) = BBTeam tname newcoach players
  where
    newcoach = Coach cname cuss (Diet dname (oldsteaks + 1) oldeggs)

2) il utilise tous les accesseurs fournis par Haskell record de la syntaxe, mais c'est aussi moche et répétitif et difficile à maintenir et à lire, je pense.

addManStk :: BBTeam -> BBTeam
addManStk team = newteam
  where
    newteam = BBTeam (teamname team) newmanager (players team)
    newmanager = Coach (coachname oldcoach) (favcussword oldcoach) newdiet
    oldcoach = manager team
    newdiet = Diet (dietname olddiet) (oldsteaks + 1) (eggs olddiet)
    olddiet = diet oldcoach
    oldsteaks = steaks olddiet

Ma question est, est l'un de ces meilleurs que les autres, ou plus préféré dans le Haskell communauté? Est-il une meilleure façon de le faire (pour modifier une valeur de profondeur à l'intérieur d'une structure de données tout en gardant le contexte)? Je ne suis pas inquiet à propos de l'efficacité, du code de l'élégance/généralité/maintenabilité.

J'ai remarqué il y a quelque chose pour ce problème (ou un problème similaire?) en Clojure: update-in -- donc, je pense que j'essaie de comprendre update-in dans le cadre de la programmation fonctionnelle et Haskell et le typage statique.

37voto

Daniel Wagner Points 38831

La syntaxe de mise à jour des enregistrements est standard avec le compilateur:

 addManStk team = team {
    manager = (manager team) {
        diet = (diet (manager team)) {
             steaks = steaks (diet (manager team)) + 1
             }
        }
    }
 

Terrible! Mais il y a un meilleur moyen. Il existe plusieurs packages sur Hackage qui implémentent des références fonctionnelles et des objectifs, ce qui est certainement ce que vous voulez faire. Par exemple, avec le package fclabels , vous placeriez des traits de soulignement devant tous vos noms d’enregistrement, puis écrivez

 $(mkLabels ['BBTeam, 'Coach, 'Diet, 'BBPlayer])
addManStk = modify (+1) (steaks . diet . manager)
 

11voto

Conal Points 9874

Voici comment vous pouvez utiliser la sémantique de l'éditeur combinators (SECs), comme Lambdageek suggéré.

D'abord quelques abréviations utiles:

type Unop a = a -> a
type Lifter p q = Unop p -> Unop q

L' Unop voici une "sémantique de l'éditeur", et l' Lifter est la sémantique de l'éditeur du combinator. Certains haltérophiles:

onManager :: Lifter Coach BBTeam
onManager f (BBTeam n m p) = BBTeam n (f m) p

onDiet :: Lifter Diet Coach
onDiet f (Coach n c d) = Coach n c (f d)

onStakes :: Lifter Integer Diet
onStakes f (Diet n s e) = Diet n (f s) e

Maintenant, il suffit de composer le Secondes pour dire ce que vous voulez, à savoir ajouter 1 à la mesure des enjeux de l'alimentation, de l'administrateur (de l'équipe):

addManagerSteak :: Unop BBTeam
addManagerSteak = (onManager . onDiet . onStakes) (+1)

En comparant avec le SYB approche, la SEC version nécessite un surcroît de travail pour définir les Secondes, et j'ai fourni seulement celles qui sont nécessaires dans cet exemple. La SEC permet une application ciblée, ce qui serait utile si les joueurs avaient des régimes mais nous ne voulons pas de les ajuster. Peut-être il y a de très SYB façon de gérer cette distinction ainsi.

Edit: Voici une variante de style de base de la Sec:

onManager :: Lifter Coach BBTeam
onManager f t = t { manager = f (manager t) }

5voto

Ed'ka Points 3810

Plus tard, vous pouvez également jeter un oeil à certains de programmation générique bibliothèques: lorsque la complexité de vos données augmente et vous vous retrouvez à écrire plus et code réutilisable (comme l'augmentation de steak de contenu pour les joueurs, des entraîneurs, des régimes alimentaires et de la bière contenu des observateurs) qui est encore passe-partout, même dans les moins verbeux forme. SYB est probablement le plus bien connu de la bibliothèque (et est livré avec Haskell Platform). En fait, le papier original sur SYB utilise très similaires problème pour démontrer l'approche:

Considérez les types de données suivants qui décrivent la structure organisationnelle de l'entreprise. Une société est divisée en départements. Chaque département dispose d'un gestionnaire, et consiste en une collection de sous-unités, où une unité est un employé ou à un service. Les gestionnaires et les employés ordinaires sont tout simplement les personnes qui reçoivent un salaire.

[skiped]

Maintenant, supposons que nous voulons augmenter le salaire de tout le monde dans la société par un pourcentage spécifié. Qui est, nous devons écrire la fonction:

augmentation :: Float -> Entreprise> de l'Entreprise

(le reste est dans le livre de lecture est recommandée)

Bien sûr, dans votre exemple, vous avez juste besoin de consulter/modifier une pièce d'une petite structure de données de sorte qu'il ne nécessite pas de méthode générique (toujours le SYB-fondé de la solution pour votre tâche est ci-dessous), mais une fois que vous voyez la répétition du code/modèle de l'accès et de modification à vous mon est recommandé de vérifier ou d'autres génériques de bibliothèques de programmation.

{-# LANGUAGE DeriveDataTypeable #-}

import Data.Generics

data BBTeam = BBTeam { teamname :: String, 
manager :: Coach,
players :: [BBPlayer]}  deriving (Show, Data, Typeable)

data Coach = Coach { coachname :: String, 
favcussword :: String,
 diet :: Diet }  deriving (Show, Data, Typeable)

data Diet = Diet { dietname :: String, 
steaks :: Integer, 
eggs :: Integer}  deriving (Show, Data, Typeable)

data BBPlayer = BBPlayer { playername :: String, 
hits :: Integer,
era :: Double }  deriving (Show, Data, Typeable)


incS d@(Diet _ s _) = d { steaks = s+1 }

addManagerSteak :: BBTeam -> BBTeam
addManagerSteak = everywhere (mkT incS)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X