User:Enamex/DIP: Extension Interfaces

From D Wiki
Jump to: navigation, search
Title: Alias (Extension) Interfaces
DIP: xx
Version: 0.1
Status: Draft
Created: 2015-09-04
Last Modified: 2015-09-11
Author: Enamex(:real-name)


The existing constraints system depends on manually-written 'tests' instead of compiler-assisted comparisons of signatures of the template with a prototype, for example (a la `sig` in ML).

A system is here proposed to allow the declaration of 'extension interfaces' (alias interface) that are ad-hoc attachable to existing types (structs and classes) and thus usable as native parts of a type within the package that declares the alias interfaces.


Idiomatic modern D in its current state prefers free functions for any added 'interface functionality' to types instead of adding (static) methods directly to types or having to inherit from interfaces with classes.

This presents several key problems:

1. The currently idiomatic D way to use constraints is by testing the availability of certain methods to be called on a type without regard for any nominative 'bundle' it might belong to in another language (like the names for Haskell type-classes or Rusts's traits or the earlier versions of C++ Concepts proposal).

2. D template constraints, because of template hygiene, can only recognize native methods of a type, effectively ignoring a substantial amount of functionality idiomatically provided through free functions.

3. Extending classes exclusively through free-standing functions has significant namespacing issues.

This proposal allows for disciplined definition of extension functions (as is provided today via UFCS) that obey stricter namespacing rules, are available to call wherever the name of a supported type is passed to a template (they bind to the implementing type's name, thus available wherever it is; provided at least either the type or the extension are local to the root package), and simplify using D constraints while improving the situation of extending structs without inheritance woes.

It optionally allows for using any interfaces as 'alias interfaces' that would be remotely implementable by class/structs.

Terms and Keywords

[1] alias interface : An interface that can be added ad-hoc to a struct or class separate from the type's definition, with no requirement for direct inheritance. They may include methods static/non-static as well as aliases to types and manifest/static-immutable constants.


alias interface InterfaceName : //Inherited alias interfaces
  alias MyTy = int;
  static void func() {}
  bool bool_func() { return !this; } // 'this' refers to the object of the extended type

[2] Root package : The package name super to all other package and module names in a project (e.g. std is the root package of Phobos, even though it has no code to its name (no package.d)).

[3] alias interface implementations : The ad-hoc implementation of an alias interface for a struct/class anywhere in a package which has either the type definition or the alias interface declaration as local to its root package.


alias struct MyStruct : InterfaceName {
  alias MyTy = char;
  static void func() {}
  bool bool_func() { return !!this && 1==1; } // 'this' refers to the object of the extended type

[4] Extended type : A struct or class that has implemented an alias interface and thus, within all sub-modules of the root package of the module where the implementation appears (and is legal given the rule in [3] above), has all the methods, aliases and constants defined in the implementation available wherever the type and the declaration of the alias interface are imported.

It has to its name the alias methods and assorted objects visible as native to the type and visible wherever only the name of the type is visible if it was passed from a scope in which both the type and the alias were visible (e.g. when passing a type to isInputRange which doesn't satisfy the constraint but has an implemented alias which does and which was visible where the isInputRange template was invoked.


Allow the declaration of alias interfaces that accept function prototypes (with optional default implementations) and associated constants (both `static immutable` and manifest constants allowed) and the implementation of such interfaces for a `struct` or `class` after its definition.

Alias interfaces defined under the user's most super package would be implementable for types provided in third-party packages/libraries. Types defined under the user's most super package would be able to implement alias interfaces provided in third-party package/libraries.

It is impossible for a package to implement a third-party alias interface for a third-party type. It must be that either the alias interface or the type or both are local to the client code.

In any sub-module of the root package of a project, any local type or alias interface that has been coupled with a local or foreign alias interface or type would behave as if the type natively possesses all functions and associated constants defined in the implemented alias interfaces. All functions in the type-implemented alias interfaces would be callable through method syntax `object.method()` and visible throughout the package no matter where the implementation block lies (it must still be under the same most super package, however).


module foreign_library;

struct ForeignStruct {...}

alias struct ForeignStruct {
    /* add methods/static functions and constants here;
       they become a part of `ForeignStruct` wherever
       `ForeignStruct` is in scope.
       The `alias struct/class` definition must be in the
       root package of the struct/class it is extending.
module my_project;

alias interface ExtensionInterface {
    /* An 'extension interface' declared as `alias interface`.
       It may provide:
       - static and non-static methods (only signatures or with
         default, override-able implementations)
       - `alias` declarations of all kinds.
       - Constants declared with `enum` or `static immutable`.

    /* An `alias interface` requires to be implemented by a custom
       type to be useful. When provided, and implementation is checked
       against the definition of the `alias interface` and, if legal,
       the implementation is 'attached' to the `alias interface`
       definition remotely so that it is usable wherever the `alias
       interface`'s definition is imported.
module my_project.core;
import foreign_library: ForeignStruct;

// `alias interface` is implemented for a struct/class using the syntax:
alias struct ForeignStruct // Name of the struct being extended
    : ExtensionInterface // Name of the `alias interface` that this
                         // implementation is checked against.
    // Implementation of `ExtensionInterface` specific to
    // `ForeignStruct`

       - static and non-static methods (only signatures or with
         default, override-able implementations)
       - `alias` declarations of all kinds.
       - Constants declared with `enum` or `static immutable`.

    // This implementation binds to both the `alias interface ExtensionInterface`
    // and the type `ForeignStruct`.
    // To use it, you need to import "my_project.ExtensionInterface"
    // and "foreign_library.ForeignStruct".

/* Put another way: implementations of extension interfaces:
   1. bind to the type if they are anonymous ("alias struct StructName" syntax)
   2. bind to to the extension interface declaration if they are named (the "alias interface" syntax)

Alternative syntax:

module superb.a;
struct Tile {
    void scan() {}


import superb.a;

// anonymous extension interface, attached to struct superb.a.Tile
interface (Tile) {
    bool detectChell() { return true; }

// named extension interface 'prototype'; later implemented by types and tested against;
// could also support using any interface this way, and dropping the `()` in declaration
interface () HydraulicMobileEnvironment
    : path.SomeOtherInterfaceInherited // inheritance of extension interfaces allowed
    static immutable int startingHealth;
    bool smash_opposite_tiles() { return true; }

module superb.core;

import superb.a: Tile;
import HydraulicMobileEnvironment;

// implement extension interface 'HydraulicMobileEnvironment' for type Tile
interface (Tile)
    : HydraulicMobileEnvironment
    // provided required implementation, uses default of 'smash_opposite_tiles'
    static immutable int startingHealth = 95;

module superb.mainfunc;

import superb.a;
import superb.core;

void main() {
    Tile t;
    t.smash_opposite_tiles(); // works; 'superb.core.HydraulicMobileEnvironment' implementation for 'superb.a.Tile';
                              // error if 'superb.core.HydraulicMobileEnvironment' is not imported;
                              // You import the interface declaration,
                              // not the implementation (which is automatically available unless declared private)
    t.scan(); // native definition
    t.detectChell(); // function in anonymous extension interface; doesn't require importing anything for it


When writing a DIP, try not to express your opinion. DIPs should provide facts and be as objective as possible. Even when that's pretty hard, you can make the DIP look more objective by not using, for example, "I prefer XXX because YYY". If YYY is an objective advantage, write that in a more objective way, like "XXX can be a better option because YYY". Try to leave non-technical personal preferences aside; "XXX can be a better option because the syntax is nicer" is not good enough even when you don't say "I prefer".

Try not to include half-baked ideas. If you are not sure about something, leave it outside the DIP and write it on the NG announcement instead for further discussion. The idea can be added to the DIP in the future when it is in a better shape.

NG Announcement

When posting the DIP announcement to the NG, please copy the abstract, so people can easily know what is it about and follow the link if they are interested.


This document has been placed in the Public Domain.