Skip to content

proposal: Go 2: Add dimensionality to enrich type system and strengthen compile-type checking #57973

Closed as not planned
@czetie

Description

@czetie

Author background

  • Would you consider yourself a novice, intermediate, or experienced Go programmer?
    Novice with Go, experienced with other languages esp. C

  • What other languages do you have experience with?
    Fortran, Pascal, Algol, C, Rust, Python, PL/M, various assembler

Related proposals

  • Has this idea, or one like it, been proposed before?
    Not to my knowledge or that of other Go programmers I spoke with

  • Does this affect error handling?
    No

  • Is this about generics?
    No

Proposal

  • What is the proposed change?
    Golang does not allow mathematical operations between operands of different types, even if they have compatible underlying types. This provides highly useful protection for addition and subtraction and is helpful in avoiding a certain class of logical errors. For example, it is almost certainly a logical error to attempt to calculate speed + elapsedTime, (assuming their types have been appropriately defined as float64, for example). However, this is too restrictive for multiplication and division: distance = speed * elapsedTime is a perfectly reasonable calculation. In order to perform this calculation today, the programmer must reduce everything to compatible types, typically the underlying types, this sacrificing the benefits of type checking. For example:
type distance float64
type time float64
type velocity float64

var v velocity
var d distance = 100
var t time = 2

v = d / t                                                  //will not compile, incompatible types
v = velocity(float64(d) / float64(t))        //will compile, but throws away type checking of d and t

A small enhancement to the rules for type mixing in expressions would allow useful constructs as mentioned above such as distance = speed * elapsedTime, as well as other more complex calculations, while still preventing mistakes such as distance = speed + elapsedTime. The characteristic of these calculations is that they mix values of different "dimension", in this case velocity, which in turn can be defined as having dimension Length/Time; and Time. And the resulting value has dimension different from either, i.e. Length in this case. While adding values of different dimension makes no sense, multiplying or dividing them can be perfectly meaningful and extremely useful.

This proposal would allow the developer to specify, by using the type system, dimensions and valid combinations of dimensions together with the new dimension that they result in.

  • Who does this proposal help, and why?
    This proposal would help any programmer working with data that has dimensions, e.g. physical quantities of Length, Time, Distance, or combinations of them, i.e. essentially all scientific and engineering data, by providing an added compile-time check that only quantities of the correct dimension are being mixed.

It could also help programmers in commercial applications where dimension might be defined more abstractly than in physics. For example, to estimate project cost we multiply "number of FTEs" by "fully loaded FTE cost" to get a result in dollars (or appropriate currency). We should not be able to use any old number defined in dollars in this calculation, but only one defined as an FTE cost. Or we might estimate project duration by dividing "number of story points" by "story points per FTE per day" and multiplying by "number of FTEs" to get a result with dimension measured in Days. Other examples of common classes of commercial mistake that can be caught with appropriate definitions include mixing quantities in different currencies; or multiplying by a fraction (0.0 to 1.0) instead of a percentage (0.0 to 100.0).

The more complex the calculation, the more valuable the checking of dimension. And to reiterate, this checking is lost today even if the programmer defines types, because in order to write calculations that mix types, they must be reduced to the underlying type, e.g. float64.

  • Please describe as precisely as possible the change to the language.
    This proposal would ADD a new form of type definition as an extension to existing syntax to define a type as the product or quotient of other types.

It would also MODIFY the restriction on mixing types in arithmetic expressions: it would still be forbidden to mix types in addition and subtraction, but it would be permitted to mix types in multiplication and division provide they have compatible underlying types.

  • What would change in the language spec?
  1. Multiplication or division of values and variables of different types would be allowed, provided their underlying types are compatible.
  2. The syntax for defining types would be extended to define types in terms of the product or quotient of other types, e.g.
type distance float64
type time float64
type mass float64
type velocity (distance / time)
type acceleration (velocity / time) 
type force (mass * acceleration)
type energy (mass * velocity * velocity)

Such type definitions could be compounded to any (reasonable) degree necessary for the domain of the application.

  • Please also describe the change informally, as in a class teaching Go.
    Consider the following code fragment that attempts to calculate a velocity from the division of distance by time.
type distance float64
type time float64
type velocity float64

var v velocity
var d distance = 100
var t time = 2

v = d / t		//will not compile

The compiler will reject the assignment because the quotient of d and t has a different type from v, even though they have the same underlying types. Specifically, d / t has type (or dimension) distance / time, which is not the same as velocity- at least as far as the compiler knows! We could make it compile by the following explicit type conversions:

v = velocity(float64(d) / float64(t))

However, now we have sacrificed the type checking that we had hoped for by creating types for distance and time; d and t could mistakenly be variables of any type whose underlying type is float64.

A better approach which allows us to retain the type checking is to define the type velocity not as float64 but as the quotient of types distance and time:

type distance float64
type time float64
type velocity (distance / time)

var v velocity
var d distance = 100
var t time = 2

v = d / t		//will compile, retains type checking

Of course, this example is very simple; real scientific and engineering formulas will often have many more variables and types.

  • Is this change backward compatible?
    Yes. This does not remove or change any existing syntax; nor change the semantics of any existing compilable code. It extends the allowed forms for calculations (type mixing) and for specifying types in terms of other types.

  • Orthogonality: how does this change interact or overlap with existing features?
    The concept of dimension is complementary to existing syntax for types, but as far as other language features are concerned, e.g. parameter passing, the end result behaves just like existing types.

  • Is the goal of this change a performance improvement?
    No.

Costs

  • Would this change make Go easier or harder to learn, and why?
    Probably minimal change. One small addition to the syntax for types is added, and a potential confusion about mixing types is removed.

  • What is the cost of this proposal? (Every language change has a cost).
    Modest. Existing code for determining the underlying types of variables and expressions and for converting between types, particularly to and from arithmetic types, will be relevant and can probably be reused. Additional code will be required in the code for parsing arithmetic expressions (see below under proposed implementation).

  • How many tools (such as vet, gopls, gofmt, goimports, etc.) would be affected?
    Compiler only.

  • What is the compile time cost?
    Modest. Additional checking for type compatibility in multiplications and divisions involving defined types whereas today those expressions are quickly rejected as errors.

  • What is the run time cost?
    Nil. After compile-time checking, the code reduces to the underlying types like any other calculation.

  • Can you describe a possible implementation?
    The compiler builds a dependency tree of types defined in terms of other types all the way down to underlying types. When it encounters an expression that multiplies or divides types it traverses the tree down to one level above underlying types, and verifies that dimension matches on both sides. For example, with the definitions

type distance float64
type time float64
type mass float64
type velocity (distance / time)
type acceleration (velocity / time) 
type force (mass * acceleration)
type energy (mass * velocity * velocity)

a variable of type energy has dimension (mass * velocity * velocity) which expands to (mass * (distance / time) * (distance / time)). The compiler will also expand the types of the variables to ensure that they have the same base dimensions, e.g. (force * distance) expands to (mass * acceleration * distance) and finally to (mass * distance / time / time * distance). Note that in order to see that these two are the same dimension, they must be rearranged into a common canonical form. However, this can be as simple as a list of base dimensions and for each a list of their indexes, e.g.
mass^1
distance^2
time^-2

Therefore, verifying compatibility requires only traversing the dependency tree to the bottom and accumulating a count of the index of each base dimension.

  • Do you have a prototype? (This is not required.)
    No.

Long prose description of proposal:
Enhancement request_ Dimensionality of Types.docx

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions