Scheme provides a limited degree of data abstraction for constructs such as
(lambda () (let ((c 0))(cons
  (lambda () (set! c (+ c 1)))
  (lambda () c))))
provides two functions that provide limited access to a count so that one can be sure that c will not become negative. You might call this unary abstraction. Often you need functions that take more than one argument of the same abstraction—a function that can see thru the abstraction of both arguments. I do not know how to do this efficiently in Scheme except perhaps the seal which provides fairly efficiently abstractions of sets so as to be able to combine two abstracted sets to produce another. The box, below, provides a rather more efficient primitive facility.

The following code achieves such abstraction but leaks allocation information.

(lambda (n) (let ((reg (make-vector 100 0))(vals (make-vector 100 0))(ac 0))
  (let* ((valid (lambda (x) (and (pair? x) (let ((n (car x))) (and
    (integer? n) (< n 100) (not (negative? n)) (eq? (vector-ref reg n) x))))))
    (alist (list
  (cons 'mc (lambda () (if (< ac 100) (let ((no (cons ac 0)))
      (vector-set! reg ac no) (set! ac (+ ac 1)) no))))
  (cons 'inc (lambda (x) (if (valid x)
      (vector-set! vals (car x) (+ 1 (vector-ref vals (car x)))))))
  (cons 'read (lambda (x) (if (valid x) (vector-ref vals (car x)))))
  (cons 'tran (lambda (x y) (and (valid x) (valid y)
      (vector-set! vals (car x) (vector-ref vals (car y)))))))))
  (lambda (sy) (let ((a (assq sy alist))) (and (pair? a) (cdr a)))))))
If T is a value produced by the above function, then ((T 'mc)) yields a pair and allocates one of the n counters. The abstracted state can then be accessed by code placed to have access to the array vals. The pair P can subsequently be used thus ((T 'inc) P) to increment the abstracted counter associated with the pair P. The holder of P has access to the allocation count (car P) which may leak information that should not be available to the holder. ((T 'inc) P) reads the counter. All of this could be done by the first pattern but the 2nd pattern can transfer one count value to another as in ((T 'tran) P Q).

Lists could be used in place of arrays and this eliminates the leak but then an overhead proportional to the number of counters would occur. Efficiently searchable data structures can be used but then there is a combination of space, time and complexity overhead not suffered by conventional OO languages.

The Box

Here is the simplest addition to Scheme I know that provides efficient data abstraction. The box is a new primitive type like the cons cell except there is neither car nor cdr for it. bcons is a new primitive procedure like cons except it produces a box instead of a cons cell. There is an op bcdr of two arguments. The following is always true:
(eq? (bcdr (bcons a b) a) b)
(bcdr (bcons a b) c) yields #f unless (eq? a c). The holder of the first value in the box is able to extract the 2nd value from any box that he finds. The above counter application can be recast:
(lambda () (let* ((key (cons 0 0)) (alist (list
   (cons 'mc (lambda () (bcons key 0)))
   (cons 'inc (lambda (x) (and (box? x) (let ((s (bcdr x key)))
       (and s (bcons key (+ 1 s)))))))
   (cons 'read (lambda (x) (and (box? x) (bcdr x key))))
   (cons 'tran (lambda (x y) (and (box? x)(box? t) (bcons key (+ (bcdr x key)(bcdr y key)))))))))
 (lambda (sy) (let ((a (assq sy alist))) (and (pair? a) (cdr a))))))
The new program has somewhat different semantics. It is purely functional, has no worry-some limit to hit, GC works on the storage, etc.

This pattern has been called the sealer-unsealer pattern which I discuss here. The box supports the other synergy patterns efficiently as well.


Scheme sealers?
(lambda () (let ((key (cons 0 0))) (cons
   (lambda (x) (bcons key x)) ; sealer
   (lambda (x) (and (box? x) (bcdr x key)))))) ; unsealer