2013-04-05 11 views
5

Sto cercando di scrivere un disco che rappresenta un conto corrente bancario:record Erlang sia con il tipo e le restrizioni di valore, così come i valori di default

-record(account, { name :: atom(), 
        type :: atom(), 
        balance = 0 :: integer() }). 

Ho anche vogliono limitare l'equilibrio di essere sempre >= 0. Come faccio a fare questo?

+7

Si noti che mentre è possibile dichiarare che il tipo è un numero intero non negativo, questo ** non ** impone nulla in fase di esecuzione, in realtà è solo un commento. È possibile impostare il campo del saldo su qualsiasi valore scelto e qualsiasi tipo. – rvirding

+0

@rviding Come posso applicare questo comportamento in fase di esecuzione (senza protezioni delle funzioni)? Oppure questa digitazione pseudo-statica non esiste in Erlang? – 2rs2ts

+1

Non puoi! Erlang è troppo dinamico, quindi la digitazione statica non esiste nella lingua e il compilatore non usa queste informazioni. È solo per la documentazione e per il dializzatore di strumenti di controllo del tipo. – rvirding

risposta

6

Qualcosa come balance = 0 :: 0 | pos_integer() potrebbe fare il trucco.

modifica non era sicuro che esistesse, ma non_neg_integer() sarebbe meglio:

balance = 0 :: non_neg_integer() 
+0

Non sapevo nemmeno che esistesse. Grazie mille! – 2rs2ts

+2

Attenzione che come detto Robert Virding questa è solo informazione per il lettore, la documentazione o gli strumenti. Non impedisce che l'equilibrio sia negativo o qualsiasi altra cosa (come un atomo). Se è importante nel tuo codice devi usare guard come is_integer (B) andalso B> = 0 prima di usarlo e/o modificare un equilibrio. – Pascal

+0

Grazie a @Pascal, sarò sicuro di usare quelle guardie. :) – 2rs2ts

6

Come notato da altri, le specifiche di tipo sono ingressi solo per strumenti di analisi come PropEr e Dialyzer. Se avete bisogno di far rispettare l'invariante balance >= 0, il tipo di account deve essere incapsulato, accessibile solo alle funzioni che rispettano l'invariante:

-module(account). 

-record(account, { name :: atom(), 
        type :: atom(), 
        balance = 0 :: non_neg_integer() }). 

%% Declares a type whose structure should not be visible externally. 
-opaque account() :: #account{}. 
%% Exports the type, making it available to other modules as 'account:account()'. 
-export_type([account/0]). 

%% Account constructor. Used by other modules to create accounts. 
-spec new(atom(), atom(), non_neg_integer()) -> account(). 
new(Name, Type, InitialBalance) -> 
    A = #account{name=Name, type=Type}, 
    set_balance(A, InitialBalance). 

%% Safe setter - checks the balance invariant 
-spec set_balance(account(), non_neg_integer()) -> account(). 
set_balance(Account, Balance) when is_integer(Balance) andalso Balance >= 0 -> 
    Account#account{balance=Balance}; 
set_balance(_, _) -> error(badarg). % Bad balance 

Avviso come questo assomiglia ad una classe con i campi privati ​​in linguaggi orientati agli oggetti come Java o C++ . Limitando l'accesso a costruttori e accessori "fidati", l'invariante viene applicato.

Questa soluzione non fornisce protezione contro modifica dannosa del campo balance. È del tutto possibile che il codice di un altro modulo ignori le specifiche del tipo "opaco" e sostituisca il campo del saldo nel record (dal records are just tuples).

+0

Grazie per l'esempio e l'avviso! – 2rs2ts