2009-11-20 6 views
9

Sto costruendo un sito Web usando erlang, mnesia e webmachine. La maggior parte della documentazione che ho letto elogia le virtù di avere funzioni referenzialmente trasparenti.Quanto lontano dovrei prendere la trasparenza referenziale?

Il problema è che l'accesso al database è uno stato esterno. Ciò significa che qualsiasi metodo che colpisce il database non è più trasparente referenziale.

Diciamo che ho un oggetto utente in un database e alcune funzioni che si occupano di autenticazione.

funzioni referenzialmente opachi potrebbe essere simile:

handle_web_request(http_info) -> 
    is_authorized_user(http_info.userid), 
    ... 
%referentially opaque 
is_authorized_user(userid) -> 
    User = get_user_from_db(userid), 
    User.is_authorized. 

%referentially opaque 
lots_of_other_functions(that_are_similar) -> 
    db_access(), 
    foo. 

referenzialmente trasparenza impone che ridurre al minimo la quantità di codice referenzialmente opaca, in modo che il chiamante deve ottenere l'oggetto dal database e passare che come argomento ad un funzione:

Il codice sopra riportato non è ovviamente un codice di produzione - è costituito esclusivamente a scopo illustrativo.

Non voglio essere risucchiato dal dogma. I vantaggi del codice referentially trasparente (come test di unità provabili) giustificano l'interfaccia meno amichevole? Fino a che punto dovrei andare nella ricerca della trasparenza referenziale?

+0

Si può semplicemente eliminare l'intero database, avendo solo un loop (MyWholeState) -> loop (ricezione X -> X end, MyWholeState) e loop (RecvHttp, MyWholeState) -> ..., loop (MyWholeNewState) funzione, rendendo tutto referenziale trasparente. – Zed

risposta

9

Perché non prendere la trasparenza referenziale fino in fondo?

Prendere in considerazione la definizione di get_user_from_db. Come fa a sapere come parlare al database? Ovviamente assume un contesto di database (globale). È possibile modificare questa funzione in modo che restituisca una funzione che consideri il contesto del database come argomento. Quello che hai è ...

get_user_from_db :: userid -> User 

Questa è una bugia. Non puoi passare da un ID utente a un utente. Hai bisogno di qualcos'altro: un database.

get_user_from_db :: userid -> Database -> User 

Ora basta curry che con l'ID utente, e dato un database in un secondo momento, la funzione vi darà un utente. Naturalmente, nel mondo reale, Database sarà un handle o un oggetto di connessione al database o altro. Per il test, dargli un database fittizio.

+1

Ora che ho letto la tua risposta, sembra del tutto ovvio. Ma non ci avrei mai pensato - grazie! Tuttavia, mi sembra che sarebbe più sensato (in questa circostanza) di scrivere la funzione che restituisce una funzione che ha un contesto di database: get_user_from_db :: Database -> userid -> Utente Suppongo che questa versione potenzialmente rompe la trasparenza referenziale in quanto il contesto del database sottostante può cambiare ... –

+1

È possibile ottenere uno dall'altro. 'a -> b -> c' è equivalente a' b -> a -> c'. Tutto ciò di cui hai bisogno è una funzione di ordine superiore: 'flip f a b = f b a'. L'ordine degli argomenti una scelta progettuale. In questo caso particolare ha senso per la componibilità mettere il Database come ultimo argomento, poiché ciò consente di eseguire la composizione di Kleisli. – Apocalisp

+1

Avendo 'Database' come primo argomento, una funzione può essere composta solo con funzioni che restituiscono database. Sospetto che non ce ne siano molti. – Apocalisp

3

Hai già menzionato il test delle unità, continua a pensare in questi termini. Tutto ciò che trova valore nei test dovrebbe essere trasparente per cui è possibile testarlo.

Se non si dispone di una logica complessa che potrebbe andare storta, e un singolo test di funzionalità/integrazione vedrebbe che è corretto, allora perché preoccuparsi di andare oltre?

Think YAGNI. Ma dove la testabilità delle unità è un'esigenza reale.