Notes from Lecture 2 – On interfaces
1 Syntactic interfaces
2 Behavioral interfaces

Notes from Lecture 2 – On interfaces

Interfaces aim to communicate sufficient information about a component so that a client can use it correctly. This last part has two sides (a) a client should “know” what operations a component provides and how the client is expected to call them so that (b) the operations return the correct results that the client “anticipates” if the component is correct. In other words, an intefrace describes the obligations of a component’s clients and the benefits the component promises in return. Of course, the benefits for the clients are obligations of the component given that the clients live up to their obligations.

The above description defines of spectrum of information that a component writer can include in an interface. In this course we will look at three levels of information:

Let’s see what the first two of these levels mean in concrete terms with the simple example of the component for grocery store products we discussed in class.

1 Syntactic interfaces

Here is a basic interface for a product:

(define product<%>
  (interface ()
    has-discount?
    value
    value-with-discount))

This tells us that our component is a class with three methods. This description of the interface is syntactic as it restricts the syntax of how clients interact with the component.

Most languages warn developers if they attempt to implement a component that is supposed to follow such an interface but do not implement all the promised methods:
> (define apple%
    (class* object% (product<%>)
  
      (super-new)
  
      (init-field quantity-in-pounds)
  
      (field [price-per-pound-in-dollars 3]
             [discount -0.1]
             [offer-expiration 100000000000])
  
      (define/public (value)
        (* quantity-in-pounds  price-per-pound-in-dollars))))

class*: missing interface-required method

  method name: value-with-discount

  class name: apple%

  interface name: product<%>

Indeed good IDEs warn developers about such mistakes before they even press the “compile” or “run” button.

The basic interface is missing important information:
  • How many arguments an operation expects?

  • What are the datatypes of these arguments?

  • What is the datatype of the operation’s result?.

This information also belongs to the syntactic level as we can validate whether components and their clients respect it from the syntax of a program (in most cases with the help of some extra annotations). Types, which are validated at compile time by analyzing the text of a program, are an example framework of how syntactic interfaces and their validation materializes in programming languages.

In languages without static typing, such as Racket, we can still state and validate syntactic interfaces with contracts. For example, here is a class apple% that implements the product<%> interface:
(define apple%
  (class* object% (product<%>)
 
    (super-new)
 
    (init-field quantity-in-pounds)
 
    (field [price-per-pound-in-dollars 3]
           [discount -0.1]
           [offer-expiration 100000000000])
 
    (define/public (value)
      (- (* quantity-in-pounds  price-per-pound-in-dollars)))
 
    (define/public (value-with-discount)
      (+ (value) (* (value) discount)))
 
    (define/public (has-discount?)
      (>= offer-expiration (date->seconds (current-date))))))
followed by a syntactic contract:
(define syntactic-apple/c
  (class/c
   [has-discount? (-> (is-a?/c product<%>) boolean?)]
 
   [value (-> (is-a?/c product<%>) number?)]
 
   [value-with-discount (-> (is-a?/c product<%>) number?)]))
The contract says that apple has methods has-discount?, value and value-with-discount that each expects one argument that implements the product<%> (in fact the argument is implicit and corresponds to the receiver of a method invocation accessible as this in the body of the method). In addition the contract specifies that the first method promises to return a boolean and the other two a number.

2 Behavioral interfaces

In contrast to types, contracts are not validated when we compile a program but when we run it. As a result, they cannot validate if the methods of apple% meet their syntactic interface for every possible argument but only for specific ones provided in the program we run.

This may look as a disadvantage compared to types but at the same time it allows contracts to express interfaces that are not just syntactic but behavioral. Behavioral interfaces describe the runtime properties of the values a component and its clients exchange. For example, we can strengthen the interface for apple% to state that value returns a positive number rather than any number and that the result of value-with-discount is less or equal to the result of value for the same receiver (->i is a contract constructor that permits us to give names to the arguments and the result of a method and use them when describing their properties):
(define behavioral-apple/c
  (class/c
   [has-discount? (-> (is-a?/c product<%>) boolean?)]
 
   [value (-> (is-a?/c product<%>) positive?)]
 
   [value-with-discount
    (->i ([this (is-a?/c product<%>)])
         (result (this) (<=/c (send this value))))]))

Notice that these properties of the results of the two methods are described with ordinary code together with bits of special notation; positive? is an ordinary predicate that we could replace with any other we define and (send this value) is a Racket expression that could show up anywhere in a program. In a sense contracts give to developers some power that language implementors have been keping for themselves. With contracts developers can create their own “vocabulary” for stating and validating the interfaces of components without being restricted by the choices of “vocabulary” of the language implementors (e.g., Integer, Float, List<T>).

With the contract defined, we can declare in our program that we would like to use it as the contract for apple%, instantiate the contracted version of the class, apple%-with-a-behavioral-interface, and invoke value on the object we get back:
> (define/contract  apple%-with-a-behavioral-interface behavioral-apple/c
    apple%)
> (define an-apple-with-a-behavioral-interface
    (new apple%-with-a-behavioral-interface
         [quantity-in-pounds 6]))
> (send an-apple-with-a-behavioral-interface value)

value: broke its own contract

  promised: positive?

  produced: -18

 blaming: (definition apple%-with-a-behavioral-interface)

   (assuming the contract is correct)

The contract violation is due to a silly mistake in the definition of apple% and the error message helps us identify the bug. It tells us that the issue is with the contracted class itself (the blaming line) and that value didn’t live up to its promises and produced the wrong result (indeed the method implementation negates the price it computes). If we fix this bug, the contract violation goes away:

(define fixed-apple%
  (class* object% (product<%>)
 
    (super-new)
 
    (init-field quantity-in-pounds)
 
    (field [price-per-pound-in-dollars 3]
           [discount -0.1]
           [offer-expiration 100000000000])
 
    (define/public (value)
      (* quantity-in-pounds  price-per-pound-in-dollars))
 
    (define/public (value-with-discount)
      (+ (value) (* (value) discount)))
 
    (define/public (has-discount?)
      (>= offer-expiration (date->seconds (current-date))))))
> (define/contract  fixed-apple%-with-a-behavioral-interface behavioral-apple/c
    fixed-apple%)
> (define a-fixed-apple-with-a-behavioral-interface
    (new fixed-apple%-with-a-behavioral-interface
         [quantity-in-pounds 6]))
> (send a-fixed-apple-with-a-behavioral-interface value)

18

So far so good, but what if unlike Racket our language does not support contracts? The answer is that we can use assertions to obtain the same effect. Indeed this is how contracts started; as a methodology for splicing assertions in the bodies of methods in a systematic manner (this is the so called Design by Contract methodology). Here is how we can modify the apple% class to achieve this:
(define apple+assertions%
  (class* object% (product<%>)
 
    (super-new)
 
    (init-field quantity-in-pounds)
 
    (field [price-per-pound-in-dollars 3]
           [discount -0.1]
           [offer-expiration 100000000000])
 
    (define/public (has-discount?)
      (let ([result
             (>= offer-expiration (date->seconds (current-date)))])
        (if (boolean? result)
            result
            (raise "assertion violation: method doesn't live up to its promises"))))
 
    (define/public (value)
      (let ([result
             (- (* quantity-in-pounds  price-per-pound-in-dollars))])
        (if (positive? result)
            result
            (raise "assertion violation: method doesn't live up to its promises"))))
 
    (define/public (value-with-discount)
      (let ([result
             (+ (value) (* (value) discount))])
        (if (and (number? result) (<= result (send this value)))
            result
            (raise "assertion violation: method doesn't live up to its promises"))))))
> (define another-kind-of-apple-with-a-behavioral-interface
    (new apple+assertions% [quantity-in-pounds 6]))
> (send another-kind-of-apple-with-a-behavioral-interface value)

uncaught exception: "assertion violation: method doesn't

live up to its promises"

Note that even if we have sprinkled assertions in the code, we are careful to do so in a way that follows a clear pattern that separates functional code from validation code and thus retains the readability and maintainability of the code.

In fact, if our language supports higher-order functions, we can define a check function and use it to factor out the repeated conditionals from the method bodies:

(define (check property x msg)
  (if (property x)
      x
      (raise msg)))
(define apple+assertions+check%
  (class* object% (product<%>)
 
    (super-new)
 
    (init-field quantity-in-pounds)
 
    (field [price-per-pound-in-dollars 3]
           [discount -0.1]
           [offer-expiration 100000000000])
 
    (define/public (has-discount?)
      (check
       boolean?
       (>= offer-expiration (date->seconds (current-date)))
       "assertion violation: method doesn't live up to its promises"))
 
    (define/public (value)
      (check
       positive?
       (- (* quantity-in-pounds  price-per-pound-in-dollars))
       "assertion violation: method doesn't live up to its promises"))
 
    (define/public (value-with-discount)
      (check
       (λ (result) (and (number? result) (<= result (send this value))))
       (+ (value) (* (value) discount))
       "assertion violation: method doesn't live up to its promises"))))

> (define yet-another-kind-of-apple-with-a-behavioral-interface
    (new apple+assertions% [quantity-in-pounds 6]))
> (send yet-another-kind-of-apple-with-a-behavioral-interface value)

uncaught exception: "assertion violation: method doesn't

live up to its promises"

And we can take the refactoring even further and completely separate contract chekcing code from the method bodies of apple%. To do that, we introduce a new class, apple-contract-wrapper%, that implements the product<%> interface, has a field that holds a apple% object, and delegates to that object all method invocations (we will return to this pattern for structuring code later on in the quarter):

(define apple-contract-wrapper%
  (class* object% (product<%>)
 
    (super-new)
 
    (init-field inner-apple)
 
    (define/public (has-discount?)
      (check
       boolean?
       (send inner-apple has-discount?)
       "assertion violation: method doesn't live up to its promises"))
 
    (define/public (value)
      (check
       positive?
       (send inner-apple value)
       "assertion violation: method doesn't live up to its promises"))
 
    (define/public (value-with-discount)
      (check
       (λ (result) (and (number? result) (<= result (send inner-apple value))))
       (send inner-apple value-with-discount)
       "assertion violation: method doesn't live up to its promises"))))
> (define yet-yet-another-kind-of-apple-with-a-behavioral-interface
      (new apple-contract-wrapper%
           [inner-apple (new apple% [quantity-in-pounds 6])]))
> (send yet-yet-another-kind-of-apple-with-a-behavioral-interface value)

uncaught exception: "assertion violation: method doesn't

live up to its promises"