1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
|
open Prelude
(** The 'kernel' of account types: a hierarchy of valid types. A valid type is a
path that leads to a node in the hierarchy. *)
module Type_hierarchy : sig
type path
type polarity = Increase_on_debit | Increase_on_credit
val children : path -> path list
val sub : path -> string -> path option
val super : path -> path option
val equal_path : path -> path -> bool
val is_prefix : path -> prefix:path -> bool
val polarity : path -> polarity
val asset : path
val equity : path
val expense : path
val income : path
val liability : path
end = struct
type polarity = Increase_on_debit | Increase_on_credit
type tree = { car : tree String.Map.t }
type trunk = { car : (polarity * tree) String.Map.t }
type path = Base of string | Sub of string * path
let rec path_to_list ?(suffix = []) p =
match p with
| Base x -> x :: suffix
| Sub (x, p') -> path_to_list ~suffix:(x :: suffix) p'
let canonical : trunk =
let mk alist : tree = { car = String.Map.of_alist_exn alist } in
{
car =
String.Map.of_alist_exn
[
( "Asset",
( Increase_on_debit,
mk
[
("Accounts_receivable", mk []);
("Bank", mk [ ("Savings", mk []); ("Checking", mk []) ]);
("Cash", mk []);
("Mutual_fund", mk []);
("Stock", mk []);
] ) );
("Equity", (Increase_on_credit, mk []));
("Expense", (Increase_on_debit, mk []));
("Income", (Increase_on_credit, mk []));
( "Liability",
( Increase_on_credit,
mk [ ("Accounts_payable", mk []); ("Credit", mk []) ] ) );
];
}
(* In this module, only the following two function entertains the
option that the given path may not be valid (i.e., it does not
throw an exception for invalid paths). *)
let rec get_node : path -> tree option =
let open Option.Let_syntax in
function
| Base x ->
let%map _, t = Map.find canonical.car x in
t
| Sub (t, p) ->
let%bind super = get_node p in
Map.find super.car t
(** Always gives [Some] under valid paths, giving a list of valid paths *)
let children (p : path) : path list =
let node = Option.value_exn (get_node p) in
List.map (Map.keys node.car) ~f:(fun sub -> Sub (sub, p))
let sub (p : path) (name : string) : path option =
let node = Option.value_exn (get_node p) in
if Map.mem node.car name then Some (Sub (name, p)) else None
let super : path -> path option = function
| Base _ -> None
| Sub (_, super) -> Some super
let rec equal_path p1 p2 =
match (p1, p2) with
| Base x1, Base x2 -> String.(x1 = x2)
| Sub (x1, p1'), Sub (x2, p2') -> String.(x1 = x2) && equal_path p1' p2'
| _, _ -> false
let is_prefix (p : path) ~(prefix : path) : bool =
List.is_prefix (path_to_list p) ~prefix:(path_to_list prefix)
~equal:String.equal
let rec polarity = function
| Base x ->
let pol, _ = Map.find_exn canonical.car x in
pol
| Sub (_, p') -> polarity p'
let assert_valid acc =
match get_node acc with None -> failwith "invalid base account" | _ -> acc
let asset = Base "Asset" |> assert_valid
let equity = Base "Equity" |> assert_valid
let expense = Base "Expense" |> assert_valid
let income = Base "Income" |> assert_valid
let liability = Base "Liability" |> assert_valid
end
module Type = struct
type t = Type_hierarchy.path [@@deriving equal]
let rec base (t : t) : t option =
match Type_hierarchy.super t with
| None -> (* [t] is the root type *) None
| Some t' ->
(* [t] is a base type if its supertype is the root type *)
Some (Option.value (base t') ~default:t)
(** [a] is a strict supertype of [b] *)
let is_strict_super a b =
Type_hierarchy.is_prefix b ~prefix:a && not ([%equal: t] a b)
end
module Debit_credit = struct
type t = Debit | Credit [@@deriving string, sexp_of]
(* let opposite = function Debit -> Credit | Credit -> Debit *)
end
module Money = struct
module Amount : sig
type t
val equal : t -> t -> bool
val compare : t -> t -> int
val of_bigint : Bigint.t -> t option
val to_bigint : t -> Bigint.t
val ( + ) : t -> t -> t
val ( = ) : t -> t -> bool
val sexp_of_t : t -> Sexp.t
val zero : t
end = struct
type t = Bigint.t [@@deriving sexp_of]
let equal = Bigint.equal
let compare = Bigint.compare
let of_bigint x = if Bigint.(zero <= x) then Some x else None
let to_bigint x = x
let ( + ) x y = Bigint.(x + y)
let ( = ) = equal
let zero = Bigint.zero
end
module Diff : sig
type t
val equal : t -> t -> bool
val compare : t -> t -> int
val of_bigint : Bigint.t -> t
val to_bigint : t -> Bigint.t
val ( + ) : t -> t -> t
val ( +% ) : t -> Amount.t -> t
val ( - ) : t -> t -> t
val ( -% ) : t -> Amount.t -> t
val ( = ) : t -> t -> bool
val neg : t -> t
val ( ~$ ) : int -> t
val sexp_of_t : t -> Sexp.t
val of_amount :
Amount.t -> Debit_credit.t -> on_debit:[ `Incr | `Decr ] -> t
end = struct
type t = Bigint.t [@@deriving sexp_of]
let equal = Bigint.equal
let compare = Bigint.compare
let of_bigint x = x
let to_bigint x = x
let ( + ) x y = Bigint.(x + y)
let ( +% ) x y = x + of_bigint (Amount.to_bigint y)
let ( - ) x y = Bigint.(x - y)
let ( -% ) x y = x - of_bigint (Amount.to_bigint y)
let ( = ) = equal
let neg = Bigint.neg
let ( ~$ ) = Fn.compose of_bigint Bigint.of_int
let of_amount x (dc : Debit_credit.t) ~on_debit =
match (dc, on_debit) with
| Debit, `Incr -> of_bigint (Amount.to_bigint x)
| Credit, `Incr -> neg (of_bigint (Amount.to_bigint x))
| Debit, `Decr -> neg (of_bigint (Amount.to_bigint x))
| Credit, `Decr -> of_bigint (Amount.to_bigint x)
end
end
module Commodity_id = struct
type t = string [@@deriving equal, compare, sexp]
module Map = Map.Make (struct
type nonrec t = t [@@deriving equal, compare, sexp]
end)
end
module Account = struct
type t = Type.t * node
and node =
(* Balance in some commodity *)
| Leaf of Commodity_id.t * Money.Diff.t
| Subtree of node String.Map.t
end
|