Make `-fixfn' more robust at handling floats

This change addreses issue #123. The two new optional parameters to
`-fixfn' allow the caller to specify a custom equality test function,
such as an approximate comparison of floats, and a halt test function,
which can trigger a halt to the fixpoint iteration if it fails to
converge.

The default equality test remains `equal'. The default halt test is a
simple counter up to `-fixfn-max-iterations'. The counter is provided by
the new function `-counter'.

The revised tests illustrate the usage of the new parameters.
master
William West 11 years ago
parent 4640a2a51c
commit 3992e3c506
  1. 30
      README.md
  2. 63
      dash-functional.el
  3. 10
      dev/examples.el

@ -253,7 +253,7 @@ These combinators require Emacs 24 for its lexical scope. So they are offered in
* [-orfn](#-orfn-rest-preds) `(&rest preds)`
* [-andfn](#-andfn-rest-preds) `(&rest preds)`
* [-iteratefn](#-iteratefn-fn-n) `(fn n)`
* [-fixfn](#-fixfn-fn) `(fn)`
* [-fixfn](#-fixfn-fn-optional-equal-test-halt-test) `(fn &optional equal-test halt-test)`
* [-prodfn](#-prodfn-rest-fns) `(&rest fns)`
## Anaphoric functions
@ -1470,6 +1470,7 @@ not, return a list with `args` as elements.
```el
(-list 1) ;; => '(1)
(-list 1 2 3) ;; => '(1 2 3)
(-list '(1 2 3)) ;; => '(1 2 3)
```
#### -fix `(fn list)`
@ -2081,17 +2082,38 @@ This function satisfies the following law:
(funcall (-iteratefn 'cdr 3) '(1 2 3 4 5)) ;; => '(4 5)
```
#### -fixfn `(fn)`
#### -fixfn `(fn &optional equal-test halt-test)`
Return a function that computes the (least) fixpoint of `fn`.
`fn` is a unary function, results are compared with `equal`.
`fn` must be a unary function. The returned lambda takes a single
argument, `x`, the initial value for the fixpoint iteration. The
iteration halts when either of the following conditions is satisified:
1. Iteration converges to the fixpoint, with equality being
tested using `equal-test`. If `equal-test` is not specified,
`equal` is used. For functions over the floating point
numbers, it may be necessary to provide an appropriate
appoximate comparsion test.
2. `halt-test` returns a non-nil value. `halt-test` defaults to a
simple counter that returns t after `-fixfn-max-iterations`,
to guard against infinite iteration. Otherwise, `halt-test`
must be a function that accepts a single argument, the
current value of `x`, and returns non-nil as long as iteration
should continue. In this way, a more sophisticated
convergence test may be supplied by the caller.
The return value of the lambda is either the fixpoint or, if
iteration halted before converging, a cons with car `halted` and
cdr the final output from `halt-test`.
In types: (a -> a) -> a -> a.
```el
(funcall (-fixfn 'cos) 0.7) ;; => 0.7390851332151607
(funcall (-fixfn 'cos 'approx-equal) 0.7) ;; ~> 0.7390851332151607
(funcall (-fixfn (lambda (x) (expt (+ x 10) 0.25))) 2.0) ;; => 1.8555845286409378
(funcall (-fixfn 'sin 'approx-equal) 0.1) ;; => '(halted . t)
```
#### -prodfn `(&rest fns)`

@ -135,18 +135,65 @@ This function satisfies the following law:
(funcall (-iteratefn fn n) init) = (-last-item (-iterate fn init (1+ n)))."
(lambda (x) (--dotimes n (setq x (funcall fn x))) x))
(defun -fixfn (fn)
(defun -counter (&optional beg end inc)
"Return a closure that counts from BEG to END, with increment INC.
The closure will return the next value in the counting sequence
each time it is called, and nil after END is reached. BEG
defaults to 0, INC defaults to 1, and if END is nil, the counter
will increment indefinitely.
The closure accepts any number of arguments, which are discarded."
(let ((inc (or inc 1))
(n (or beg 0)))
(lambda (&rest _)
(when (or (not end) (< n end))
(prog1 n
(setq n (+ n inc)))))))
(defvar -fixfn-max-iterations 1000
"The default maximum number of iterations performed by `-fixfn'
unless otherwise specified.")
(defun -fixfn (fn &optional equal-test halt-test)
"Return a function that computes the (least) fixpoint of FN.
FN is a unary function, results are compared with `equal'.
FN must be a unary function. The returned lambda takes a single
argument, X, the initial value for the fixpoint iteration. The
iteration halts when either of the following conditions is satisified:
1. Iteration converges to the fixpoint, with equality being
tested using EQUAL-TEST. If EQUAL-TEST is not specified,
`equal' is used. For functions over the floating point
numbers, it may be necessary to provide an appropriate
appoximate comparsion test.
2. HALT-TEST returns a non-nil value. HALT-TEST defaults to a
simple counter that returns t after `-fixfn-max-iterations',
to guard against infinite iteration. Otherwise, HALT-TEST
must be a function that accepts a single argument, the
current value of X, and returns non-nil as long as iteration
should continue. In this way, a more sophisticated
convergence test may be supplied by the caller.
The return value of the lambda is either the fixpoint or, if
iteration halted before converging, a cons with car `halted' and
cdr the final output from HALT-TEST.
In types: (a -> a) -> a -> a."
(lambda (x)
(let ((re (funcall fn x)))
(while (not (equal x re))
(setq x re)
(setq re (funcall fn re)))
re)))
(let ((eqfn (or equal-test 'equal))
(haltfn (or halt-test
(-not
(-counter 0 -fixfn-max-iterations)))))
(lambda (x)
(let ((re (funcall fn x))
(halt? (funcall haltfn x)))
(while (and (not halt?) (not (funcall eqfn x re)))
(setq x re
re (funcall fn re)
halt? (funcall haltfn re)))
(if halt? (cons 'halted halt?)
re)))))
(defun -prodfn (&rest fns)
"Take a list of n functions and return a function that takes a

@ -960,10 +960,12 @@ new list."
(-last-item (-iterate fn init (1+ 5)))))) => t)
(defexamples -fixfn
;; Find solution to cos(x) = x
(funcall (-fixfn 'cos) 0.7) => 0.7390851332151607
;; Find solution to x^4 - x - 10 = 0
(funcall (-fixfn (lambda (x) (expt (+ x 10) 0.25))) 2.0) => 1.8555845286409378)
;; Find solution to cos(x) = x (may not converge without fuzzy comparison)
(funcall (-fixfn 'cos 'approx-equal) 0.7) ~> 0.7390851332151607
;; Find solution to x^4 - x - 10 = 0 (converges using 'equal comparison)
(funcall (-fixfn (lambda (x) (expt (+ x 10) 0.25))) 2.0) => 1.8555845286409378
;; The sin function has a fixpoint at zero, but it converges too slowly and is halted
(funcall (-fixfn 'sin 'approx-equal) 0.1) => '(halted . t))
(defexamples -prodfn
(funcall (-prodfn '1+ '1- 'int-to-string) '(1 2 3)) => '(2 1 "3")

Loading…
Cancel
Save