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 + -
显示快捷键?