Class

Each class is composed of the keyword followed by the class identifier, optionally followed by a parametrized type list < ... >, optionally followed by the main constructor, optionally followed by an inheritance tree inherits ... (or equivalently : ... or :> ...), followed by the class body { ... } which contains the class members. Each pair of class members may be separated by newline(s) and/or a comma. Each class member is a declaration of a , , function, or instance reference.

Each member which was declared as a class, enum, or instance reference, may be accessed using the identifier of the containing class declaration, concatenated with the dot operator ClassIdentifier.member. Issuing a function call on a static instance reference member is allowed ClassIdentifier.member().

Each reference member may be accessed using an reference having the type of the containing class, concatenated with, the dot operator reference.member or, the bracket operator reference['member']. Issuing a function call on a class member, which is a reference that points to an instance of an anonymous function expression or , is allowed reference.member(). However, is only implicitly prepended to the function call parameters where the member was declared as a instance reference, i.e. in that case reference.member() is equivalent to ClassIdentifier.member( reference ) where ClassIdentifier is the type of the reference. A non- non-anonymous function expression member declaration, creates a method, because it implicitly declares a reference member that points to an instance of that function's type modified, such that a argument is implicitly prepended to the function's formal parameter (a/k/a argument) list.

Members may not be simultaneously , and any of , , or .

Access Scope

Class members prefixed with are not accessible outside the class body nor in any subclass body (nor subclass body of a class granted private access). Thus, by default class members not prefixed with are public. Note, is a compile-time feature that is not enforced at runtime if the virtual machine does not enforce it, e.g. if the VM has no notion of private members or if the VM allows cast of the containing class type to ( dynamic ){}. The keyword may be syntactically constrained similar to a type parameter, which grants member access to the body of each the class in the constraint list. See also Properties.

Parametrized Type

In the discussion that follows, a "class" means a parametrized type , or , which has type parameters. Each type parameter of a "class", may be used any where a type is expected in the "class" body. Each "class" type parameter may be optionally constraint bound. Also methods (excluding constructors) may be individually parametrized with the same syntax as for functions. Each of a method's type parameter(s) must be distinct from the containing "class" type parameter(s). The constraint for a method's type parameter may optionally be one of the containing "class" type parameters. Where accessing or static members with the head of of parametrized type Head<T>.member, instantiating a type parametrized or accessing a type parametrized method, and not providing a type parameter list of concrete type(s), the type parameter(s) are inferred.

Type Parameter Substitutability

This subsection is difficult to understand, but to use this feature you may only need to memorize the following chart, the footnotes that follow it, and read the first paragraph below the footnotes.

Syntax Substitutability Requirements
IType<+T> ref r : IType<Super> = new Type<Sub>
ref r : IType<Any> = new Type<Any>
no method parameter1 nor writable member3 are T2 type4
IType<-T> ref r : IType<Sub> = new Type<Super>
ref r : IType<Any> = new Type<Any>
no return value1 nor readable member are T2 type4
IType<T>
ref r : IType<Any> = new Type<Any>
none

1For each function which is a method parameter, apply the requirements swapped (between +T and -T) on method parameter and return types, but not recursively on each parameter which is a function within a function which is method parameter.

2Additionally, T may not appear in a parameter or return type, as the type parameter of a "class" that has the same or opposite substitutability "sign" respectively, where "sign" is + or -.

3except those only writable from a constructor.

4Do not apply to static members nor constructor(s), because these are never accessed via a reference.

Each "class" type parameter may be optionally prepended with a + or -, to indicate the allowed substitution of the type parameter by its subtype or supertype (i.e. covariant or contravariant) respectively; otherwise, the type parameter is not allowed to be substituted by a different type (i.e. invariant). An example of substitution is shown in the above chart, e.g. a subtype is allowed to be assigned to its supertype, if the type parameter was prepended with + in the declaration of the parametrized type.

The substitutability for or type hierarchy applies whether it has type parameters or not, i.e. this paragraph applies to non-parametrized and parametrized types. (sidenote: is not a referencable type) Liskov Substitution Principle, which depends on the fact that a supertype has a greater set of possible states than any of its subtype, thus requires that, for subtype Sub to be substitutable in place of (e.g. assigned to) its supertype Super, then Sub's method parameters1 (and writable members3) must each be the same type or a supertype of the corresponding method parameters (and writable members) of Super.4 And requires that Sub's method return values1 (and readable members) must each be the same type or a subtype of the corresponding method return values (and readable members) of Super.4 Thus, a instance reference that the compiler only knows has a type Super, will not cause the runtime error of assigning a supertype to a subtype, if at runtime the reference points to an instance of type Sub. This enables the compiler to safely allow the assignment of a Sub to a Super, e.g. ref r : Super = new Sub.

The substitutability for a type parameter must not violate the Liskov Substitution Principle requirements. Thus, because subtype Type<+T> type parameter substitutability allows the assignment of a Type<Sub> to a Type<Super>, e.g. ref r : Type<Super> = new Type<Sub>, then the type parameter T is not allowed to be the type of a method parameter1 (nor writable member) of Type. And vice versa because supertype Type<-T> type parameter substitutability allows the assignment of a Type<Super> to a Type<Sub>, e.g. ref r : Type<Sub> = new Type<Super>, then the type parameter T is not allowed to be the type of a return value1 (nor readable member) of Type. Scala documents an important use case.

The design of parametrized inheritance hierarchy fundamentally depends on immutability.

Static Member

A member declaration prepended with , has the same semantics as for example Java.

Additionally non-anonymous function expression member, which is not a method, may be declared within an . If it is not implemented within the , it requires an implementation of that static. It may be accessed like all static via an explicit (or if it has or inherits implementation) identifier, or if employing inversion-of-control principle, then it may be accessed with a parametrized function's type constraint.

Instance Member

The keyword is implicit and not allowed, when declaring class member instance references inside the body { ... }. When the class's constructor is called and before it executes, any initialization assignments in the class body are completed in the order they appear in the class declaration. It is an error to initialize an instance reference member with both assignment in the class body and in the constructor. If the type of the of the instance reference member was not explicitly declared, then it is inferred from the assignment in class body or constructor.

If the instance reference member is prefixed with , then its value is shared by all instances of the class. The inter-class (as opposed to intra-, which is specified above) initialization order is indeterminant for the initialization of a instance reference member.

Initialization
= expressionin new()
non-done firstdone second
allowednot allowed

Properties

By default, the read and write access scope for an instance reference member is given by the declaration of prefix for the member, or lack of prefix in the member declaration being implicitly public. Thus by default, the read and write access scope are equal.

Optionally the individual read and write access scope may be declared as follows, when no prefix is declared.

  • public access
  • private access
  • no access, except in constructor
class C
{
    identifier (read, write) : Type
}

A (i.e. method identifier to call) choice is not provided and would be redundant, because in Copute every getter (method Void -> Type) can be read accessed without supplying the empty function call suffix (), and every setter (method Type -> Void) can be assigned to, as an alias for calling the setter. A choice is not provided and would be redundant to providing either a getter or a setter, and not both.

(read, write) Equivalency
(public, public) public getter public setter
(public, private) public getter private setter
(private, public) private getterpublic setter
(private, private) private getterprivate setter
(new, new) n/a
(new, public) n/a
(new, private) n/a
(public, new) public getter
ignores
function closure
on constructor parameters
(private, new) private getter
ignores
function closure
on constructor parameters

"n/a" means not allowed, compiler error.

Because an may only contain abstract member declaration(s), an instance reference declaration in an will be implemented as getter and setter methods, that operate on an implicitly created hidden private instance reference, if the implementing subclass or repeats the instance reference declaration. The implementing subclass or may instead provide a getter and/or setter. Although it is slower to implement all instance references in interfaces as methods, this insures that the member can be abstracted in any scenario, and the point of an is abstraction.

If an has getter and/or setting which correspond to a row in the above table, they may optionally be implemented with the equivalent properties declaration. The properties declaration is converted to getter and/or setter required by the . If this automatic conversion to methods is not desired, then don't inherit from an .

This optional read and write access scope is a compile-time feature, that is not enforced at runtime if the virtual machine does not enforce it. For example, if the declaration is converted to a getter and/or setter, the caveats for the prefix access scope apply.

Method

A method is declared inside the body { ... } by any of the following.

For a method, the body of the non-anonymous function { ... } may optionally be declared with an alternative form = expression.

The function body { ... } of a method, may access a member in the containing class, by prefixing the member identifier with and the dot operator this.identifier. Except for the case, the this. prefix is not required, e.g. identifier also works. The main use of is make member access visually explicit and to disambiguate a method parameter (or other reference) that has the same identifier as a class member. To access any inherited implementation which is a (i.e. not a ), use super.member because is not a referencable type.

Method Callback

A method is uncallable without an instance reference having the type of the class that contains the method, because a method is a class member reference to a function that inputs as the first argument.

ClassIdentifier.method( reference, argument )  // reference.method( argument )

If a method is curried (even implicitly) via , then the is an implicitly supplied argument, for the currying of the static class member function that inputs as the first argument.

Since any Void -> Any, e.g. getter, method will be called if it is accessed with or without trailing (), curry it with no arguments by calling it with a double underscore argument.

Constructor

The constructor is called when an instance of the class is created. The main constructor is function parameter(s) grouped within parenthesis that precedes any inheritance declaration and the class body. The main constructor's parameter(s) are implicitly , , and properties with access modifier . If the main constructor is not specified, the implicit constructor's parameter(s) are all the non- properties that are without explicitly initialized values in order of declaration in the class. Note members have a default value of . Note the implicit constructor's parameter(s) assume the access modifiers of the declared properties.

Constructors may be overloaded, but every constructor other than the main one, must call the main one new(...) before accessing any other member of .

The optional constructors are a method is a function with a return type of , which may be prepended with zero or one of the keywords (the default), , or .

Constructors declared in a must not include a return type. An may not contain a constructor, nor any properties. A may not contain an explicit constructor, nor member initialization that is not a constant literal. Any implicit constructor for is never callable because is not an accessible type. Instead the becomes part of its subclass's constructor.

class ClassIdentifier()                // or class ClassIdentifier, with no empty parenthesis
{
    new( arg ) { new() ... }
}

ref reference = ClassIdentifier
reference = ClassIdentifier            // reference.new()
reference = ClassIdentifier( any )     // reference.new( any )

Overloading

Methods (including constructors) follow the same overloading rules as for functions.

Purity

Since a method is a static function with set to the instance, the rules for non-method functions apply. Thus, in a method, the instance reference is inferred to be , and any member instance reference (including any in the data structure hierarchy) that has assign-by-reference type, will be implicitly if returned from the method. This is so a pure method isn't able to modify its instance by modifying the returned instance from another pure method of same class.

A method declared has the following attributes.

  • must not be static
  • does not implicitly infer to be
  • does not implicitly infer the instance reference arguments with assign-by-reference type to be
  • must follow other purity rules, except may do the following.

    • modify and/or return (even as non-immu the) non-immu members of the containing class
    • call other mutable methods of the containing class

Thus unlike a pure function, a mutable method may return .

A subclass is not allowed to add nor remove the attributes and , because these create overrides not available in the superclass. In any (pure or impure) function, the class type modifier automatically cascades to all type parameters of the class, and an instance reference that has an assign-by-reference type of can only be assigned to another that is . Properties are the purity of their named method, else for getter or for a setter.

Operators

An non-assignment operator can be implemented for a class with a instance reference member that has an identifier that is an operator inside parenthesis (op), a function type with for the first argument, and a return type equal to the containing class.

The function type should take no additional arguments for an unary operator, or one extra argument for a infix operator. An infix operator can have multiple overloaded implementations on the second argument.

Virtual Method

There is no keyword nor to indicate that the implementation of a class method is allowed to be changed at runtime by subclass inheritance.

In all languages, virtual methods are not type-safe polymorphic, because at runtime they can break the compile-time contract, that (per the Liskov Substitution Principle) says the semantics (i.e. pre/post-conditions, invariants, and immutability constraints) of subclass must be substitutable for the superclass.

The compiler can enforce the contravariant method parameter(s), covariant return type, and the immutability constraits if these are declared, but it can not infer the implicit pre/post-conditions and invariants which are not declared. Provably correct semantic substitution will never be attainable in the general case, because the Halting Problem, Gödel's theorem, Russell's Paradox, Coase's Theorem, Linsky Referencing, and the Second Law of Thermodynamics all state that any such proof set will always fail at a boundary condition. Thus Liskov Substitution Principle effectively states that whether subsets inherit is an undecidable problem.

Thus, a reference having (that thinks it is pointing to) the type of a superclass, thus thinks it has the semantics of the implemention of that superclass, and not the implementation (and possibly incongruent semantics of) some unknown subclass type that could be created (via inheritance) at any time in the future by anyone. With a virtual method, the single-point-of-truth semantic is lost, because it can be overridden at runtime by any extension at any time in future without the permission of the single-point-of-truth.

In Copute, a method accessed via a reference having an type, is type-safe polymorphic because an interface has no implementation, i.e. no concrete members. Since an can not be instantiated, a runtime reference having an type, points to an instance of a subclass (of that ) which has an implementation. The implementation of the runtime instance is called, which is very similar to a virtual method, except this does not break the compile-time contract of the , because an has no single-point-of truth implementation, and each subclass is its own single-point-of-truth implementation. Thus the lack of an a boundary condition on expected semantics is explicitly declared when dereferencing an . As pointed out in the prior paragraphs, any such hypothetical boundary condition would be pointless, because it would break any way, due to Coase's Theorem and the other theorems.

The key distinction between a virtual method and an , is that the latter does not override the single-point-truth implemention in each branch of the inheritance tree from that root . On each of infinite possible branches in the inheritance from an , there will be (forever!) only one class that declares the concrete members that are accessed through a reference having the type of that . Although it is possible to override that single-point-of-truth, the override will only be accessed from a reference having the type of the class that created the override, thus the override becomes its own explicit single-point-of-truth with no hidden (implicit) runtime violation of a single-point-of-truth contract.

Although access via reference having the type of an has no single-point-of-truth with respect to semantic implemention, this semantic polymorphism is the point of having an , and is in fact required by Coase's Theorem as previously explained. An does have a single-point-of-truth in that the guarantee given by the compiler to the consumer of an interface, is only the declared type signature and any pre/post-condition and invariants-- not its documentation. Whereas, virtual methods conflate (due to declared substitutability) the expected implementation semantics of the entire monolithic superclass (a/k/a base class), with the compiler checked attributes of each of the superclass's abstract members which should otherwise be individually orthogonal.

An forces the separation-of-concerns, because an interface identifier can not be constructed. Also, the explicitly always (not sometimes determined only at runtime) calls a subclass implementation, and thus can't produce the opaque bug exhibited by a virtual method, where the programmer can't see, i.e. no single-point-of-truth, why a class isn't doing its expected implementation when looking at the superclass's code at compile-time. This is made more conspicious by requiring that identifier for an must be preceded with a capital I.

Although it is possible to create inconsistency in semantics between the documentation of the and the implementation(s), this is not peculiar to polymorphic methods (surjectively to the , and it is irrelevant that more than one subclass implementation may be inconsistent with each other), as any kind of function can have inconsistency between documentation (or expected semantics) and the actual semantics implemented. Since many programs can not be proven to halt, it may not even be possible to verify the semantics (test all code paths) of an implementation which is not referentially transparent and non-recursive.

Virtual methods which are not type-safe polymorphic, may be implemented in Copute by creating a class method of type in the super class, and then in the subclass constructor, save the superclass's method (if it will be needed), then assign a new static function.

class Super
{
   static virtual : dynamic = function( this : Super ) {}
}

class Sub( Super )
{
   new()
   {
      ref super = virtual
      virtual = function( this : Super )
         {
            super( this )
         }
   }
}

A method can be assigned a different function (even with different arguments and/or return type), but it can not be assigned a non-function. Thus, virtual methods are just one of infinite programming patterns using methods, and a dedicated syntax (to simplify the pattern) is undesirable because it would obfuscate the breakage of compile-time type safety caused by redefinition of methods. Also a dedicated virtual method syntax would require an explicit (i.e. and ) distinction between inheriting implementation and interface.

Inheritance

The purpose of inheritance is to maximize reuse via orthogonal (a/k/a separation-of-concerns) composition, without violating the invariants that insure semantic coherence. Casting of named types to anonymous structural types violates such invariants, and thus is not allowed in Copute, neither explicitly nor implicitly (i.e. nor silently without a declaration). Instead maximally granular single-responsibility-principle and classes should be used.

Composition of inheritance is accomplished by class and members that are abstract and concrete. Abstract members of an , and concrete members of a class, can be composed in mixin multiple inheritance; whereas, concrete members of a concrete can only be composed in single inheritance hierarchy. An abstract member is an instance reference (including any properties) that has an explicitly declared type but is not initialized. An abstract method is declared without the function body.

Mixin composition may change inheritance order, thus each must not have an explicit constructor, nor member initialization that is not a constant literal. A mixin may only use super.member to access a non-static member of the superclass, and not this.SuperclassIdentifier.member, because mixin composition can change the ClassIdentifier of the superclass.

A must not contain an abstract member in its body, and must have a concrete member in its body, or inheritance tree, for each abstract member in its inheritance tree. An must not contain a concrete member in its body nor its inheritance tree. A provides concrete member(s) which must inherit from abstract member(s). A must not contain an abstract member in its body, but may (but not 'must') contain one or more extra abstract member(s) in its inheritance tree, for which there is not a concrete member in its body, or inheritance tree. The benefit of Copute's separation of from is that monolithic semantics are not conflated with each abstract member type, the importance of which is explained more in the discussion of virtual method. Unlike , is not a referencable type, thus can not be referenced, and is only used for mixin inheritance declaration.

The identifier for an must begin with capital I followed by another capital letter. The identifier for a or must begin with a capital letter other than I followed by any character that is not a capital letter.

class Sub : Super1, Super2, Super3
{
}

The mixin composition inheritance list Super1, Super2, Super3 is flattened (a/k/a folded or linearized) to a single inheritance, such that the left-most Super1 is the subclass of the next in the list Super2, which in turn is the subclass of the next in the list Super3. Only the last right-most in the list Super3 is allowed to be a , and the preceding in the list are the mixins. Each mixin must be a or . A mixin is not repeated (a/k/a duplicated) if it inherits from a copy of itself in the resultant single inheritance. Obviously a is not allowed to inherit from itself as this would be infinite recursion. If distinct (not repeated) mixins produce the same member, then to disambiguate, a concrete member must exist in the subclass body.

Mixin order can insert a in middle of the inheritance hierarchy of another . For example, given mixin A : B and mixin C : B, then mixin D : C,A would effectively cause mixin C : A,B, because the B in mixin C : B is already in the inheritance tree of mixin A : B, and thus not repeated. A more detailed explanation can be found at top, left of page 7 in the Super subsection of the 2.2 Modular Mixin Composition section of Scalable Component Abstractions, Odersky & Zenger, Proceedings of OOPSLA 2005, San Diego, October 2005. See also section 5.1.2 "Class Linearization" in the Scala Reference. Another explanation is in section 12.6 "Why not multiple inheritance?" in Programming in Scala (Odersky, Spoon, Venners). Note Copute reverses the inheritance list order of Scala.

Function overloading conventions determine which methods are equivalent. Also a method which is equivalent to and thus hides the method of a supertype, may return a subtype of the hidden method's return type. Note if the supertype is an , this is overriding not hiding, i.e. the substituted method will be called from a reference that has the type of that interface.

Class Should Be Private

Composability is destroyed by construction of an explicit , which should instead be abstracted (over inheritance) through a factory . Best design practice for composable modules (i.e. public APIs) should expose abstract interfaces but not their concrete classes. Thus creation of a new instance of an API's private explicit class(es) becomes impossible, because there is no public (i.e. external to the composable API) explicit concrete to instantiate. Since (form of concrete implementation) is not a referencable type, and an is a type (without concrete implementation), they can be publicly exposed (for benefits of reuse) without risk of explicit instantiation that would reduce the abstraction of the composable public API, because a mixin is instantiated in an instance of a subclass but is referenced only via its inherited abstract interface. In summary, a reference to an abstract interface type never tells which mixin is being referenced.

Parametrized Inheritance

Non-parametrized Substitution

By definition of inheritance hierarchy, non-parametrized types allow covariant, but not contravariant, substitution (i.e. assignment), e.g. ref r : Super = new Sub is allowed, but ref r : Sub = new Super is not. This is because by definition Sub always contains a Super, but at compile-time a Super might not contain a Sub, because the compiler only knows there is a reference to a Super that might be pointing to any subclass. The types for any method parameters, return values, and members, which comprise a non-parametrized type must obey the Liskov Substitution Principle. Any (covariant) type, except , may be substituted for (i.e. assigned to) the type Any (the most general type), i.e. all (covariant) types implicitly inherit from Any. Whereas, an Any must not be substituted for (i.e. assigned to) another (covariant) type, without a runtime cast that has been suitably conditioned to avoid throwing an exception. A will never be substituted for (i.e. assigned to) another type, because a reference to a can never be created, i.e. is not a referencable type. is only used to express inheritance (covariance) relationships, or the absence of function parameters and/or a return value.

Same as non-parametrized types do, parametrized types substitute with respect to the inheritance hierarchy on the head (non-parametrized) portion of their type, e.g. on the Head in Head<T>, when the type parameters of the substitution are invariant or match the variance of any declared substitutability.

Type Parameter Substitution

Covariant Head<+T> type parameter substitution is always be possible for a type parameter T, if T is only used in covariant positions. In other words, covariant type parameter substitution is always possible for a class that is always immutable (even if only immutable for the members that have the type of T). This is because a pure method can not modify its instance, thus the use of T by the pure method does not affect the instance of , thus it is always possible to replace the disallowed convariant use of T as a pure method parameter, with a type parametrized method that uses a separate type parameter contravariantly constrained to be a supertype of T. Scala documents an example. Copute may automate this case in future, where it is not explicitly declared. Covariant substitution is always due to immutability in any computer language.

Contravariant Head<-T> type parameter substitution is always be possible for a type parameter T, if T is only used in contravariant positions. Any contravariant type parameter (only type parameters have the possibility to be contravariant), including Any, may be substituted for (i.e. assigned to) the type (the least general type, which means "nothing"), i.e. all contravariant types implicitly inherit from . When a occurs in the context of inheritance from a covariant, or substitution by a contravariant, type parameter, it will never create a reference to a because due to substitutability rules a contravariant type parameter can never be returned nor read, and in the covariant inheritance case, an instance of is never allowed to be constructed, because by definition does not know which covariance tree branch its instance may be on.

Inherit Dynamic

A class that inherits has unlimited instance reference members, each with type. Each class member declared in the class body, will have dynamic type regardless if they were so individually declared (because otherwise would allow conflicting key access to a dynamic class members created at run-time with the same instance reference identifier). Dynamic class member bracket [member] access is a string hash table.

Dynamic implies an infinitely recursive parameterized type dynamic<dynamic<dynamic<dynamic<dynamic ... >>>>. Whereas, dynamic<string> declares that all non-declared members have type.

Enumerated Inheritance

Provides an algebraic data type, which in the expression problem model (more...), has single-point-of-truth (SPOT) on subtypes, seals (limits) the number of subtypes, and has unlimited operations (functions that input as a parameter). Whereas, functions have no SPOT, unlimited types (identifiers), and unlimited operations (overloads). Thus functions have an unsealed model which forces a default guard in pattern matching to avoid a compile-time conflict.

TODO: need to rewrite this section to agree with the changes in the grammar and semantics. The new syntax is very similar to Scala's case class. Key differences are:

  • case instead of case class or case object. A singleton object is used implicitly for a case with no constructor parameters, with the advantage that ambiguities are automatically resolved.
  • case may optionally be inner classes to prove identifier scoping (this may be allowed in Scala, have not tried). These inner case implicitly inherit from the containing or , the type parametrization of the containing supertype.
  • The supertype or of the class must be in same file, and it sealed from inheritance outside the file. Pattern matching with is forced to have a for an unsealed declaration. We could offer a unsealed option in future, if it proves compelling.
  • For inner case, shorthand for case with no constructor parameters then do not specify empty parenthesis. Within a containing interface, the case keyword may be omitted for those with no constructor parameters.
  • Constructor parameters are always and , because algebraic data type relations break with immutability.

An is an algebraic type, which is a syntactical shortcut for declaring a superclass, declaring the subclasses, declaring any subclass members, and finalizing all inheritance (i.e. an enum can inherit a class, but nothing may inherit an enum).

When are compared via or equality inequality operators, the constructor arguments are aliases to matched subclass members. An has the advantage, that if the does not have a case, then a must be declared for each subclass. An enum that contains no parameterized type nor subclasses with arguments nor more than 32 subclasses, automatically inherits from ICastInt32 and contains the methods toInt32 : Void -> Int32 and static fromInt32 : Int23 -> ICastInt32, where the Int32 is a bit flag in order of least most significant bit assigned to first subclass.

An example follows (33 lines of code).   And is equivalent to the following (59 lines of code),
except class Color<T> does not finalize inheritance.
enum Color<T> { red green blue abstract Color<T>() { class red<T>( Color<T> ) {} class green<T>( Color<T> ) {} class blue<T>( Color<T> ) {}
rgb( r : T, g : T, b : T ) class rgb<T>( Color<T> ) { r( default, private ) g( default, private ) b( default, private ) new( r : T, g : T, b : T ) { this.r = r this.g = g this.b = b } }
yuv( y : T, u : T, v : T ) class yuv<T>( Color<T> ) { y( default, private ) u( default, private ) v( default, private ) new( y : T, u : T, v : T ) { this.y = y this.u = u this.v = v } }
alpha( a : T, c : Color<T> ) class alpha<T>( Color<T> ) { a( default, private ) c( default, private ) new( a : T, c : Color<T> ) { this.a = a this.c = c } }
function toYuv() : Color<T> { switch( this ) { case red: return yuv( 0.299, -0.14713, 0.615 ) case green: return yuv( 0.587, -0.28886, -0.51499 ) case blue: return yuv( 0.114, 0.436, -0.10001 ) case rgb( r, g, b ): return yuv( 0.299 * r + 0.587 * g + 0.114 * b, -0.14713 * r - 0.28886 * g + 0.436 * b, 0.615 * r - 0.51499 * g - 0.10001 * b ) case yuv( y, u, v ): return yuv( y, u, v ) case alpha( a, c ): return alpha( a, c.toYuv() ) } } } function toYuv() : Color<T> { switch( typeof this ) { case red<T>: return yuv<T>( 0.299, -0.14713, 0.615 ) case green<T>: return yuv<T>( 0.587, -0.28886, -0.51499 ) case blue<T>: return yuv<T>( 0.114, 0.436, -0.10001 ) case rgb<T>: ref c = this.instanceOf( rgb<T> ) return yuv<T>( 0.299 * c.r + 0.587 * c.g + 0.114 * c.b, -0.14713 * c.r - 0.28886 * c.g + 0.436 * c.b, 0.615 * c.r - 0.51499 * c.g - 0.10001 * c.b ) case yuv<T>: ref c = this.instanceOf( yuv<T> ) return yuv<T>( c.y, c.u, c.v ) case alpha<T>: ref c = this.instanceOf( alpha<T> ) return alpha<T>( c.a, c.c.toYuv() ) } } }
ref clr : Color<Int> = alpha( 0xFF, rgb( 0, 0, 0 ) ) switch( clr ) { case alpha( a, c ): if( a == 0xFF ) return c default: } ref clr : Color<Int> = alpha<Int>( 0xFF, rgb<Int>( 0, 0, 0 ) ) switch( typeof clr ) { case Color<Int>.alpha<Int>: ref c = clr.instanceOf( Color<Int>.alpha<Int> ) if( c.a == 0xFF ) return c.c default: }
if( clr == alpha( a, c ) ) if( a == 0xFF ) return c if( typeof clr == Color<Int>.alpha<Int> ) { ref c = clr.instanceOf( Color<Int>.alpha<Int> ) if( c.a == 0xFF ) return c.c }
ref morphed = clr == alpha( _, c ) ? Color<Int>.alpha( 0x00, c ) : clr ref morphed = clr if( typeof clr == Color<Int>.alpha<Int> ) { ref c = clr.instanceOf( Color<Int>.alpha<Int> ) morphed = alpha<Int>( 0x00, c.c ) }

Anonymous Type

An anonymous type (a/k/a structural type) is a class declaration without the keyword and without the class identifier. Such an anonymous class can be used instead of a class identifier to specify a type, e.g. in explicit typing of an instance reference, as the concrete type for a type parameter, in an inheritance tree, and in an instantiation new ...() (where ... is an anonymous class declaration). Note new ...() (instead of just the ... portion) is only required and allowed if there is inheritance declared in the anonymous class being instantiated.

ref reference = {}()
ref reference = <ParamType>( ParentClass ){ new() {} }()
ref reference : { x : Int, y : Int } = function() : { x : Int, y : Int } { return { x = 0, y = 0 } }()

An advantage over named classes, the class body in an anonymous class has access to the variables and functions in the current scope.

ref any = 0
function f() { return any }
ref reference = { x = f() }()

And the (implicit or explicit) constructor and thus member variable types can be inferred from the assignment context.

ref any = 0
function g( p : { x, y } ) {}
g( { x = 0, y = 0 } )           // see Anonymous Instance
g( <[0, 0]> )                   // see Sequence

Static Duck Typing

Structural typing is a form of compile-time duck typing, where types are equivalent if they have the same members. Unlike other computer languages that support compile-time structural typing, Copute does not ignore any identifier and inheritance structure of the class, abstract, or interface when determining structural type equivalence. Thus in Copute it is impossible at compile-time to accidentally violate the single-point-of-truth that upholds the separation-of-concerns and single-responsibility-principle semantics by for example, supplying an instance of Array with expand() member to a function addAir that takes an anonymous structural type containing an expand() member. The semantics of adding air is does not apply to arrays. Or supplying an instance of List with count() member and Iterator with count() member to a function that takes an anonymous structural type containing an count() member. The count of elements in a set is not a compatible semantic to the count of iterations completed or remaining. Otherwise, especially with inferred (a/k/a implicit) typing, such accidents of expediency can proliferate. Proponents of unrestricted structural typing argue that the risk of such accidents are justified to obtain the freedom to mix-and-match interfaces, but this granularity of composition can be achieved within Copute's inheritance and type parametrization.

An instance of an anonymous class type can be assigned to another if both types have no inheritance or any inheritance tree matches, and if no explicit constructor exists in the destination type. They can be assigned to another with more members, if all the members in the source type exist (in the destination type, i.e. injective), and there are default values for all the members (in the destination type) which are not in the source type, e.g.

ref reference : { x : Int, y : Int = 1, z : Maybe<Int> } = { x : Int }( 1 )

The above refers to the compile-time declared members, not dynamic members created at run-time. Assigning to a type with fewer compile-time members is only allowed if the destination type inherits from dynamic, in which case the extra members are added to the destination instance.

ref reference :            { x : Int } = { x : Int, y : Int }()          // not allowed, generates compile-time error
ref reference : ( dynamic ){ x : Int } = { x : Int, y : Int }()          // allowed

Note that Copute has a type, so runtime duck typing (without single-point-of-truth limitation) is allowed.

Anonymous Instance

An anonymous instance is the liternal instance of an anonymous class, and thus are a shortened syntax declaring the anonymous class and constructing a new instance of it. Every instance reference member must be assigned a value, except those that have a default value.

ref reference = { x = any, y = 1 }   // ref reference = { x = any, y : Int }( 1 )

Dynamic Instances

Dynamic class instances are a short-hand to infer an anonymous instance that inherits from . They reduce verbosity of dynamically typed programming.

ref reference = <{}>                     // ref reference = new ( dynamic ){}()
ref reference = <{ x = 0, y = 0 }>       // ref reference = new ( dynamic ){ x, y }( 0, 0 ), or new ( dynamic ){ x = 0, y = 0 }
ref reference = { x = 0, y = 0 }         // ref reference = { x, y }( 0, 0 )

String keys are allowed, because dynamic class member bracket [member] access is a string hash table.

ref reference = <{ 'x' = 0, y = 0 }>
ref reference = <{ 'x' = 0, x = 1 }>     // instance['x'] (aka reference.x) == 1

Sequence

A sequence <[ ]> is an ordered set of expressions. The sequence type is an ordered set of types, e.g. (T1, T2, Tn) or T1 -> T2 -> Tn. The sequence is a literal instance of the sequence type.

The sequence elements are expressions in the local lexical scope. When assigning to a sequence (i.e. when the sequence expression is on left-hand side of the assignment operator =), each element expression must be an instance identifier (ref identifier is allowed).

Sequences are necessary to support multi-dimensional iterations, reduce verbosity, and simulate Haskell, PHP, and Standard ML tuples, plus do much more. They are a generalization and unification of local scope aliases with class typing, e.g. see example below that a returned sequence can be inferred to be a class with any matching member names.

Sequences are applicable:

  1. Where the ordered set of types match all of the (at least the non-default) arguments of a function (or method), the function or method may be called with the sequence as the only argument (except for any unmatched non-default arguments), e.g. function( <[ ]> ), or alternatively for functions (not methods) then the matched arguments are not written within the parenthesis and the function is called in the syntax as if it were a method of the literal sequence, e.g. <[ ]>.function().
  2. Where the ordered set of types match all of the (at least the non-default) arguments of a constructor of the class being assigned to, or match all of the public non-method members of the class being assigned from. See implicit constructors.
  3. Where the sequence type is the type of a "not used" argument of a function or method, the instance identifier(s) become argument(s) in the function body, and may be called with matching inserted arguments, sequence, or class instance.
  4. Note: the special single underscore variable _ is reserved for "not used" placeholder, which on write will skip over (and force) the corresponding default value, and on read will throw away the corresponding value.
function f( p : { u, v } )
{
    return <[ p.v, p.u ]>              // return { u = p.v, v = p.u },...
}                                      // ...except that the u and v member names...
                                       // ...are not declared for the return type
function g( _ : <[ x, y ]> )
{
    return <[ x, y ]>
}

function h( u, v )
{
    return g( <[ u, v ]> )
}

ref x = 0, y = 0
<[ x, y ]> = f( <[ x, y ]> )
<[ x, y ]> = g( <[ x, y ]> )
<[ x, y ]> = h(    x, y    )
<[ x, y ]> = h( <[ x, y ]> )
<[ x, y ]> = f( <[ 1, 1 ]> )
<[ x, y ]> = g( <[ 1, 1 ]> )
<[ x, y ]> = f( { x = x, y = y } )     // Error type mismatch
<[ x, y ]> = f( { u = x, v = y } )
<[ x, y ]> = g( { x = x, y = y } )
<[ x, y ]> = g( { u = x, v = y } )     // Error type mismatch
<[ x, y ]> = f( { u = 1, v = 1 } )
<[ x, y ]> = f( { x = 1, y = 1 } )     // Error type mismatch
<[ 1, 1 ]> = g( <[ x, y ]> )           // Error can't assign to a non-instance