2012-06-19 23 views
15

Sono un principiante Haskell. Ho notato che Haskell non supporta dati del codice di sovraccarico:Perché Haskell/GHC non supportano il nome del record sovraccarico

-- Records.hs 

data Employee = Employee 
    { firstName :: String 
    , lastName :: String 
    , ssn :: String 
    } deriving (Show, Eq) 

data Manager = Manager 
    { firstName :: String 
    , lastName :: String 
    , ssn :: String 
    , subordinates :: [Employee] 
    } deriving (Show, Eq) 

Quando compilo questo ottengo:

[1 of 1] Compiling Main    (Records.hs, Records.o) 

Records.hs:10:5: 
    Multiple declarations of `firstName' 
    Declared at: Records.hs:4:5 
       Records.hs:10:5 

Records.hs:11:5: 
    Multiple declarations of `lastName' 
    Declared at: Records.hs:5:5 
       Records.hs:11:5 

Records.hs:12:5: 
    Multiple declarations of `ssn' 
    Declared at: Records.hs:6:5 
       Records.hs:12:5 

Data la "forza" del sistema di tipo Haskell, sembra come dovrebbe essere facile per il compilatore per determinare quale campo accedere in

emp = Employee "Joe" "Smith" "111-22-3333" 
man = Manager "Mary" "Jones" "333-22-1111" [emp] 
firstName man 
firstName emp 

C'è qualche problema che non vedo. So che il rapporto Haskell non lo consente, ma perché no?

+1

Questa non è affatto una risposta alla tua domanda, ma di solito divido i tipi di dati in moduli separati ogni volta che si presenta una situazione come la tua. Potrei, ad esempio, creare un modulo 'Employee' e un modulo' Manager', e importarli come 'E' e 'M' rispettivamente, e poi usare' E.primo nome', 'M.primo nome', ecc. Questo mi dà una sintassi ragionevolmente bella. (Non sto dicendo che sia necessariamente una buona idea, ma è quello che ho finito per fare e si è rivelato bene nei miei casi). – gspr

+3

Sì, ma questo sembra un "kludge" in un linguaggio altrimenti elegante. – Ralph

risposta

9

L'attuale sistema di registrazione non è molto sofisticato. È principalmente uno zucchero sintattico per le cose che potresti fare con boilerplate se non ci fosse una sintassi del record.

In particolare, questo:

data Employee = Employee 
    { firstName :: String 
    , lastName :: String 
    , ssn :: String 
    } deriving (Show, Eq) 

genera (tra le altre cose) una funzione firstName :: Employee -> String.

Se si consente anche nello stesso modulo di questo tipo:

data Manager = Manager 
    { firstName :: String 
    , lastName :: String 
    , ssn :: String 
    , subordinates :: [Employee] 
    } deriving (Show, Eq) 

allora quale sarebbe il tipo di funzione firstName?

Dovrebbero essere due funzioni separate che sovraccaricano lo stesso nome, che Haskell non consente. A meno che non immaginiate che ciò implicherebbe implicitamente una classe di caratteri e ne crei delle istanze per tutto con un campo chiamato firstName (diventa complicato nel caso generale, quando i campi potrebbero avere tipi diversi), allora il sistema di record corrente di Haskell non sarà in grado di supportare più campi con lo stesso nome nello stesso modulo. Haskell non tenta nemmeno di fare una cosa del genere al momento.

Potrebbe, naturalmente, essere fatto meglio. Ma ci sono alcuni problemi spinosi da risolvere, e in sostanza nessuno ha trovato soluzioni che abbiano convinto tutti che c'è ancora una direzione molto promettente.

+0

Immagino che tu possa creare una classe di tipizzazione e poi fare in modo che i metodi di classe di caratteri chiamino le versioni specifiche dei record, ma ciò aggiungerebbe molto standard in una lingua che fortunatamente di solito non ne ha bisogno. – Ralph

+1

Se si abilita il sovraccarico di campo, la funzione 'firstName' dovrebbe avere il tipo' forall a. a'. Con inferenza di tipo o dichiarazione di tipo esplicita questo tipo dovrebbe essere specializzato. I costruttori di record di Agda funzionano così. – JJJ

4

Un'opzione per evitare questo è inserire i tipi di dati in moduli diversi e utilizzare importazioni qualificate. In questo modo è possibile utilizzare gli stessi accessori di campo su diversi record di dati e mantenere il codice pulito e più leggibile.

È possibile creare un modulo per il dipendente, per esempio

module Model.Employee where 

data Employee = Employee 
    { firstName :: String 
    , lastName :: String 
    , ssn :: String 
    } deriving (Show, Eq) 

e un modulo per il Gestore, ad esempio:

module Model.Manager where 

import Model.Employee (Employee) 

data Manager = Manager 
    { firstName :: String 
    , lastName :: String 
    , ssn :: String 
    , subordinates :: [Employee] 
    } deriving (Show, Eq) 

E poi ovunque si desidera utilizzare questi due tipi di dati è possibile importarli qualificati e accedervi come segue:

import   Model.Employee (Employee) 
import qualified Model.Employee as Employee 
import   Model.Manager (Manager) 
import qualified Model.Manager as Manager 

emp = Employee "Joe" "Smith" "111-22-3333" 
man = Manager "Mary" "Jones" "333-22-1111" [emp] 

name1 = Manager.firstName man 
name2 = Employee.firstName emp 

Tenete a mente che, dopo tutto quello che stai usando due diversi tipi di dati e quindi Manger.firstName è un'altra funzione di Employee.firstName, anche quando sai che entrambi i tipi di dati rappresentano una persona e ogni persona ha un nome. Ma spetta a te quanto lontano vai ai tipi di dati astratti, ad esempio per creare un tipo di dati Person da quelle "collezioni di attributi".

Problemi correlati