! File : rainfall.pop ! SCCS : "%Z%20%E% %M% %I%" ! Author : Richard A. O'Keefe ! Purpose: Simple exercise, refactored using repeater combinators. comment The rainfall problem: Given a list of numbers, compute the average of the non-negative numbers, stopping at the end of the list or the first -999. This is not identical to, but is based on, the problem in "Learning to Program = Learning to Construct Mechanisms and Explanations" Elliot Soloway CACM September 1986, Volume 29, Number 9, pages 850-858. ; function rainfall_list xs; vars s n x; 0 -> s; 0 -> n; while xs.islink and (xs.dest -> xs -> x; x /= -999) do if x >= 0 then x + s -> s; 1 + n -> n; close; enddo; if n = 0 then 0 else s/n close end; function rainfall_stdin; vars s n x; 0 -> s; 0 -> n; while .numberread -> x; x.isnumber and x /= -999 do if x >= 0 then x + s -> s; 1 + n -> n; close; enddo; if n = 0 then 0 else s/n close end; comment Those are essentially the same problem. The first refactoring introduces the idea of a REPEATER. A repeater is a function of no arguments which returns the elements of a notional sequence, a new element each time it is called, until it reaches the end of the sequence, when it returns a special value 'termin'. type repeater(T) = () => T | termin In particular, the built-in function numberread reads numbers from the current input stream, and the built-in function listin takes a (linked) list and returns a repeater reporting the elements of the list: numberread : repeater(number) listing : list(T) => repeater(T) This refactoring separates "how to get the elements of a sequence" from "how to stop early", "how to select some of the elements", and "how to compute a mean". ; function rainfall_repeater r; vars s n x; 0 -> s; 0 -> n; while .r -> x; x /= termin and x /= -999 do if x >= 0 then x + s -> s; 1 + n -> n; close; enddo; if n = 0 then 0 else s/n close end; function rainfall_list xs; rainfall_repeater(listin(xs)) end; function rainfall_stdin; rainfall_repeater(numberread) end; comment We can refactor further We can make this clearer by refactoring it. listin : list(T) => repeater(T) -- in library untilin : repeater(T), (T => boolean) => repeater(T) filterin : repeater(T), (T => boolean) => repeater(T) safe_/ : number, number => real mean : repeater(number) => real ; function untilin r p; lambda r_ p_ => x_; .r_ => x_; if x_ /= termin and x_.p_ then termin -> x_ close; end(% r, p %) end; function filterin r p; lambda r_ p_ => x_; until .r_ -> x_; x_ = termin or x_.p_ do enddo; end(% r, p %) end; function safe_/ x y z; ! division, protecting against y=0 if y == 0 then z else x / y close end; function mean xs; ! xs can be any sequence, an array, or a repeater vars s n; 0 -> s; 0 -> n; applist(xs, lambda x; x + s -> s; 1 + n -> n end); safe_/(s, n, 0.0) end; function generic_mean r; mean(filterin(untilin(r, nonop =(% -999 %)), nonop >=(% 0 %))) end; comment Now rainfall_list(L) is L.listin.generic_mean and rainfall_file() is numberread.generic_mean ;