[plt-dev] overriding constructor style printing

From: Felix Klock's PLT scheme proxy (pltscheme at pnkfx.org)
Date: Tue Mar 3 17:17:16 EST 2009

Matthew (cc'ing plt-dev)-

On Mar 3, 2009, at 4:00 PM, Matthew Flatt wrote:

> I think you want to implement the `prop:print-convert-constructor- 
> name'
> property:
> #lang scheme
> (provide external)
> (require mzlib/pconvert-prop)
> (define-struct internal (value) #:transparent
>   #:property prop:print-convert-constructor-name 'external)
> (define (external v) (make-internal v))

I looked into that option, but it is not sufficiently general for my  

I think reducing my issue down to a simpler test case for presentation  
to the list was a mistake; I've corrected that mistake at the end of  
this email.


I do not want to expose the internal structure at all; I want an  
abstract view of the fields it contains.  In general, the designer of  
an abstract data type wants to only provide an abstract view of that  
type.  Sometimes that goal conflicts with constructor-style printing,  
but not always.

In my particular case, I am trying to develop a multiset abstraction,  
The primary constructor for bag (or ``pseudo-constructor'' if you  
prefer) is a *procedure* and not one of the constructors resulting  
from a define-struct special form.  The pseudo-constructor for bag has  
an interface analogous to that of the list procedure.

Internally a bag is represented by a structure, unordered, with one  
field (which itself holds a list).  But I do not want my external  
student clients to see that view; I want them only to see an  
expression involving the bag pseudo-constructor, and the exported  
operations on the bag abstraction.


So, back to my question: "if I am developing a teachpack that is  
intended for use with the Student languages (Beginning Student et al),  
how do I override how structures I define are printed?"

I have attached the code I have below so you can see concretely what I  
am talking about.  The end goal here is that I want the following to  
work as follows at the Interactions Window:
 > (bag 1 2)
(bag 1 2)

but I cannot figure out how to get that effect in the Student  
languages.  (I *can* get it via the mzlib/pconvert library alone.)  At  
this point I have employed the prop:print-convert-constructor-name  
property to implement the following approximation:
 > (bag 1 2)
(make-bag (list 1 2))

but this does not satisfy me.


Here is the relevant code:

;;; FILE: bag.ss
#lang scheme
(provide bag bag? bag-contains?
          bag-choose bag-choose-first bag-choose-rest
          bag-length bag-union bag-map bag-fold

(require mzlib/pconvert)
(require mzlib/pconvert-prop)

;; A [Bagof X] is a (make-unordered [Listof X])

;; interpretation:
;; a (make-unordered (list x1 x2 .. xN) is a multiset containing N  

(define-struct unordered (elems)
   #:property prop:equal+hash
   (let ((hasher (lambda (b hash)
                   (foldr (lambda (x n) (bitwise-ior (hash x) n)) 0))))
     (list (lambda (b1 b2 equal?)
             (and (= (bag-length b1) (bag-length b2))
                  (subbag? b1 b2)
                  (subbag? b2 b1)))

   #:property prop:custom-write
   (lambda (b p write-mode)
     (let ((p* (open-output-string)))
       (if write-mode
           (write (unordered-elems b) p*)
           (display (unordered-elems b) p*))
       (write-string "#<bag " p)
       (write-string (get-output-string p*)
       (write-string ">" p)))

   #:property prop:print-convert-constructor-name 'make-bag

  (let ((old-hook (current-print-convert-hook)))
    (lambda (v basic-convert sub-convert)
      (if (bag? v)
          `(bag ,@(map sub-convert (unordered-elems v)))
          (old-hook v basic-convert sub-convert)))))

(define (bag? x)
   (unordered? x))

;; subbag? : [Bagof X] [Bagof X] -> Boolean
(define (subbag? b1 b2)
   (andmap (lambda (x) (bag-contains? b2 x))
           (unordered-elems b1)))

;; bag-contains? : [Bagof X] X -> Boolean
(define (bag-contains? b x)
   (not (not (member x (unordered-elems b)))))

;; bag : X ... -> [Bagof X]
(define (bag . l)
   (make-unordered l))

;; bag-choose : [Bagof X] -> (values X [Bagof X])
(define (bag-choose b)
   (values (car (unordered-elems b))
           (make-unordered (cdr (unordered-elems b)))))

;; bag-choose-first : [Bagof X] -> X
(define (bag-choose-first b)
   (call-with-values (lambda () (bag-choose b)) (lambda (x b*) x)))

;; bag-choose-rest : [Bagof X] -> [Bagof X]
(define (bag-choose-rest b)
   (call-with-values (lambda () (bag-choose b)) (lambda (x b*) b*)))

;; bag-length : [Bagof X] -> Nat
(define (bag-length b)
   (length (unordered-elems b)))

;; bag-union : [Bagof X] ... -> [Bagof X]
(define (bag-union . l)
   (make-unordered (append-map unordered-elems l)))

;; bag-map : (X -> Y) [Bagof X] -> [Bagof Y]
(define (bag-map f b)
   (make-unordered (map f (unordered-elems b))))

;; bag-append-map : (X -> [Bagof Y]) [Bagof X] -> [Bagof Y]
(define (bag-append-map f b)
   (foldr bag-union (bag) (map f (unordered-elems b))))

;; bag-fold : (X Y -> Y) Y [Bagof X] -> Y
(define (bag-fold f z b)
   (foldr f z (unordered-elems b)))

;; build-bag : Nat (Nat -> X) -> [Bagof X]
(define (build-bag n f)
   (make-unordered (build-list n f)))

;;; END OF FILE bag.ss

;; The first three lines of this file were inserted by DrScheme. They  
record metadata
;; about the language level of this file in a form that our tools can  
easily process.
#reader(lib "htdp-intermediate-lambda-reader.ss" "lang")((modname  
arrangements) (read-case-sensitive #t) (teachpacks ((lib "world.ss"  
"teachpack" "htdp"))) (htdp-settings #(#t constructor repeating- 
decimal #f #t none #f ((lib "world.ss" "teachpack" "htdp")))))
;;; FILE: arrangements.ss

(require "bag.ss")

;; A Word is a [Listof Symbol]

;; all-placements : Symbol Word -> [Bagof Word]
(check-expect (all-placements 's '()) (bag '(s)))
(check-expect (all-placements 's '(a)) (bag '(s a) '(a s)))
(check-expect (all-placements 's '(a b)) (bag '(s a b) '(a s b) '(a b  
(define (all-placements s w)
     ((empty? w) (bag (list s)))
     (else  (bag-union
             (bag (cons s w))
             (bag-map (lambda (w*) (cons (first w) w*))
                      (all-placements s (rest w)))))))

;; arrangements : Word -> [Bagof Word]
;; produces all permutations of a-word
(check-expect (arrangements empty)        (bag empty))
(check-expect (arrangements (list 'd))    (bag (list 'd)))
(check-expect (arrangements (list 'c 'd)) (bag (list 'c 'd) (list 'd  
(check-expect (arrangements (list 'c 'd)) (bag (list 'd 'c) (list 'c  
(define (arrangements x)
     ((empty? x) (bag empty))
     (else (bag-fold (lambda (w* b)
                       (bag-union (all-placements (first x) w*)
                     (arrangements (rest x))))))

;;; END OF FILE arrangements.ss

Posted on the dev mailing list.