When would I use boxes?

I'm enjoying the time I'm spending learning Racket and Scheme. One thing that has puzzled me is the topic of boxes. The guide says that "A box is like a single-element vector". Okay, but why would I want to use a box rather than a global variable? The box-cas! function is particularly strange, with it's reference to threads and futures. I get the feeling that this is something quite important to low-level memory, and I worry that I'm missing something important when I have absolutely no idea how or why I would ever need to use a box. Can some helpful racketeer enlighten me?

Thanks in advance.

3 Likes

Boxes can be used to pass arguments by reference in function calls:

#lang racket
(define (foo abox)
  (set-box! abox (- (unbox abox))))

(define xbox (box 3))
(foo xbox)
(unbox xbox) ; -3

There are a few uses in racket/gui, but their use in general is pretty rare.

4 Likes

You may be surprised to learn that set! is implemented using boxes. That is, code such as:

(let ([a 1])
  (set! a 2)
  (+ a 1))

Is translated into:

(let ([a (box 1)])
   (set-box! a 2)
   (+ (unbox a) 1))

You will often hear that mutable code is slower in Racket, and this is the reason why: every time you use set! the compiler has to box the variable and unbox it everywhere it is used -- this severely reduces the opportunities for optimizations.

As for box-cas! , a lot of synchronization mechanisms can be implemented using a compare-and-swap operation -- most processors provide a single machine instruction for this.

Boxes are another "under-the-hood" feature, less useful to the application programmer, but useful to language developers.

For a related discution, see What are examples of use-cases for continuations?

Alex.

8 Likes

Excellent. Thanks very much!

Cool. Thanks very much!

Oh interesting. A long time ago I came across a Stack Overflow post that asked something along the lines of "If Chicken Scheme compiles to C why is it slower than C?" And I believe one of responders mentioned something about boxed types.

An example wherein a Scheme compiler produces C that runs faster than the equivalent C program.

https://groups.google.com/g/comp.lang.lisp/c/7kgeNUp8jMw/m/r52D3S66AZgJ

(Featuring Stalin Scheme)

2 Likes

I came across Stalin years ago. I found it's approach to optimization interesting. The whole program analysis thing seems pretty cool. My understanding is that the trade off here is that the debugging and the compile times were quite large. It is also only implements R4RS.

This raises two questions for me.

  1. I wonder how the Stalin Scheme example would fare against Rust, C, and C++ today and if it would still be faster than the idiomatic way to do it in those languages
  2. I wonder if to get around the debugging and compile time issues related to Stalin Scheme, if writing the initial program in another R4RS compliant Scheme, then only once all the debugging and iteration was exhausted there, going to Stalin for the final pass would be a better workflow

In regards to #2 I've always been interested in how much of the Racket Programs I write are not portable to other Scheme Implementations. And what the Scheme ecosystem would look like if more third party libaries were written in portable scheme that could be used across all the major Schemes.

How about an immutable hash with immutable keys and mutable boxes for its values? Immutable hashes are handy in recursive procedures. Using boxes the values can be mutated though. I have used such hashes for environments in toy interpreters that implement set!.

1 Like

Reminds me of the fast-multiply story from https://legacy.cs.indiana.edu/~dfried/mex.pdf

3 Likes