0
votes

Preamble: I don't really know what I'm doing. I'm new to racket and programming. I'm a slow learner.

I installed DrRacket, and I'm messing around with "racket/gui/base".

I want to put containees into objects that are not containers.

Contracts are preventing me from doing this.

My first idea is to find ways to break contracts or modify contracts.

  1. Is that a fruitful avenue to pursue?
  2. Will it take too long?

I'm starting to read this: https://docs.racket-lang.org/guide/contract-boundaries.html

  1. Is that a waste of time given that I want to solve a problem like the following below problem?

For example, let's say that I want to put a button in a menu instead of a menu item.

  1. What would I do to avoid a contract violation in the above case?

  2. Could I generalize that solution (to 4), or would it be particular to those objects?

  3. Can I cast objects in racket to avoid this contract issue?

2
It would be more helpful to know what you actually want to do. Why do you want to break contracts?Sorawee Porncharoenwase

2 Answers

3
votes

For example, let's say that I want to put a button in a menu instead of a menu item.

In that case, the answer is that your pursuit is not a fruitful avenue.

The idea behind a menu object is hold "stuff" that will be sent to the underlying GUI to be drawn on screen. The various GUIS (macOS, Linux, Windows) do not allow arbitrary elements to be drawn in a menu, so the GUI layer in Racket must check that a menu only holds menu items that makes sense. So let's say you managed to bypass the contract checker and passed a button along as a menu item. Eventually that menu item is to be passed from the Racket GUI layer to the OS and that point you will run into an error - most likely a crash (the program halts with a core dump).

In order words: the contracts are in place to make sure, that menu items are things that makes sense to place in a menu. It prevents you from accidentally storing objects of the wrong type in a menu.

3
votes

It's important to be clear what contracts are for. Like a legal contract, Racket's contracts do two things:

  • they tell all the parties to the contract what they must do to comply with the contract;
  • they tell all the parties to the contract what they can assume as a result of the contract.

Both of these aspects are important, but the second one is more important here. As an example consider the factorial function:

(define (factorial n)
  (if (= n 0)
      1
      (* n (factorial (- n 1)))))

This all seems to be fine:

> (factorial 10)
3628800
> (factorial 100)
93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000

Except it's not fine, at all:

> (factorial 12.2)

and there's a long pause and then at some point Racket runs out of memory.

Well, that's because when I wrote factorial I wrote it with an understanding about what the domain and range of the factorial function are: for this version of the factorial function its domain is the natural numbers (integers greater than or equal to 0) and its range is integers greater or equal to 1. And the implementation relies critically on a property of natural numbers: a natural number n is either zero, or, if you subtract 1 from it enough times, that number is zero.

When I called factorial I called it with a number which is not in its domain, but it didn't know that, so it simply failed to terminate. Well, we can solve this by providing a contract for factorial. I'll do this by providing the contract directly on the function rather than at the module level, because it means I have to type less:

(define/contract (factorial n)
    (-> natural-number/c (integer-in 1 #f))
    (if (= n 0)
        1
        (* n (factorial (- n 1)))))

And now

> (factorial 10)
3628800
> (factorial 12.2)
; factorial: contract violation
;   expected: natural-number/c
;   given: 12.2
;   [...]

Now there's a contract which protects factorial from me. Unfortunately it also protects it from itself; each time it calls itself it has to dutifully check its own contract. Well, we can avoid that:

(define/contract (factorial n)
  (-> natural-number/c (integer-in 1 #f))
  (define (floop m r)
    (if (= m 0)
        r
        (floop (- m 1) (* r m))))
  (floop n 0))

within factorial, I can assume things about the contract, and things don't need to be obsessively checked.

Except I made a mistake:

> (factorial 10)
; factorial: broke its own contract
;   promised: exact-positive-integer?
;   produced: 0

So now the contract on factorial is protecting me from a buggy implementation. Code I write which uses factorial can safely assume that its return value will be an integer greater or equal to 1. And I can fix that, of course:

(define/contract (factorial n)
  (-> natural-number/c (integer-in 1 #f))
  (define (floop m r)
    (if (= m 0)
        r
        (floop (- m 1) (* r m))))
  (floop n 1))

OK, so this has been a long preamble to say this:

  • contracts place obligations on the parties to them;
  • contracts allow the parties to them to make assumptions.

In particular, it is almost certainly the case that containers make assumptions that the objects being put into them are containees, and have the behaviour of containees, because they know that there is a contract in place which makes that be true.

If, somehow, you manage to circumvent that contract, then what will happen is that the container will still assume it's true, and still make the same assumptions. And the result will be disaster of some kind: if you're lucky you'll get errors, if you're not lucky there will just be trash on the screen or the program will crash (or, worst of all, not crash, but fill its memory with crap).

So the answer is, in brief: that contract exists for a reason, and if you want to place something into a container, you need to make sure it's a containee, and that it is not lying about being a containee – it needs to actually implement the behaviour of a containee properly, because (almost certainly) the container will be relying on that behaviour to work. That's what contracts are for.