Werid contract violation blame erros

58 views
Skip to first unread message

e...@disroot.org

unread,
Apr 7, 2021, 3:10:09 PM4/7/21
to Racket Users
Hello Racket users,

I am trying to understand a contract violation message that I am getting.
Here is the file a.rkt:

#lang racket
(provide f)
(define/contract (f a)
(-> boolean? any/c)
'())

and this is b.rkt:

#lang racket
(require "a.rkt")
(f 3)


I would expect that the caller is blamed for the contract violation, but the error message that I get is as follows:


f: contract violation
expected: boolean?
given: 3
in: the 1st argument of
(-> boolean? any/c)
contract from: (function f)
blaming: /home/epi/snippets/a.rkt
(assuming the contract is correct)
at: /home/epi/snippets/a.rkt:3.18
context...:
/usr/share/racket/collects/racket/contract/private/blame.rkt:347:0: raise-blame-error
/usr/share/racket/collects/racket/contract/private/arrow-higher-order.rkt:379:33
body of "/home/dan/snippets/blameme.rkt"

So, I am a bit surprised that the error message blames the file a.rkt.
What am I missing here?

Robby Findler

unread,
Apr 7, 2021, 4:35:11 PM4/7/21
to e...@disroot.org, Racket Users
The short answer: you probably should use (provide (contract-out....)) instead of define/contract.

The slightly longer answer: when you write a contract, you are not just describing what the legal inputs and outputs are, you are also establishing a *boundary* between two regions of code. In the case of define/contract, you are establishing a boundary between the function (here: f) and the module that it contains (here the file "a.rkt"). In the case of (provide (contract-out  ...)) the boundary is between a.rkt and b.rkt.

The slightly uncomfortable answer: it is my (not completely solid yet) opinion that the boundary that is drawn for define/contract is perhaps not the right / best one. In a theoretical/mathematical sense it is a completely fine and completely defensible one. But it trips people up sometimes. (There are examples others have posted in the past that are perhaps even stranger than yours but boil down to the same specific design choice for define/contract.)

Robby



--
You received this message because you are subscribed to the Google Groups "Racket Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to racket-users...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/racket-users/149cfc632cd666ff1a92177dce90296b%40disroot.org.

Dominik Pantůček

unread,
Apr 7, 2021, 4:47:39 PM4/7/21
to racket...@googlegroups.com
It is worth noting that it is relatively easy to implement a
define/provide/contract syntax without any problems. In my two most
active projects I use it extensively. In one case even with
scribble/srcdoc to keep the contracts, scribblings and implementation in
one place.

The simplest version:

(define-syntax (define/provide/contract stx)
(syntax-case stx ()
((_ (name args ...)
contract
body ...)
#'(begin
(provide (contract-out (name contract)))
(define (name args ...)
body ...)))))

Of course, this is not the best approach for all situations (and I do
not use it everywhere), but it covers many common use-cases well.


Dominik

On 07. 04. 21 22:34, Robby Findler wrote:
> The short answer: you probably should use (provide (contract-out....))
> instead of define/contract.
>
> The slightly longer answer: when you write a contract, you are not just
> describing what the legal inputs and outputs are, you are also
> establishing a *boundary* between two regions of code. In the case of
> define/contract, you are establishing a boundary between the function
> (here: f) and the module that it contains (here the file "a.rkt"). In
> the case of (provide (contract-out  ...)) the boundary is between a.rkt
> and b.rkt.
>
> The slightly uncomfortable answer: it is my (not completely solid yet)
> opinion that the boundary that is drawn for define/contract is perhaps
> not the right / best one. In a theoretical/mathematical sense it is a
> completely fine and completely defensible one. But it trips people up
> sometimes. (There are examples others have posted in the past that are
> perhaps even stranger than yours but boil down to the same specific
> design choice for define/contract.)
>
> Robby
>
>
>
> On Wed, Apr 7, 2021 at 2:10 PM epi via Racket Users
> <racket...@googlegroups.com <mailto:racket...@googlegroups.com>>
> <mailto:racket-users%2Bunsu...@googlegroups.com>.
> <https://groups.google.com/d/msgid/racket-users/149cfc632cd666ff1a92177dce90296b%40disroot.org>.
>
> --
> You received this message because you are subscribed to the Google
> Groups "Racket Users" group.
> To unsubscribe from this group and stop receiving emails from it, send
> an email to racket-users...@googlegroups.com
> <mailto:racket-users...@googlegroups.com>.
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/racket-users/CAL3TdONLMx%3Dy_cgfDsY_k9L9yaX_touO52phiK9scziW_jGrOA%40mail.gmail.com
> <https://groups.google.com/d/msgid/racket-users/CAL3TdONLMx%3Dy_cgfDsY_k9L9yaX_touO52phiK9scziW_jGrOA%40mail.gmail.com?utm_medium=email&utm_source=footer>.

David Storrs

unread,
Apr 7, 2021, 5:37:40 PM4/7/21
to Robby Findler, e...@disroot.org, Racket Users
I've always liked define/contract because it guarantees the safety of
the function from erroneous calls by other functions in the module,
which helps with debugging and testing. It sounds like you think
that's a bad move?
> To view this discussion on the web visit https://groups.google.com/d/msgid/racket-users/CAL3TdONLMx%3Dy_cgfDsY_k9L9yaX_touO52phiK9scziW_jGrOA%40mail.gmail.com.

Robby Findler

unread,
Apr 7, 2021, 7:22:41 PM4/7/21
to David Storrs, e...@disroot.org, Racket Users
No, I don't think it is a bad move if that's your goal! (I usually work at the file-level granularity but different code calls for different things.) I inferred from epi's message that that wasn't what was going on (perhaps incorrectly).

Robby

Raoul Duke

unread,
Apr 7, 2021, 7:28:26 PM4/7/21
to Robby Findler, David Storrs, e...@disroot.org, Racket Users
Clueless newb here. Wait, why can't we have both? As a joe programmer
on the street I would want the blame to be on b.rkt, and also on any
function calling f() incorrectly from inside a.rkt. Reading this
thread it sounds to me like that's not easily available?
> To view this discussion on the web visit https://groups.google.com/d/msgid/racket-users/CAL3TdOO3P5eYn_GW8jmKO82haXvXNbKWJodeRh1%2BZ58y3SH%3DsQ%40mail.gmail.com.

Robby Findler

unread,
Apr 7, 2021, 9:00:36 PM4/7/21
to Raoul Duke, David Storrs, e...@disroot.org, Racket Users
The idea is that a contract violation is not merely telling you "oh, someone said that you should get an integer? here and you didn't." That is what an assert macro might do. But contracts offer more.

Contracts are also giving you information to help you hone in on where the error actually is in your code. The rough idea is that you think of your program as partitioned into a bunch of pieces. And then contracts "stand guard" between those pieces, attempting to stop bogus stuff from crossing over from one piece to another piece. (Simplest form of "piece" for Racket: a file (containing a module).) So when you put a boundary between `f` and its containing module, you station guards there -- they tell you "something bogus came out of f" or "something bogus went into f". For the former/first one of those errors, it is clear: f is wrong. For the second, however, we have to decide -- who is this guard standing between? In this code, when we use define/contract, the other side of the boundary is the rest of the module containing f. But then, you say -- what happened with b.rkt?! Well, there was no guard on that boundary! So a.rkt didn't put anything to guard that boundary, so no checks happened and off the value went -- but now, hiding inside that value is the guard, and that guard knows only that boundary it started on, so when that bad input comes in, it blames a.rkt. a.rkt _could_ have protected itself (using contract-out to station a guard on the boundary between a.rkt and b.rkt, but it didn't --coulda shoulda woulda).

Anyway, that's the original idea, dating back to about 2002. More recently, Christos Dimoulas together with Lukas Lazarek has started an ambitious research program to try to actually verify that this way of erecting boundaries actually helps the programmer. Here's a paper on the topic if you're interested: https://dl.acm.org/doi/10.1145/3371133

Robby


Alex Harsányi

unread,
Apr 7, 2021, 9:03:17 PM4/7/21
to Racket Users
I think "define/contract" protects a function from other incorrect calls from the same module.  So your "define/provide/contract" is not really "define/contract" + "provide".  Here is an example illustrating the problem, where the call to "bar" correctly reports the contract violation, but the call to "foo" does not:

#lang racket
(define-syntax (define/provide/contract stx)
  (syntax-case stx ()
    ((_ (name args ...)
        contract
        body ...)
     #'(begin
         (provide (contract-out (name contract)))
         (define (name args ...)
           body ...)))))

(define/provide/contract (foo a)
  (-> integer? any/c)
  (printf "a = ~a~%" a))

(define/contract (bar a)
  (-> integer? any/c)
  (printf "a = ~a~%" a))

(foo "Hello")
(bar "Hello")

e...@disroot.org

unread,
Apr 9, 2021, 3:03:22 AM4/9/21
to Robby Findler, Racket Users
Thank you, Robby,

This clarifies it a little bit. So far I've avoided reading about the racket module system in details, but I think now is as good time as ever.
Reply all
Reply to author
Forward
0 new messages