taxes.txt
来自「prolog开发工具」· 文本 代码 · 共 518 行 · 第 1/2 页
TXT
518 行
,_O< Amzi! inc. 40 Samuel Prescott Dr. >O_,
( ) Stow, MA 01775, USA ( )
~~~~~~~~~--------------------------------------~~~~~~~~~~~
tel 508/897-7332 info@amzi.com fax 508/897-2784
Technical Note: TAXES
Mar95
This is the text of an article that was originally published
in PCAI magazine in 1988. The tax code that is referenced
is from that time, but the ideas are still valid.
The source code for the tax program is part of the Cogent
Prolog samples and also included with the Building Expert
Systems in Prolog disk.
Introduction
Prolog is extremely well suited to laying out tax forms.
This is because the forms are really just composed of rules
for filling out various lines. Those rules map almost directly
into Prolog syntax, giving a very desirably small "semantic
gap".
Semantic gap refers to the difference between expressing a
problem in its own domain, and expressing it in some
programming environment. For example, the tax program could
have been written in C or Assembler. Assembler would have
the largest semantic gap, since the semantics of assembler
are concerned with moving and manipulating bits and bytes.
This is far from the semantics of the tax form which are
concerned with relationships between monetary values. A C
program would have a semantic gap somewhere in between that
of Assembler and Prolog.
The programmer's job is to map a problem specified in problem
domain semantics into the programming environment semantics.
The smaller the semantic gap, the easier the job. The
greater the semantic gap, the higher the salary the
programmer commands.
A tax program also requires a database of raw data, and the
ability to bring that data into the tax forms. Prolog's
built-in database of facts and rules is ideally suited to
this purpose. The raw data is represented as simple facts,
and rules express more complex relationships between the
data.
The architecture used had two files. The first had the
rules for filling out the lines of the tax form that were
relevant for my situation. The second had the data which was
referred to by the rules.
This basic structure was extremely well suited to the "what-
if" types of games associated with financial hacking. The
program could be run for various cases by simply changing the
data.
It is also well suited for getting estimated results.
Guesses can be filled in for values which are not initially
known, and the program run to get a feel for what your tax
situation will be.
There are other advantages as well. The program and its data
become a clean record for the taxes of that year. If you
need to file estimated taxes, just plugging in new data for
the current year and running the program gives as accurate a
picture as possible for quarterly estimates.
The final advantage is, if you buy a Prolog to do your taxes,
you can write off the cost on your taxes. (I won't go to the
audit with you, but bring your code, maybe the IRS will buy a
copy of your program.)
The Basic Idea
Let's start with an oversimplification, and fantasize about a
simple tax form, 1040F in figure 1.
line 1 wages | |
line 2 tax - enter 5% of line 1 | |
line 3 withheld | |
line 4 refund (line 3 - line 2) | |
Figure 1 - Fantasy Tax Form, 1040F
Each line of the tax form can be represented as a rule, with
the name line. The line predicate has four arguments: the
form name, the line number, a description, and a value. The
rules for each line will refer to the database and to each
other as necessary. Given this, here is a Prolog program to
compute taxes for form 1040F. (Remember, terms beginning
with upper case are variables.)
tax:-
line('1040F', 4, refund, X),
write('They owe you: '), write(X), nl.
line('1040F', 1, wages, X) :- wages(X).
line('1040F', 2, tax, X) :-
line('1040F', 1, _, WAGES),
X is .05 * WAGES.
line('1040F', 3, withheld, X) :- withheld(X).
line('1040F', 4, refund, X) :-
line('1040F', 2, _, TAX),
line('1040F', 3, _, WITHHELD),
X is WITHHELD - TAX.
wages(30000).
withheld(2000).
In this program, tax is the top level predicate which is
called in a query to start the program. In true business
like fashion, it immediately asks for the bottom line. The
rule for line 4 calls rules for line 3 and 2, which call
other line rules etc. This program accesses data from the
predicates wages and withheld.
This basic scheme can be applied to the entire tax form,
however there are a few changes to be made which make the
total job simpler. First, this program computes the bottom
line, but doesn't "remember" the numbers that go on all of
the other lines of the tax form. In addition, if the same
line is needed more than once either due to backtracking, or
just the nature of the tax law, this program will recompute
it.
Instead of having each line refer directly to other lines, we
can add an intermediate predicate which "remembers" the value
of each line as it is computed. It will then use the
remembered value if it is available, or if not, compute it
and remember the answer in the database.
The intermediate predicate is getl, which is called by the
lines in the tax form when a value from another line is
needed. If a line hasn't been called before, getl will call
the line and save the result in the predicate lin (using
assertz). If the line has been called, getl will simply
get the saved value from lin. Since every line that is
called should have a value, getl returns an error message if
it fails to come up with an answer.
getl(Form, Line, X) :-
lin(Form, Line, _, X), !.
getl(Form, Line, X) :-
line(Form, Line, Description, X),
assertz( lin(Form, Line, Description, X) ),
!.
getl(Form, Line, X) :-
write(' *** error on: '), write(Form-Line),
write(' ***'), nl.
The predicate getl is then used in the tax form rules instead
of direct references to other lines.
line('1040F', 1, wages, X) :- wages(X).
line('1040F', 2, tax, X) :-
getl('1040F', 1, WAGES),
X is .05 * WAGES.
line('1040F', 3, withheld, X) :- withheld(X).
line('1040F', 4, refund, X) :-
getl('1040F',2,TAX),
getl('1040F', 3, WITHHELD),
X is WITHHELD - TAX.
Now that the data for each line has been saved, we can write
a report predicate which lays out the tax form as it needs
to be filled out. Note that even though we start at the
bottom line, the first line actually computed is line 1.
Seeing as getl uses assertz to add things to the end of the
database, the clauses for lin (stored by getl) are generally
in the correct order for the report.
report(Form) :-
write('----- '), write(Form), write(' -----'), nl,
lin(Form, Number, Description, Value),
write(Number),
tabto(10), write(Description),
tabto(40), write(Value), nl,
fail.
report(_).
Since many of the lines of the tax form involve adding
together multiple other lines, or taking the difference of
two lines, it would be useful to write two utility predicates
which perform these functions, so that the readability of the
program is not adversely affected.
The first is sum_lines which takes for arguments a form name
and a list of line numbers on the form. It returns the sum
of all of the values from those lines. It uses a secondary
predicate sumlin to ensure a more efficient tail recursive
execution.
sum_lines(F,L,X) :- sumlin(F,L,0,X).
sumlin(F,[],X,X).
sumlin(F,[H|T],X,Y) :-
getl(F,H,A),
XX is X + A,
sumlin(F,T,XX,Y).
The second is line_dif which computes the difference between
two lines on a given form.
line_dif(F,A,B,X) :-
getl(F,A,AX),
getl(F,B,BX),
X is AX - BX.
To use the program, the goal tax is first used to get the
bottom line. Then the report predicate is used to get a
detailed listing.
?- tax.
They owe you: 500
?- report('1040F').
----- 1040F -----
1 wages 30000
2 tax 1500
3 withheld 2000
4 refund 500
The same tax formulas can be used with different data. To
use the program for a different individual, simply change the
data values for wages and withheld.
Back to Reality
Given this basic strategy, and the tools developed so far,
one can easily build a customized tax program covering those
forms and lines applicable to a particular case. Lets look
at various excerpts from the program in listing 1.
The dependent calculations indicate how to use multiple
versions of a line to cover different situations. The two
clauses for line 6b represent the two cases of a spouse
exemption depending on whether the status is married joint or
not. Line 6e shows how sum_lines is used. This segment of
code makes use of two data section predicates, status and
children.
line(1040,'6a','exemption self',1).
line(1040,'6b','exemption spouse',1) :-
status(married_joint).
line(1040,'6b','',0).
line(1040,'6c','dependent children',X) :-
children(X).
line(1040,'6e','total dependents',X) :-
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?