Transparent Procedures
| (require transparent-procedure) | |
| package: transparent-procedure-lib | |
Usually one does not want to compare functions, but there are situations where it’s desirable. For instance, most memoization implementations use a hash table to cache previous results. As a consequence, memoization of higher-order functions is thwarted by extensionally equivalent program transformations such as η expansion. The potential of cache misses for memoized higher-order functions does not affect correctness, so it may not seem like much of a problem. However, it’s actually possible to perfectly memoize higher-order functions. The idea is to track all interactions with a higher-order input and store a first-order representation of its extensional behavior. Determining if a future application is in cache requires testing if inputs behave (extensionally) the same as anything recorded in the memo table.
> (define inc (make-procedure (λ (x) (+ x 1)))) > inc (procedure)
> (void (inc 1) (inc 2) (inc 3)) > inc (procedure [ 1 . 2 ] [ 2 . 3 ] [ 3 . 4 ])
> (define add1′ (make-procedure add1)) > (void (add1′ 3) (add1′ 4) (add1′ 5)) > (equal? inc add1′) #t
> inc (procedure [ 1 . 2 ] [ 2 . 3 ] [ 3 . 4 ] [ 4 . 5 ] [ 5 . 6 ])
> add1′ (procedure [ 3 . 4 ] [ 4 . 5 ] [ 5 . 6 ] [ 1 . 2 ] [ 2 . 3 ])
Intuitively, think of make-procedure as similar to constructors of other mutable data structures such as make-hash. However, instead of being able to mutate the data structure using an operation such as hash-set!, application of a transparent procedure mutates the underlying data structure based on the input and output. As with other mutable data structures, equal? is interpreted as "equal now," and eq? is interpreted as "equal always." The result of equal? may change based on future interactions with the transparent procedure that reveal more of its graph.
> (define map′ (make-procedure map)) > (void (map′ add1 '(1 2 3))) > (void (map′ sub1 '(4 5 6))) > map′ (procedure [ (procedure [ 6 . 5 ] [ 5 . 4 ] [ 4 . 3 ]) '(4 5 6) . '(3 4 5) ] [ (procedure [ 3 . 4 ] [ 2 . 3 ] [ 1 . 2 ]) '(1 2 3) . '(2 3 4) ]
> (struct unknown ()) > (define values′ (make-procedure values)) > (values′ (unknown)) make-procedure: cannot inspect #<unknown>
> (define (f x) (if (zero? x) 0 (add1 x))) > (define map′-curried (dict-ref map′ f)) > map′-curried (procedure [ '(1 2 3) . '(2 3 4) ])
> (dict-ref map′-curried '(1 2 3)) '(2 3 4)
The nested structure of this dictionary makes it a trie where every argument is a "character."
> (dict-ref map′-curried '(10 11 12)) dict-ref: no value found for key '(10 11 12)
> (map′ f '(10 11 12)) '(11 12 13)
> (dict-ref map′-curried '(10 11 12)) '(11 12 13)
> (define counter (make-procedure (let ([state 0]) (λ () (set! state (add1 state)) state)))) > (begin (counter) counter) (procedure [ . 1 ])
> (begin (counter) counter) (procedure [ . 2 ])
> (define (memo f) (define g (make-procedure f)) (define absent (gensym)) (λ args (let go ([g g] [args args]) (match args [(list) g] [(cons arg args-rest) (let ([g′ (dict-ref g arg absent)]) (if (eq? g′ absent) (apply g args) (go g′ args-rest)))]))))
> (define filter′ (memo (λ (f xs) (displayln "cache miss") (filter f xs)))) > (filter′ integer? '(1 0.5 3)) cache miss
'(1 3)
> (filter′ (λ (x) (integer? x)) '(1 0.5 3)) '(1 3)
1 Reference
procedure
v : procedure?
procedure
(procedure-includes? x y) → boolean?
x : transparent-procedure? y : procedure?
The order imposed by procedure-includes? is precisely the same as that for function domains: f ⊑ g if and only if for all x in the domain of f, f(x) ⊑ g(x).
> (define abs′ (make-procedure abs)) > (void (abs′ 1) (abs′ -2)) > (procedure-includes? abs′ (λ (x) (if (> x 0) x (- x)))) #t
> (procedure-includes? abs′ values) #f
procedure
v : any/c
2 Prior Art
Higher-order memoization was first described by Ralf Hinze [Hinze 2000] as a passing remark. It was explored further by Conal Elliott in a series of blog posts, which became the foundation for a memoization library for Haskell. This library is an adaptation of those ideas in ways that are ergonomic for Racket.
Bibliography
| [Hinze 2000] | Ralf Hinze, “Memo functions, polytypically!,” Workshop on Generic Programming (WGP), 2000. https://www.cs.ox.ac.uk/people/ralf.hinze/publications/WGP00b.ps.gz |