1 | ((title . "Issue 15") |
---|
2 | (authors "Alaric Snell-Pym" "Moritz Heidkamp") |
---|
3 | (date . 1291685838)) |
---|
4 | |
---|
5 | == 0. Introduction |
---|
6 | |
---|
7 | Welcome to issue 15 of the Chicken Gazette. |
---|
8 | |
---|
9 | == 1. The Hatching Farm |
---|
10 | |
---|
11 | * In his everlasting quest to provide the best documentation tooling |
---|
12 | of all, [[user:jim-ursetto|Jim Ursetto]] created yet another |
---|
13 | [[egg:chicken-doc|chicken-doc]] spin-off. Luckily it is not called |
---|
14 | {{yacdso}}. Please welcome [[egg:manual-labor|manual-labor]]! |
---|
15 | |
---|
16 | * [[user:ivan-raikov|Ivan Raikov]] went on and fixed an edge case |
---|
17 | problem in the [[egg:npdiff|npdiff]] egg, leading to the release of |
---|
18 | version 1.14. While he was at it he also released version 1.11 of |
---|
19 | [[format-textdiff|format-textdiff]] fixing some bugs in the context |
---|
20 | diff handler. |
---|
21 | |
---|
22 | * [[user:peter-lane|Peter Lane]] updated his |
---|
23 | [[egg:dataset-utils|dataset-utils]] and |
---|
24 | [[egg:classifiers|classifiers]] eggs. If machine learning or data |
---|
25 | mining is your thing you might want to check those out! |
---|
26 | |
---|
27 | * [[user:felix-winkelmann|Felix Winkelmann]] released version 0.2 of |
---|
28 | the handy [[egg:progress-indicators|progress-indicators]] extension. |
---|
29 | |
---|
30 | * Lastly, our daring [[user:peter-bex|Peter Bex]] wrestled with |
---|
31 | [[egg:openssl|openssl]] once again. The new release 1.5 should |
---|
32 | hopefully fix the remaining issues we had with the |
---|
33 | [[http://wiki.call-cc.org/|Chicken Wiki]]. |
---|
34 | |
---|
35 | |
---|
36 | == 2. Core development |
---|
37 | |
---|
38 | Chicken's {{master}} branch has seen two bug fix commits this |
---|
39 | week. One of them fixed a problem with {{thread-sleep!}} which failed |
---|
40 | when it was passed an inexact/flonum sleep-time. Thanks to Karel |
---|
41 | Miklav for reporting this! The other one fixed a bug which caused all |
---|
42 | kinds of trouble when {{cons}} got redefined. This was noticed by |
---|
43 | David Steiner when he tried following the code examples in |
---|
44 | [[http://mitpress.mit.edu/sicp/|SICP]] using Chicken. |
---|
45 | |
---|
46 | The {{experimental}} branch has been busy experimenting: In the |
---|
47 | aftermath of the [[egg:jbogenturfahi|jbogenturfa'i]] debugging session |
---|
48 | (see below), Felix decided to remove the lambda-lifter entirely since |
---|
49 | he figured it was rather ineffective at lifting lambdas. The |
---|
50 | scrutinizer on the other hand got some dents flattened and is here to |
---|
51 | stay. Finally, the aforementioned [[egg:manual-labor|manual-labor]] is |
---|
52 | the official (experimental) manual generator now! |
---|
53 | |
---|
54 | == 3. Chicken Talk |
---|
55 | |
---|
56 | Readers of [[/issues/11.html|issue 11]] may remember the |
---|
57 | [[http://www.call-cc.org/pictures/t-dose2010/index.html|T-DOSE picture gallery]]. Be |
---|
58 | informed that it now contains a few additional pictures, some of which |
---|
59 | are quite okay! |
---|
60 | |
---|
61 | The chicken-users list was dominated by [[user:alyn.post|Alan Post]]'s |
---|
62 | ongoing effort to create a Lojban parser this week, spanning across |
---|
63 | two threads. In the first of them he |
---|
64 | [[http://www.mail-archive.com/chicken-users@nongnu.org/msg12661.html|details his progress on optimizing the code's compilation]] |
---|
65 | while in the second |
---|
66 | [[http://www.mail-archive.com/chicken-users@nongnu.org/msg12705.html|he reports on problems he encountered when running it for the first time]]. As |
---|
67 | it turned out, they were caused by using {{equal?}} on a list |
---|
68 | containing circular data resulting in an endless recursion and finally |
---|
69 | in a stack overflow. A change request is imminent to ameliorate this |
---|
70 | situation. |
---|
71 | |
---|
72 | Christian Kellermann kindly announced the all new |
---|
73 | [[http://www.mail-archive.com/chicken-users@nongnu.org/msg12711.html|Gazette authorship organisation scheme]]. Check |
---|
74 | it out if you are interested in contributing or helping with a future |
---|
75 | Gazette issue! |
---|
76 | |
---|
77 | == 4. Omelette Recipes |
---|
78 | |
---|
79 | Today we're going to look at the [[egg:amb|amb]] egg. This egg |
---|
80 | implements the {{amb}} operator, much-loved as an example of the use |
---|
81 | of continuations to alter control flow in exciting ways, unusual |
---|
82 | evaluation orders, and a mind-altering image of a world where |
---|
83 | computers exploit parallel universes or quantum mechanics to explore |
---|
84 | multiple branches of a recursion. |
---|
85 | |
---|
86 | However, {{amb}} is sometimes a useful tool; there's been a few cases |
---|
87 | where I've wished it was available in projects I've done in other |
---|
88 | languages, and I'm going to simplify one of them into an example. |
---|
89 | |
---|
90 | But first, let's look at what {{amb}} does (and a little about how it |
---|
91 | works). Basically, {{amb}} is a form (it can be a macro or a |
---|
92 | procedure, and the difference in effect is immaterial for our purposes |
---|
93 | in this recipe) that has a variable number of arguments and, in |
---|
94 | principle, returns them all in separate "threads"; it can be thought |
---|
95 | of as a bit like POSIX's {{fork()}}, except lightweight. However, the |
---|
96 | intent is not to exploit parallelism (most implementations of {{amb}} |
---|
97 | do not provide any kind of pre-emption; each "thread" runs until it |
---|
98 | terminates, then lets another run), but to explore different possible |
---|
99 | control flows. As such, there is an {{amb-assert}} form that, if its |
---|
100 | argument is #f, kills the current "thread" and runs another. |
---|
101 | |
---|
102 | So every time your program performs an {{amb}}, multiple threads pop |
---|
103 | into existence; and whenever it performs an {{amb-assert}}, some threads |
---|
104 | are culled. The principle is, whenever you have a point in your |
---|
105 | program where a choice must be made, you can use {{amb}} to have the |
---|
106 | program split up and explore every possible choice; and when it turns |
---|
107 | out, further down the line, to have been the wrong choice, you can |
---|
108 | kill it, freeing the CPU to explore another choice. |
---|
109 | |
---|
110 | This is usually demonstrated with a program that solves a logic puzzle |
---|
111 | of the form "Peter, Jim, Moritz and Felix are hacking on the Chicken |
---|
112 | core. Peter is working only on source files written in Scheme. Moritz |
---|
113 | works only on {{posix.scm}}, whoever works on the expander also works |
---|
114 | on the compiler, you can't work on the scheduler without also working |
---|
115 | on {{csi}}, ...and so on... So, who introduced bug #232?". Which is |
---|
116 | all well and good for an undergraduate programming exercise, but who |
---|
117 | encounters such problems in real life? |
---|
118 | |
---|
119 | Well, the general class of problem is a "tree search problem". The |
---|
120 | idea is you have some situation that can be modelled as a tree of |
---|
121 | possibilities (potentially infinite!), with solutions dotted around |
---|
122 | the tree; and our goal is to find perhaps all the solutions, perhaps |
---|
123 | just any solution, or perhaps the solution nearest to the root of the |
---|
124 | tree. Practical examples include finding a record in a B-Tree (where |
---|
125 | the tree structure actually corresponds to physical index blocks on |
---|
126 | disk), solving logic puzzles (where the tree structure is a purely |
---|
127 | logical tree of possible situations, some of which may yield |
---|
128 | solutions), or choosing the best move to make in a game (where the |
---|
129 | tree represents possible states of the game, and the moves that can |
---|
130 | move between states). |
---|
131 | |
---|
132 | These kinds of algorithms are normally written as recursive functions, |
---|
133 | which take whatever represents a position on the tree as an argument, |
---|
134 | check to see if there's a solution there (and handle it |
---|
135 | appropriately if so, which may involve terminating the search if we've |
---|
136 | found enough solutions), then work out all the positions below this |
---|
137 | one in the tree and recursively call the function on each in |
---|
138 | turn. This forces a "depth-first" search, as the recursion will only |
---|
139 | bottom out if a solution is found (that terminates the search |
---|
140 | entirely) or a subtree is totally exhausted of options. Going for a |
---|
141 | "breadth-first" search, where every child of the current position is |
---|
142 | checked for solutions before starting to recurse down into the |
---|
143 | children of the children, is a little harder to implement; rather than |
---|
144 | just writing a recursive function that explores the tree, one must |
---|
145 | keep track of a queue of subtrees that will need exploring in future, |
---|
146 | pushing newly-found subtrees onto the back of the queue rather than |
---|
147 | exploring them as soon as they're found. However, breadth-first |
---|
148 | searches will find the solutions nearest to the top of the tree first, |
---|
149 | and will not flounder if they encounter infinite subtrees with no |
---|
150 | solutions in, so they are often more attractive than depth-first. |
---|
151 | |
---|
152 | However, rather than writing a depth-first recursive search function, or a |
---|
153 | breadth-first search function using a queue, you can also implement |
---|
154 | these search problems as a simple function using {{amb}}. One benefit |
---|
155 | of this is that one can swap the {{amb}} implementation used from |
---|
156 | depth-first to breadth-first to choose the best search strategy, |
---|
157 | without changing your program - but that's a side benefit. The real |
---|
158 | benefit is, of course, clearer, more maintainable code - which makes |
---|
159 | the {{amb}} egg worth its memory usage in leaked diplomatic cables! |
---|
160 | |
---|
161 | Let's look at a real example from my sordid past. I once wrote an app |
---|
162 | (in C, alas) to help people navigate the UK's complex welfare system, |
---|
163 | which basically lets people apply to receive money back from the |
---|
164 | government if they meet certain criteria. There's a lot of different |
---|
165 | benefits, each of which with different eligibility rules, and complex |
---|
166 | rules to work out how much benefit you're entitled to based on your |
---|
167 | circumstances. It's a nightmare, and exactly the sort of thing |
---|
168 | computers are good at. So I wrote a program to work it out. Now, |
---|
169 | working out eligibility for a benefit, and how much can be claimed, |
---|
170 | involves asking the user a lot of questions, which is tiresome. Many |
---|
171 | of these questions are shared between different benefits (or different |
---|
172 | branches of the complex flowchart you have to follow to work out some |
---|
173 | of the hairier benefits), and also, many of these questions need only |
---|
174 | be asked if certain paths are explored (questions about your child |
---|
175 | need only be asked if you're looking into benefits that are meant to |
---|
176 | help with the costs of raising children - which are only worth |
---|
177 | exploring at all if you actually have children; and the ones relating |
---|
178 | to pregnancy, well, there's no point in asking about any of them if |
---|
179 | the answer to "What is your biological gender?" was "Male".) We want |
---|
180 | to ask the minimum number of questions we can - so we shouldn't ask |
---|
181 | the same question twice, and we shouldn't ask questions unless we need |
---|
182 | the answers. The first problem can be solved by keeping a database of |
---|
183 | answers, and only asking the question if the desired information isn't |
---|
184 | already in the database; the second problem has to be solved by |
---|
185 | organising the control flow of the process to ask questions that can |
---|
186 | eliminate entire branches of enquiry up-front. Which implies a tree |
---|
187 | search! |
---|
188 | |
---|
189 | As it happens, in C, I wrote a horrible nest of tangled-looking code |
---|
190 | that basically followed all the rules to work out what benefits you |
---|
191 | were entitled to. It worked, but it was hard to maintain - and the |
---|
192 | benefits rules change frequently. Sometimes the order of questions |
---|
193 | being asked or calculations being performed mattered (you needed to |
---|
194 | ask things up front to eliminate possibilities) and sometimes it was |
---|
195 | irrelevant, as I had to put them in some order, and only my comments |
---|
196 | in the code made it clear which was which. A big part of the problem |
---|
197 | was that I had to arrange the computation around the asking of the |
---|
198 | questions; this was fine when we could ask a single question that |
---|
199 | would choose the path to take (if the client is male, don't ask about |
---|
200 | pregnancy), but sometimes we had to work out several benefits that |
---|
201 | were available, working out each case in turn (which was complex, as |
---|
202 | claiming some benefits altered your eligibility for others) to see |
---|
203 | which one would work out best for the client - rejecting any they |
---|
204 | turned out to not be eligible for. The resulting code was messy |
---|
205 | indeed. |
---|
206 | |
---|
207 | But enough prose - let's get to an example. With some code! |
---|
208 | |
---|
209 | I can't remember the details of the UK welfare system as of the late |
---|
210 | 1990s, and it was incredibly tedious anyway. So we'll work on a |
---|
211 | fictitious over-simplified example, to get to the heart of the matter |
---|
212 | easily. |
---|
213 | |
---|
214 | Let's say we have these benefits available: |
---|
215 | |
---|
216 | * Low Income Credit, which is payable to people who earn less than |
---|
217 | £10,000 a year, and whose partner (if the have one) earns less than |
---|
218 | £15,000 a year. You get given £(30,000 - total income of |
---|
219 | person/couple) / 10 per year, split into monthly payments. |
---|
220 | |
---|
221 | * Housing Benefit, which is payable to people or couples who earn less |
---|
222 | than £20,000 a year between them, or £15,000 if they have children, |
---|
223 | and who live in rented accomodation, and are not claiming Low Income |
---|
224 | Credit. You get £2,500 a year, in monthly payments, if you have |
---|
225 | children or earn less than £15,000; £2,000 a year if you do not have |
---|
226 | children and earn more than £15,000. |
---|
227 | |
---|
228 | * Carer's Allowance, which is payable to people whose partners are so |
---|
229 | ill that they need help with basic household tasks. The partner must |
---|
230 | not be earning any income for this to be claimed. It's a flat £1,000 |
---|
231 | a year. However, being an "allowance" rather than a "credit" or a |
---|
232 | "benefit", it counts as income so may affect other benefits. |
---|
233 | |
---|
234 | * Family Credit, which is available to the parent of a child (as long |
---|
235 | as the other parent is not also claiming it for the same |
---|
236 | child). It's a flat £1,000 a year, unless you (and your partner, if |
---|
237 | you have one) together earn more than £30,000 a year. |
---|
238 | |
---|
239 | Now, on to the code. To avoid asking the same question more than once, |
---|
240 | we can keep a global alist of asked questions: |
---|
241 | |
---|
242 | <enscript highlight="scheme"> |
---|
243 | (use srfi-1) |
---|
244 | |
---|
245 | (define *asked-questions* '()) |
---|
246 | |
---|
247 | (define (ask question) |
---|
248 | (let ((existing (assoc question *asked-questions*))) |
---|
249 | (if existing |
---|
250 | (cdr existing) |
---|
251 | (begin |
---|
252 | (write question) (newline) |
---|
253 | (let ((answer (read-line))) |
---|
254 | (set! *asked-questions* |
---|
255 | (cons (cons question answer) |
---|
256 | *asked-questions*)) |
---|
257 | answer))))) |
---|
258 | </enscript> |
---|
259 | |
---|
260 | As we use an actual global variable, this state will be preserved |
---|
261 | even if we backtrack with {{amb}}. |
---|
262 | |
---|
263 | We can wrap that in a few functions that ask useful questions: |
---|
264 | |
---|
265 | <enscript highlight="scheme"> |
---|
266 | (define (income allowances) |
---|
267 | (+ allowances (string->number |
---|
268 | (ask "What is your income, not including any allowances? Enter 0 for none")))) |
---|
269 | |
---|
270 | (define (has-partner?) |
---|
271 | (string=? (ask "Do you have a partner?") "yes")) |
---|
272 | |
---|
273 | (define (partner-is-ill?) |
---|
274 | (if (has-partner?) |
---|
275 | (string=? (ask "Does your partner need help around the house?") "yes") |
---|
276 | #f)) |
---|
277 | |
---|
278 | (define (renting?) |
---|
279 | (string=? (ask "Do you rent your home?") "yes")) |
---|
280 | |
---|
281 | (define (partner-income) |
---|
282 | (if (has-partner?) |
---|
283 | (string->number |
---|
284 | (ask "What is your partner's income, including any allowances? Enter 0 for none")) |
---|
285 | 0)) |
---|
286 | |
---|
287 | (define (family-income allowances) |
---|
288 | (+ (income allowances) (partner-income))) |
---|
289 | |
---|
290 | (define (num-children) |
---|
291 | (string->number (ask "How many children do you have?"))) |
---|
292 | </enscript> |
---|
293 | |
---|
294 | Now, clearly, "effective income" is the actual earnings of the person |
---|
295 | or couple, plus any "allowances" they receive, and what other benefits |
---|
296 | are being claimed may affect the computation of a benefit. So we will |
---|
297 | phrase our functions to work out the benefits as having an argument |
---|
298 | which is a record giving details of what else they're claiming. We can |
---|
299 | then write our basic benefit calculators as fairly direct translations |
---|
300 | of the rules, using {{amb-assert}} from the amb egg to reject any |
---|
301 | invalid benefits: |
---|
302 | |
---|
303 | <enscript highlight="scheme"> |
---|
304 | (use amb) |
---|
305 | |
---|
306 | (define-record benefits |
---|
307 | claimed allowances) |
---|
308 | |
---|
309 | (define (claim-benefit existing-benefits benefit amount allowance?) |
---|
310 | (make-benefits |
---|
311 | (cons (cons benefit amount) |
---|
312 | (benefits-claimed existing-benefits)) |
---|
313 | (if allowance? |
---|
314 | (+ amount (benefits-allowances existing-benefits)) |
---|
315 | (benefits-allowances existing-benefits)))) |
---|
316 | |
---|
317 | (define (claiming? existing-benefits benefit) |
---|
318 | (assq benefit (benefits-claimed existing-benefits))) |
---|
319 | |
---|
320 | (define (compute-lic other-benefits) |
---|
321 | (amb-assert (< (income (benefits-allowances other-benefits)) 10000)) |
---|
322 | (amb-assert (not (claiming? other-benefits 'hb))) |
---|
323 | (if (has-partner?) |
---|
324 | (amb-assert (< (partner-income) 15000))) |
---|
325 | (claim-benefit |
---|
326 | other-benefits |
---|
327 | 'lic (/ (- 30000 (family-income (benefits-allowances other-benefits))) 10) #f)) |
---|
328 | |
---|
329 | (define (compute-hb other-benefits) |
---|
330 | (if (zero? (num-children)) |
---|
331 | (amb-assert (< (family-income (benefits-allowances other-benefits)) 20000)) |
---|
332 | (amb-assert (< (family-income (benefits-allowances other-benefits)) 25000))) |
---|
333 | (amb-assert (renting?)) |
---|
334 | (amb-assert (not (claiming? other-benefits 'lic))) |
---|
335 | (claim-benefit |
---|
336 | other-benefits |
---|
337 | 'hb (if (and (zero? (num-children)) (> (family-income) 15000)) |
---|
338 | 2000 |
---|
339 | 2500) #f)) |
---|
340 | |
---|
341 | (define (compute-ca other-benefits) |
---|
342 | (amb-assert (zero? (partner-income))) |
---|
343 | (amb-assert (partner-is-ill?)) |
---|
344 | (claim-benefit |
---|
345 | other-benefits |
---|
346 | 'ca 1000 #t)) |
---|
347 | |
---|
348 | (define (compute-fc other-benefits) |
---|
349 | (amb-assert (not (zero? (num-children)))) |
---|
350 | (amb-assert (< (family-income (benefits-allowances other-benefits)) 30000)) |
---|
351 | (claim-benefit |
---|
352 | other-benefits |
---|
353 | 'fc 1000 #f)) |
---|
354 | </enscript> |
---|
355 | |
---|
356 | Having done that, we can try out all the possibilities using {{amb}} |
---|
357 | to decide whether we try to claim each benefit or not: |
---|
358 | |
---|
359 | <enscript highlight="scheme"> |
---|
360 | (define (compute-benefits) |
---|
361 | (let* ((initial-state (make-benefits '() 0)) |
---|
362 | ;; Compute allowances |
---|
363 | (claim-ca (amb #t #f)) |
---|
364 | (after-ca (if claim-ca (compute-ca initial-state) initial-state)) |
---|
365 | ;; Compute other benefits |
---|
366 | (claim-fc (amb #t #f)) |
---|
367 | (after-fc (if claim-fc (compute-fc after-ca) after-ca)) |
---|
368 | (claim-hb (amb #t #f)) |
---|
369 | (after-hb (if claim-hb (compute-hb after-fc) after-fc)) |
---|
370 | (claim-lic (amb #t #f)) |
---|
371 | (after-lic (if claim-lic (compute-lic after-hb) after-hb))) |
---|
372 | after-lic)) |
---|
373 | </enscript> |
---|
374 | |
---|
375 | What does {{compute-benefits}} actually do? For each benefit, it |
---|
376 | "splits the world" using {{amb}} into two versions - one where we try |
---|
377 | and claim this benefit, and one where we don't. We compute Carer's |
---|
378 | Allowance first, as it is an allowance which might affect later |
---|
379 | calculations; we can't compute any income-dependent benefits until |
---|
380 | we've decided if we're claiming this one or not, so it has to go |
---|
381 | first. The order of the others was picked arbitrarily. |
---|
382 | |
---|
383 | {{compute-benefits}} then returns the final state of affairs as a |
---|
384 | benefits record, but it will return several times with different |
---|
385 | possible final states. We need to find them all, and pick the one with |
---|
386 | the highest benefits earned. The way to do that is with |
---|
387 | {{amb-collect}}, which takes an expression and makes a list of all the |
---|
388 | results that expression returns in different threads: |
---|
389 | |
---|
390 | <enscript highlight="scheme"> |
---|
391 | (define (total-benefits benefits) |
---|
392 | (fold (lambda (claim sum-so-far) |
---|
393 | (+ (cdr claim) sum-so-far)) |
---|
394 | 0 |
---|
395 | (benefits-claimed benefits))) |
---|
396 | |
---|
397 | (define (best-benefits-to-claim) |
---|
398 | (let ((options |
---|
399 | (sort |
---|
400 | (amb-collect (compute-benefits)) |
---|
401 | (lambda (a b) |
---|
402 | (< (total-benefits b) |
---|
403 | (total-benefits a)))))) |
---|
404 | (display "Your best option is to claim the following: ") |
---|
405 | (write (benefits-claimed (car options))) |
---|
406 | (newline))) |
---|
407 | </enscript> |
---|
408 | |
---|
409 | Running {{(best-benefits-to-claim)}} will ask you some questions (if |
---|
410 | it's the first time you've run it, at any rate) and then suggest how |
---|
411 | much to claim of which benefits: |
---|
412 | |
---|
413 | #;22> (best-benefits-to-claim) |
---|
414 | "Do you have a partner?" |
---|
415 | #;22> no |
---|
416 | "How many children do you have?" |
---|
417 | #;22> 2 |
---|
418 | "What is your income, not including any allowances? Enter 0 for none" |
---|
419 | #;22> 15000 |
---|
420 | "Do you rent your home?" |
---|
421 | #;22> yes |
---|
422 | Your best option is to claim the following: ((hb . 2500) (fc . 1000)) |
---|
423 | |
---|
424 | You can try again if you clear the {{*asked-questions*}} store: |
---|
425 | |
---|
426 | #;25> (set! *asked-questions* '()) |
---|
427 | #;26> (best-benefits-to-claim) |
---|
428 | "Do you have a partner?" |
---|
429 | #;26> yes |
---|
430 | "What is your partner's income, including any allowances? Enter 0 for none" |
---|
431 | #;26> 0 |
---|
432 | "Does your partner need help around the house?" |
---|
433 | #;26> yes |
---|
434 | "How many children do you have?" |
---|
435 | #;26> 2 |
---|
436 | "What is your income, not including any allowances? Enter 0 for none" |
---|
437 | #;26> 14500 |
---|
438 | "Do you rent your home?" |
---|
439 | #;26> yes |
---|
440 | Your best option is to claim the following: ((hb . 2500) (fc . 1000) (ca . 1000)) |
---|
441 | |
---|
442 | The resulting code is highly extensible. The entire complexities of |
---|
443 | choosing which benefits to go for are reduced to listing the |
---|
444 | requirements of each benefit inside its definition, using |
---|
445 | {{amb-assert}}s, then stacking up the benefits in the {{let*}} inside |
---|
446 | {{compute-benefits}}. If {{compute-benefits}} became much more |
---|
447 | complex, I'd be inclined to put the benefits functions into two |
---|
448 | lists (one for allowances, one for the rest, to ensure allowances are |
---|
449 | covered first) and iterate through them. |
---|
450 | |
---|
451 | This example is a bit simplified and contrived - imagine each benefit |
---|
452 | has tens to hundreds of computation steps, many of which involve |
---|
453 | asking a question, and many of which depend on the results of previous |
---|
454 | calculations or assumptions; and imagine there are fifteen different |
---|
455 | benefits of varying complexity levels. And then imagine that the rules |
---|
456 | for each benefit change periodically, so you need to minimize the |
---|
457 | amount of duplication of information in your formulation of the rules. |
---|
458 | |
---|
459 | Aren't you glad to be a smug Lisp weenie? |
---|
460 | |
---|
461 | == 5. About the Chicken Gazette |
---|
462 | |
---|
463 | The Gazette is produced weekly by a volunteer from the Chicken |
---|
464 | community. The latest issue can be found at |
---|
465 | [[http://gazette.call-cc.org]] or you can follow it in your feed |
---|
466 | reader at [[http://gazette.call-cc.org/feed.atom]]. If you'd like to |
---|
467 | write an issue, [[http://wiki.call-cc.org/gazette|consult the wiki]] |
---|
468 | for the schedule and instructions! |
---|