[racket] contracts on functions that take arbitrary keyword arguments

From: Alexander D. Knauth (alexander at knauth.org)
Date: Sat Dec 28 19:08:34 EST 2013

This is my first try at making something like a make-keyword-procedure- 
contract.  Any suggestions or things I did wrong?
By the way it doesn't seem like a good idea to put separate contracts  
on the kws and kw-args, but that's what I did for now because I don't  
need to check anything about the keywords.  Also, the error messages  
aren't the best.

#lang racket

(require rackunit)

(define ((make-keyword-procedure-contract-proj kws-contract-proj kw- 
args-contract-proj rest-contract-proj range-contract-proj) blame)
   (lambda (f)
     (unless (procedure? f)
       (raise-blame-error blame f
        '(expected: "procedure?" given: "~e") f))
     (make-keyword-procedure
      (lambda (kws kw-args . rest-args)
        (let ([kws-blame (blame-add-context blame "the keyword list  
of" #:swap? #t)]
              [kw-args-blame (blame-add-context blame "the kw- 
arguments list of" #:swap? #t)]
              [rest-args-blame (blame-add-context blame "the arguments  
list of" #:swap? #t)]
              [range-blame (blame-add-context blame "the rage of")])
          (call-with-values (lambda () (keyword-apply f
                                                      ((kws-contract- 
proj kws-blame) kws)
                                                      ((kw-args- 
contract-proj kw-args-blame) kw-args)
                                                      ((rest-contract- 
proj rest-args-blame) rest-args)))
                            (range-contract-proj range-blame)))))))

(define (make-keyword-procedure-contract kws-contract kw-args-contract  
rest-contract range-contract)
   (make-contract #:name `(make-keyword-procedure-contract ,kws- 
contract ,kw-args-contract ,rest-contract ,range-contract)
                  #:first-order (lambda (f) (procedure? f))
                  #:projection (apply make-keyword-procedure-contract- 
proj
                                      (map contract-projection
                                           (list kws-contract kw-args- 
contract
                                                 rest-contract range- 
contract)))))

(define-syntax match?/c
   (syntax-rules ()
     [(match?/c pat)
      (flat-named-contract '(match?/c pat)
                           (lambda (x)
                             (match x
                               [pat #t]
                               [_ #f])))]
     [(match?/c pat #:when condition)
      (flat-named-contract '(match?/c pat #:when condition)
                           (lambda (x)
                             (match x
                               [pat #:when condition #t]
                               [_ #f])))]
     ))


(with-handlers ([exn:fail:contract? (compose1 displayln exn-message)])
   (define/contract not-a-function (make-keyword-procedure-contract  
(listof keyword?) list?
                                                                     
list?
                                                                     
(const #t))
     "not-a-function")
   (void))

(newline)

(define/contract contracted-apply (make-keyword-procedure-contract  
(listof keyword?) list?
                                                                     
(match?/c (list (? procedure? f) arg ... (? list? rest-args)))
                                                                     
(const #t))
   (make-keyword-procedure
    (lambda (kws kw-args f . rest-args)
      (keyword-apply f kws kw-args (apply list* rest-args)))))

(check-equal? (contracted-apply + (list 1 2 3))
               6)

(define (kinetic-energy #:mass m #:velocity v)
   (* 1/2 m (sqr v)))

(check-equal? (contracted-apply kinetic-energy #:mass 2 #:velocity 1  
'())
               1)

(with-handlers ([exn:fail:contract? (compose1 displayln exn-message)])
   (contracted-apply 5 (list 1 2 3 4)))

On Dec 28, 2013, at 4:46 PM, Robby Findler wrote:

> http://docs.racket-lang.org/reference/Building_New_Contract_Combinators.html?q=racketcontract
>
> (But there is a new interface (the old one will continue to work of  
> course) in the git version)
>
> Let me know if you have questions or get stuck and I'll try to  
> improve the docs.
>
> Robby
>
>
> On Sat, Dec 28, 2013 at 3:40 PM, Alexander D. Knauth <alexander at knauth.org 
> > wrote:
> What's the lower-level, projection-based api and how do I use it?
>
> On Dec 28, 2013, at 11:50 AM, Robby Findler wrote:
>
>> I think you probably could make a contract that did what you were  
>> talking about in the first message in this thread, but you cannot  
>> do it with any of the existing contract combinators (->, ->*, case- 
>> >, ->i, etc). You would have to use the lower-level, projection- 
>> based api to make a new combinator.
>>
>> But Matthias's advice is probably a better route.
>>
>> Robby
>>
>>
>> On Sat, Dec 28, 2013 at 1:43 AM, Alexander D. Knauth <alexander at knauth.org 
>> > wrote:
>> The problem isn't with defining the function, the problem is with  
>> making a contract for it.
>>
>> I can define the function fine, I just don't know how to write a  
>> contract for it.  I used make-keyword-procedure to define the  
>> function, but is there something like make-keyword-procedure- 
>> contract?  I could write my own error code, but it would be better  
>> if I could express it as a contract.
>>
>> The actual concrete example is that I'm making my own version of  
>> send that works with method-like procedures.
>>
>> #lang racket
>>
>> (define/contract current-send-object (parameter/c (or/c object? #f))
>>   (make-parameter #f))
>>
>> (define my-send
>>   (make-keyword-procedure
>>    (lambda (kws kw-args object proc . rest-args)
>>      (parameterize ([current-send-object object])
>>        (keyword-apply proc kws kw-args rest-args)))))
>>
>> (define point%
>>   (class object%
>>     (super-new)
>>     (init-field [x 0] [y 0])
>>     (define/public (get-x) x)
>>     (define/public (get-y) y)))
>>
>> (define (get-vector)
>>   (let* ([object (current-send-object)]
>>          [x (send object get-x)]
>>          [y (send object get-y)])
>>     (vector x y)))
>>
>> (define p (new point% [x 3] [y 5]))
>>
>> (require rackunit)
>>
>> (check-equal? (my-send p get-vector)
>>               (vector 3 5))
>>
>> (check-equal? (my-send p (compose vector->list get-vector))
>>               (list 3 5))
>>
>> that way you can define new "methods" without them being specified  
>> by the class, and they're first class functions.  I wanted to be  
>> able to make a contract for my-send (and my-send/apply etc.) that  
>> would check that object is an object and that proc is a procedure,  
>> but still take arbitrary keyword arguments and just pass them on to  
>> proc.
>>
>> On Dec 27, 2013, at 1:03 PM, Matthias Felleisen wrote:
>>
>>>
>>> This is a bit vague. Can you clarify the question with a concrete  
>>> example?
>>>
>>>
>>> On Dec 26, 2013, at 7:45 PM, "Alexander D. Knauth" <alexander at knauth.org 
>>> > wrote:
>>>
>>>> I want to make contracts on some apply-like functions that check  
>>>> some arguments but just passes all the others (including keyword  
>>>> arguments) on to a function (provided as an argument).  If there  
>>>> weren't any keyword arguments, I could use a rest argument to do  
>>>> this, but that wouldn't work with keyword arguments.  Is there a  
>>>> way to do something like a rest argument for keyword-arguments in  
>>>> a contract?____________________
>>>> Racket Users list:
>>>> http://lists.racket-lang.org/users
>>>
>>
>>
>> ____________________
>>   Racket Users list:
>>   http://lists.racket-lang.org/users
>>
>>
>
>

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.racket-lang.org/users/archive/attachments/20131228/c35ca04c/attachment-0001.html>

Posted on the users mailing list.