normalized the results of procedure-arity

svn: r17504
This commit is contained in:
Robby Findler 2010-01-06 19:34:29 +00:00
parent 07fcfd8eee
commit 68efb4008a
7 changed files with 213 additions and 14 deletions

View File

@ -0,0 +1,84 @@
(module norm-arity '#%kernel
(#%require "for.ss" "define.ss" "small-scheme.ss" "sort.ss")
(#%provide norm:procedure-arity
norm:raise-arity-error
normalize-arity) ;; for test suites
(define norm:procedure-arity
(let ([procedure-arity
(λ (p)
(normalize-arity (procedure-arity p)))])
procedure-arity))
(define norm:raise-arity-error
(let ([raise-arity-error
(λ (name arity-v . arg-vs)
(if (or (exact-nonnegative-integer? arity-v)
(arity-at-least? arity-v)
(and (list? arity-v)
(andmap (λ (x) (or (exact-nonnegative-integer? x)
(arity-at-least? x)))
arity-v)))
(apply raise-arity-error name (normalize-arity arity-v) arg-vs)
;; here we let raise-arity-error signal an error
(apply raise-arity-error name arity-v arg-vs)))])
raise-arity-error))
;; normalize-arity : (or/c arity (listof arity))
;; -> (or/c null
;; arity
;; non-empty-non-singleton-sorted-list-of-nat
;; (append non-empty-sorted-list-of-nat
;; (list (make-arity-at-least nat))))
;;
;; where arity = nat | (make-arity-at-least nat)
;;
;; result is normalized in the following sense:
;; - no duplicate entries
;; - nats are sorted
;; - at most one arity-at-least, always at the beginning
(define (normalize-arity a)
(if (pair? a)
(let-values ([(min-at-least) #f])
(for ((a (in-list a)))
(when (arity-at-least? a)
(when (or (not min-at-least)
(< (arity-at-least-value a)
(arity-at-least-value min-at-least)))
(set! min-at-least a))))
(if-one-then-no-list
(cond
[min-at-least
(append (sort
(uniq
(filter (λ (x) (and (number? x)
(< x (arity-at-least-value
min-at-least))))
a))
<)
(list min-at-least))]
[else
(sort (uniq a) <)])))
a))
;; have my own version of this to avoid a circular dependency
(define (filter p l)
(cond
[(null? l) l]
[else
(let ([x (car l)])
(if (p x)
(cons x (filter p (cdr l)))
(filter p (cdr l))))]))
(define (if-one-then-no-list lst)
(cond
[(and (pair? lst) (null? (cdr lst)))
(car lst)]
[else lst]))
(define (uniq lst)
(let ([ht (make-hash)])
(for-each (λ (i) (hash-set! ht i #f)) lst)
(hash-map ht (λ (x y) x)))))

View File

@ -14,6 +14,7 @@
"for.ss" "for.ss"
"map.ss" ; shadows #%kernel bindings "map.ss" ; shadows #%kernel bindings
"kernstruct.ss" "kernstruct.ss"
"norm-arity.ss"
'#%builtin) ; so it's attached '#%builtin) ; so it's attached
(define-syntaxes (#%top-interaction) (define-syntaxes (#%top-interaction)
@ -70,7 +71,10 @@
(rename lambda #%plain-lambda) (rename lambda #%plain-lambda)
(rename #%module-begin #%plain-module-begin) (rename #%module-begin #%plain-module-begin)
(rename module-begin #%module-begin) (rename module-begin #%module-begin)
(all-from-except '#%kernel lambda λ #%app #%module-begin apply prop:procedure) (rename norm:procedure-arity procedure-arity)
(rename norm:raise-arity-error raise-arity-error)
(all-from-except '#%kernel lambda λ #%app #%module-begin apply prop:procedure
procedure-arity raise-arity-error)
(all-from "reqprov.ss") (all-from "reqprov.ss")
(all-from "for.ss") (all-from "for.ss")
(all-from "kernstruct.ss") (all-from "kernstruct.ss")

View File

@ -163,11 +163,17 @@ the procedure. The printed form of @scheme[v] is appended to
Creates an @scheme[exn:fail:contract:arity] value and @scheme[raise]s Creates an @scheme[exn:fail:contract:arity] value and @scheme[raise]s
it as an exception. The @scheme[name] is used for the source it as an exception. The @scheme[name] is used for the source
procedure's name in the error message. The @scheme[arity-v] value must procedure's name in the error message.
be a possible result from @scheme[procedure-arity], and it is used for
the procedure's arity in the error message; if The @scheme[arity-v] value must
@scheme[name-symbol-or-procedure] is a procedure, its actual arity is be a possible result from @scheme[procedure-arity], except
ignored. The @scheme[arg-v] arguments are the actual supplied that it does not have to be normalized (see @scheme[procedure-arity?] for
the details of normalized arities); @scheme[raise-arity-error]
will normalize the arity and used the normalized form in the error message.
If @scheme[name-symbol-or-procedure] is a procedure, its actual arity is
ignored.
The @scheme[arg-v] arguments are the actual supplied
arguments, which are shown in the error message (using the error value arguments, which are shown in the error message (using the error value
conversion handler; see @scheme[error-value->string-handler]); also, conversion handler; see @scheme[error-value->string-handler]); also,
the number of supplied @scheme[arg-v]s is explicitly mentioned in the the number of supplied @scheme[arg-v]s is explicitly mentioned in the

View File

@ -129,6 +129,14 @@ A valid arity @scheme[_a] is one of the following:
] ]
Generally, @scheme[procedure-arity] always produces an arity that is normalized.
Specifically, it is either the empty list (corresponding to the procedure
@scheme[(case-lambda)]), one of the first two cases above, or a list
that contains at least two elements. If it is a list, there is at most one
@scheme[arity-at-least] instance that appears as the last element of the list,
all of the other elements are sorted in ascending order, and there are no duplicate
elements.
@mz-examples[ @mz-examples[
(procedure-arity cons) (procedure-arity cons)
(procedure-arity list) (procedure-arity list)

View File

@ -3,7 +3,8 @@
(Section 'basic) (Section 'basic)
(require scheme/flonum) (require scheme/flonum
scheme/private/norm-arity)
;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -1818,12 +1819,108 @@
(test (list 1 3 (make-arity-at-least 5)) (test (list 1 3 (make-arity-at-least 5))
procedure-arity (case-lambda [(x) 0] [(x y z) 1] [(x y z w u . rest) 2])) procedure-arity (case-lambda [(x) 0] [(x y z) 1] [(x y z w u . rest) 2]))
(test (make-arity-at-least 0) procedure-arity (lambda x 1)) (test (make-arity-at-least 0) procedure-arity (lambda x 1))
(test (list 0 (make-arity-at-least 0)) procedure-arity (case-lambda (test (make-arity-at-least 0) procedure-arity (case-lambda [() 10] [x 1]))
[() 10]
[x 1]))
(test (make-arity-at-least 0) procedure-arity (lambda x x)) (test (make-arity-at-least 0) procedure-arity (lambda x x))
(arity-test procedure-arity 1 1) (arity-test procedure-arity 1 1)
(test '() normalize-arity '())
(test 1 normalize-arity 1)
(test 1 normalize-arity '(1))
(test '(1 2) normalize-arity '(1 2))
(test '(1 2) normalize-arity '(2 1))
(test (make-arity-at-least 2) normalize-arity (list (make-arity-at-least 2) 3))
(test (list 1 (make-arity-at-least 2))
normalize-arity (list (make-arity-at-least 2) 1))
(test (list 1 (make-arity-at-least 2))
normalize-arity (list (make-arity-at-least 2) 1 3))
(test (list 0 1 (make-arity-at-least 2))
normalize-arity (list (make-arity-at-least 2) 1 0 3))
(test (list 0 1 (make-arity-at-least 2))
normalize-arity (list (make-arity-at-least 2)
(make-arity-at-least 4) 1 0 3))
(test (list 0 1 (make-arity-at-least 2))
normalize-arity (list (make-arity-at-least 4)
(make-arity-at-least 2) 1 0 3))
(test (list 1 2) normalize-arity (list 1 1 2 2))
(test 1 normalize-arity (list 1 1 1))
(test (list 1 (make-arity-at-least 2))
normalize-arity (list (make-arity-at-least 2) 1 1))
(test (list 1 (make-arity-at-least 2))
normalize-arity
(list (make-arity-at-least 2)
(make-arity-at-least 2) 1 1))
(let ()
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; randomized testing
;; predicate: normalize-arity produces a normalized arity
;;
(define (normalized-arity? a)
(or (null? a)
(arity? a)
(and (list? a)
((length a) . >= . 2)
(andmap arity? a)
(if (arity-at-least? (last a))
(non-empty-non-singleton-sorted-list-ending-with-arity? a)
(non-singleton-non-empty-sorted-list? a)))))
(define (arity? a)
(or (nat? a)
(and (arity-at-least? a)
(nat? (arity-at-least-value a)))))
(define (nat? a)
(and (number? a)
(integer? a)
(a . >= . 0)))
;; non-empty-non-singleton-sorted-list-ending-with-arity? : xx -> boolean
;; know that 'a' is a list of at least 2 elements
(define (non-empty-non-singleton-sorted-list-ending-with-arity? a)
(let loop ([bound (car a)]
[lst (cdr a)])
(cond
[(null? (cdr lst))
(and (arity-at-least? (car lst))
(> (arity-at-least-value (car lst)) bound))]
[else
(and (nat? (car lst))
((car lst) . > . bound)
(loop (car lst)
(cdr lst)))])))
(define (non-empty-sorted-list? a)
(and (pair? a)
(sorted-list? a)))
(define (non-singleton-non-empty-sorted-list? a)
(and (pair? a)
(pair? (cdr a))
(sorted-list? a)))
(define (sorted-list? a)
(or (null? a)
(sorted/bounded-list? (cdr a) (car a))))
(define (sorted/bounded-list? a bound)
(or (null? a)
(and (number? (car a))
(< bound (car a))
(sorted/bounded-list? (cdr a) (car a)))))
(for ((i (in-range 1 2000)))
(let* ([rand-bound (ceiling (/ i 10))]
[l (build-list (random rand-bound)
(λ (i) (if (zero? (random 5))
(make-arity-at-least (random rand-bound))
(random rand-bound))))]
[res (normalize-arity l)])
(unless (normalized-arity? res)
(error 'normalize-arity-failed "input ~s; output ~s" l res)))))
(test #t procedure-arity-includes? cons 2) (test #t procedure-arity-includes? cons 2)
(test #f procedure-arity-includes? cons 0) (test #f procedure-arity-includes? cons 0)
(test #f procedure-arity-includes? cons 3) (test #f procedure-arity-includes? cons 3)
@ -2299,7 +2396,7 @@
(lambda (proc) (lambda (proc)
(let ([a (procedure-reduce-arity proc ar)]) (let ([a (procedure-reduce-arity proc ar)])
(test #t procedure? a) (test #t procedure? a)
(test ar procedure-arity a) (test (normalize-arity ar) procedure-arity a)
(map (lambda (i) (map (lambda (i)
(test #t procedure-arity-includes? a i) (test #t procedure-arity-includes? a i)
(when (i . < . 100) (when (i . < . 100)
@ -2331,7 +2428,7 @@
(check-ok + (expt 2 70) (list (expt 2 70)) (list 0 10 (add1 (expt 2 70)))) (check-ok + (expt 2 70) (list (expt 2 70)) (list 0 10 (add1 (expt 2 70))))
(check-ok + (make-arity-at-least 2) (list 2 5 (expt 2 70)) (list 0 1)) (check-ok + (make-arity-at-least 2) (list 2 5 (expt 2 70)) (list 0 1))
(check-ok + (list 2 4) '(2 4) '(0 3)) (check-ok + (list 2 4) '(2 4) '(0 3))
(check-ok + (list 4 2) '(4 2) '(0 3)) (check-ok + (list 2 4) '(4 2) '(0 3))
(check-ok + (list 0 (make-arity-at-least 2)) (list 0 2 5 (expt 2 70)) (list 1)) (check-ok + (list 0 (make-arity-at-least 2)) (list 0 2 5 (expt 2 70)) (list 1))
(check-ok + (list 4 (make-arity-at-least 2)) '(2 3 4 10) '(0 1)) (check-ok + (list 4 (make-arity-at-least 2)) '(2 3 4 10) '(0 1))
(check-ok + (list 2 (make-arity-at-least 4)) '(2 4 10) '(0 1 3)))]) (check-ok + (list 2 (make-arity-at-least 4)) '(2 4 10) '(0 1 3)))])

View File

@ -40,7 +40,7 @@
;; using only optionals ;; using only optionals
(t (procedure-arity (lambda/kw (#:optional) 0)) => 0 (t (procedure-arity (lambda/kw (#:optional) 0)) => 0
(procedure-arity (lambda/kw (x #:optional y z) 0)) => '(3 1 2)) (procedure-arity (lambda/kw (x #:optional y z) 0)) => '(1 2 3))
(let ([f (lambda/kw (x #:optional y) (list x y))]) (let ([f (lambda/kw (x #:optional y) (list x y))])
(t (f 0) => '(0 #f) (t (f 0) => '(0 #f)
(f 0 1) => '(0 1))) (f 0 1) => '(0 1)))

View File

@ -1248,7 +1248,7 @@
(check-arity-error (mk-f) #f)) (check-arity-error (mk-f) #f))
(let ([mk-f (lambda () (let ([mk-f (lambda ()
(eval (syntax-property #'(case-lambda [(a b) a][(c d) c]) 'method-arity-error #t)))]) (eval (syntax-property #'(case-lambda [(a b) a][(c d) c]) 'method-arity-error #t)))])
(test '(2 2) procedure-arity (mk-f)) (test 2 procedure-arity (mk-f))
(check-arity-error (mk-f) #t) (check-arity-error (mk-f) #t)
(test 1 (mk-f) 1 2) (test 1 (mk-f) 1 2)
(let ([f (mk-f)]) (let ([f (mk-f)])