Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When is ->i actually useful in racket?

I've been going through Contracts in the Racket Guide.

The ->i construct allows one to place arbitrary constraints on the input/output of a function.

For example, I could have an unzip function that takes a list of pairs and returns two lists. Using contracts, I could confirm that every element of the in-list is a pair and that the out-lists have the corresponding elements.

The racket guide hints that this is when contracts are useful. But it seems like this would be better done inside the function itself. I could throw an error if I encounter a non-pair, this would check the in-list. The output is automatically checked by having a correct function.

What is a concrete example of where code is improved somehow by a contract more complex than simple types?

like image 810
Luke Miles Avatar asked May 26 '15 17:05

Luke Miles


People also ask

What is DrRacket used for?

DrRacket IDE The editor provides highlighting for syntax and run-time errors, parenthesis matching, a debugger and an algebraic stepper.

Is learning Racket hard?

So it is easy to learn, because there is a lot of documentation, but it is also hard, because you need to actually read it. You cannot pick up Racket in a few weekends. The good news is that some people will find that exciting, a perfect chance to learn a new family of languages.

What is expt in Racket?

expt : (number number -> number) Purpose: to compute the power of the first to the second number. floor : (real -> integer) Purpose: to determine the closest integer (exact or inexact) below a real number.

Is DrRacket object oriented?

While Racket originated in the functional world of programming languages, it also is a full-fledged class-based, object-oriented programming language.


1 Answers

As you have described, pretty much any check that can be performed in ->i can be performed within the function itself, but then again, any check performed by contracts can, for the most part, be performed within functions themselves. Encoding the information into a contract provides a few advantages.

  1. You can extract the invariants out of the function implementation. This is nice because you don't need to clutter the function itself with guard clauses, you can just write code and know that invariants will be maintained by the contract.
  2. Contracts work hard to provide good error reporting. They'll automatically assign "blame" to the party that violates the contract, and for complex contracts, they'll add context to the error messages to make it as clear as possible what the problem is.

These are most apparent with ->i when the contract needs to specify dependencies within arguments supplied to the function. For example, I have a collections library, which includes a subsequence function. It takes three arguments, a sequence, a start index, and an end index. This is the contract I use to protect it:

(->i ([seq sequence?]
      [start exact-nonnegative-integer?]
      [end (start) (and/c exact-nonnegative-integer? (>=/c start))])
     [result sequence?])

This allows me to explicitly specify that the end index must be greater than or equal to the start index, and I don't have to worry about checking that invariant within my function. When I violate this contract, I get a nice error message:

> (subsequence '() 2 1)
subsequence: contract violation
  expected: (and/c exact-nonnegative-integer? (>=/c 2))
  given: 1
  which isn't: (>=/c 2)

It can be used to ensure more complex invariants, too. I also define my own map function, which, like Racket's built-in map, supports variable numbers of arguments. The procedure supplied to map must accept the same number of arguments as there are sequence provided. I use the following contract for map:

(->i ([proc (seqs) (and/c (procedure-arity-includes/c (length seqs))
                          (unconstrained-domain-> any/c))])
     #:rest [seqs (non-empty-listof sequence?)]
     [result sequence?])

This contract ensures two things. First of all, the proc argument must accept the same number of arguments as there are sequences, as mentioned above. Additionally, it also demands that that function always returns a single value, since Racket functions can return multiple values.

These invariants would be much harder to check inside the function body because, especially with the second invariant, they must be delayed until the function itself is applied. It must also be checked with every invocation of the function. Contracts, on the other hand, wrap the function and handle this automatically.

Do you always want to encode every single invariant of a function into a contract? Probably not. But if you want that extra level of control, ->i is available.

like image 191
Alexis King Avatar answered Nov 16 '22 01:11

Alexis King