Domanda

Diciamo che ho il seguente modello di dati, per tenere traccia delle statistiche di giocatori di baseball, squadre e allenatori:

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)

Ora diciamo che i manager, che di solito sono fanatici della bistecca, vogliono mangiare ancora più bistecca, quindi dobbiamo essere in grado di aumentare il contenuto di bistecca della dieta di un manager. Ecco due possibili implementazioni per questa funzione:

1) Questo utilizza un sacco di abbinamento a pattern e devo ottenere tutto l'argomento ordinando per tutti i costruttori giusti ... due volte. Sembra che non si ridimensionerebbe molto bene o sarebbe molto mantenebile/leggibile.

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) Questo utilizza tutti gli accessori forniti dalla sintassi record di Haskell, ma è anche brutto e ripetitivo e difficile da mantenere e leggere, credo.

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

La mia domanda è: una di queste è migliore dell'altra o più preferita all'interno della comunità di Haskell? Esiste un modo migliore per farlo (per modificare un valore in profondità all'interno di una struttura di dati mantenendo il contesto)? Non sono preoccupato per l'efficienza, solo in codice eleganza/generalità/manutenibilità.

Ho notato che c'è qualcosa per questo problema (o un problema simile?) In Clojure: update-in - Quindi penso che sto cercando di capire update-in Nel contesto della programmazione funzionale e di Haskell e tipizzazione statica.

È stato utile?

Soluzione

La sintassi dell'aggiornamento del record è disponibile con il compilatore:

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

Terribile! Ma c'è un modo migliore. Esistono diversi pacchetti sull'hackage che implementano riferimenti funzionali e obiettivi, che è sicuramente quello che vuoi fare. Ad esempio, con il fclabels pacchetto, metteresti sottotitoli davanti a tutti i nomi dei record, quindi scrivi

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

Modificato nel 2017 per aggiungere: In questi giorni c'è un ampio consenso sul lente Il pacchetto è una tecnica di implementazione particolarmente buona. Sebbene sia un pacchetto molto grande, c'è anche un'ottima documentazione e materiale introduttivo disponibile in vari luoghi del Web.

Altri suggerimenti

Ecco come potresti usare i combinatori di editor semantici (SEC), come ha suggerito Lambdageek.

Prima un paio di utili abbreviazioni:

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

Il Unop Ecco un "editor semantico" e il Lifter è il combinatore dell'editor semantico. Alcuni sollevatori:

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

Ora basta comporre i SEC per dire quello che vuoi, vale a dire aggiungi 1 alla posta in gioco della dieta del manager (di una squadra):

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

Rispetto all'approccio SYB, la versione SEC richiede un lavoro extra per definire i SEC e ho fornito solo quelli necessari in questo esempio. La SEC consente l'applicazione mirata, il che sarebbe utile se i giocatori avessero una dieta, ma non volevamo modificarli. Forse c'è anche un grazioso modo SYB per gestire quella distinzione.

Modificare: Ecco uno stile alternativo per i SEC di base:

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

Successivamente potresti anche voler dare un'occhiata ad alcune librerie di programmazione generiche: quando la complessità dei tuoi dati aumenta e ti ritrovi a scrivere di più e codice caldaia è ancora la piastra della caldaia anche in forma meno verbosa.Syb è probabilmente la biblioteca più conosciuta (e viene fornita con la piattaforma Haskell). In effetti il carta originale su syb usa un problema molto simile per dimostrare l'approccio:

Considera i seguenti tipi di dati che descrivono la struttura organizzativa di un'azienda. Una società è divisa in dipartimenti. Ogni dipartimento ha un manager ed è composto da una raccolta di sottotitoli, in cui un'unità è un singolo dipendente o un dipartimento. Sia i manager che i dipendenti ordinari sono solo persone che ricevono uno stipendio.

Skiped

Supponiamo ora che vogliamo aumentare lo stipendio di tutti gli abitanti di una percentuale specificata. Cioè, dobbiamo scrivere la funzione:

Aumenta :: float -> azienda -> azienda

(Il resto è nella carta - si consiglia la lettura)

Naturalmente nel tuo esempio devi solo accedere/modificare un pezzo di una piccola struttura di dati in modo che non richieda un approccio generico (ancora la soluzione basata su SYB per il tuo compito è sotto) ma una volta che si vede il codice/modello ripetuto di accesso/ Modifica ti voglio controllare questo o Altro Librerie di programmazione generica.

{-# 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)
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top