C!! — Contracts, Duck-Typing and the Transformation Assignment Operator in the C bang bang programming language | Accidental Scientist

C!! — Contracts, Duck-Typing and the Transformation Assignment Operator in the C bang bang programming language

One of the features I’d like to add to C++ is duck-typing. In this case, what I mean is, if an object has all of the named methods or variables you care about, you can treat it as “one of those things”.

The way you specify what an object has to have for it to be of a given duck-type is through a contract. Here’s an example of one:

contract(loose) PhysicsComponentContract
{
   vector3 position;
   vector3 velocity;
   float mass;

   void UpdatePhysicsState( float deltaT );
}

So what this says is that this is a contract (a list of methods and types an object must have to be said to honor the contract). The object it refers to has to have a position, velocity and a mass field, and an UpdatePhysicsState field. (I need to figure out whether default parameters and implicit conversion counts as matches… my gut says “not at first”… Maybe we’ll revisit that later).

The (loose) part says that the fields and methods can be laid out in any order in the object. Fields and methods are treated separately (because methods are only logically part of the object – they’re not part of the data layout).

There’s other options for the type of contract it can be. The basic rule applies that to honor a contract, all of the items specified by the contract have to be present. In the case of methods, if you specify a method in a contract, it always has to be present (so the contract type makes no difference) (kind of like an interface, but without the implicit requirement of virtual functions).

 

Contract type Definition
loose The fields can occur anywhere in the layout, shuffled or reordered, as long as they’re all present.
contiguous Must have all of the fields, in the exact order listed. Fields must be contiguous (except for padding necessary for alignment). Can occur anywhere in the object’s layout, as long as they’re a contiguous block.
strict Must only have the fields listed, in the order specified, laid out contiguously.
exact The same as strict, but must be laid out exactly as specified, with the same padding, packing, alignment. In other words, in-memory, the data must be memcpy-able between two objects which share that contract.

It might be necessary to get finer-grained than this for specific scenarios. That will need more work to figure out.

Requiring adherence to a contract

If you want to define a type and make sure that you honor the requirements needed for it to be considered honoring the contract, you can add honors ContractName to the class or struct definition. For example:

struct PhysicsActor : public Actor,
                      honors PhysicsComponentContract
{
   vector3 position;
   vector3 velocity;
   float mass;
   bool bDirtyFlag;
  
   void UpdatePhysicsState( float deltaT );
   void Render( Renderer& renderer );
}

In this example, PhysicsActor fully honors the PhysicsComponentContract. (If it didn’t, we’d get an error). If we wanted to, we could override the contract strictness requirements by specifying a new value after the honors keyword – like this:

struct PhysicsActor : public Actor,
                      honors(strict) PhysicsComponentContract
{
   vector3 position;
   vector3 velocity;
   float mass;
   bool bDirtyFlag;
  
   void UpdatePhysicsState( float deltaT );
   void Render( Renderer& renderer );
}

… and in this case, we’d get an error – PhysicsActor has extra fields that do not match the PhysicsComponentContract contrat when applied with strict requirements.

Interfaces

As an aside, interfaces are very similar to contracts, except they work very similarly to C# – an interface has to have:

  • A virtual destructor
  • Each method specified is virtual
  • All listed fields/properties much be present (and public)
  • But it’s really just like in C#. Heck, you might even look at them as the simple version of a MIDL file.

    Contracts as types

    It’d be great if we could pass objects around to methods by the contracts they honor, rather than by explicit type, but this would be quite difficult. It could be treated as simple syntactic sugar over what would be templates in C++ (and this might be worth considering). The object being passed in would need to be able to be statically determined as honoring the contract for this to happen.

    If we go that way, we’d want it to be explicitly done – you’d have to cast the object to the Contract type before you could pass it into a method expecting a Contract-honoring pseudotype.

    Transformation assignment and contracts

    The transformation assignment operator (<==) takes one type and performs an assignment via a mapping operation or a contract specification (with explicit mappings taking precedence). Each contract defines an implicit mapping between any types that honor the contract.

    (I say mapping, rather than “it knows how to copy between them”, because as we’ll get to later, mappings and transformation assignments can be more complex).

    In the simple case, what this means is say that you have two structs, which both honor a contract. You can assign from one to the other as follows:

    contract(loose) MustHaveANameContract
    {
       string firstname;
       string middlename;
       string lastname;
    }

    struct AOLChatPerson : honors(strict) MustHaveANameContract
    {
       string firstname;
       string middlename;
       string lastname;
       int age;
       Gender sex;
       GeoLocation location;
    }

    struct PublishedAuthor : honors(strict) MustHaveANameContract
    {
       string firstname;
       string middlename;
       string lastname;
       string bestBook;
       float highestMetacriticReview;
       currency currentAmazonKindleStorePrice;
    }

    … and then, let’s say that you want to create a new AOLChatPerson out of a PublishedAuthor. (Maybe they’re promoting their new book).

    If you want to make life easy on yourself, you can do this:

    AOLChatRoomDenizen* CreateNewAOLChat( const PublishedAuthor& author, int age, Gender sex, GeoLocation location )
    {
       AOLChatPerson* pChatroomDenizen = new AOLChatPerson;
       pChatroomDenizen <== author; // copy first, last and middle name by contract from author
       pChatroomDenizen->age = age;
       pChatroomDenizen->sex = sex;
       pChatroomDenizen->location = location;
       return pChatroomDenizen;
    }

    This example is deliberately simple, but it shows how the transformation assignment operator can be used to simplify copying data between structs.

    It gets more interesting once you start doing things like mappings between versions of structs, and other transformations that might be useful for serialization, or for converting endianness of parameters.

    Yes, this could be done using an assignment operator, but I like the idea of exposing that the assignment in this case may have higher cost than you’d expect. It also implies the duck-typing that’s going on.

    While we’re talking about duck-typing, we didn’t have to specify that the structs have the MustHaveANameContract contract on them – remember, that only applies to struct definition to make sure that what you end up with meets the terms of the contract at definition time. We could also have done this:

    AOLChatRoomDenizen* CreateNewAOLChat( const PublishedAuthor& author, int age, Gender sex, GeoLocation location )
    {
       AOLChatPerson* pChatroomDenizen = new AOLChatPerson;
       pChatroomDenizen <==(MustHaveANameContract) author;
       pChatroomDenizen->age = age;
       pChatroomDenizen->sex = sex;
       pChatroomDenizen->location = location;
       return pChatroomDenizen;
    }

    I think that’s enough for now. More next time!

    You can find the previous article in this series here.

    About Simon Cooke

    Simon Cooke is a video game developer, ex-freelance journalist, screenwriter, film-maker and all-round good egg in Seattle, WA. The views posted on this blog are his and his alone, and have no relation to anything he's working on, his employer, or anything else and are not an official statement of any kind.
    facebook comments