eclipse / mita

mita
Eclipse Public License 2.0
56 stars 20 forks source link

RFC: Default values #330

Open wegendt-bosch opened 5 years ago

wegendt-bosch commented 5 years ago

Introduction

High level language support types that are more defined than numbers, structures and pointers. The most ubiquitous one are Objects. These are structures that carry more semantics and group together functions operating on them. They also support different constructors like a default constructor or constructors that do special initialization.

Some languages then use these default constructors to facilitate common code paths, for example in C++ declaring a variable without an explicit constructor call will call the default constructor, or in Kotlin variables with a nullable type are initialized to null. These are all some kind of default value for types declaring one.

Current Situation

Currently we validate that some types have an explicit initialization, like references, and initialize all other types with some kind of 0. Booleans are false, integers zero, structures are filled with 0, and so on. Sum types per default initialize to their first member with everything set to zero, again, by calling = {0} in C, which sets the tagged union to the first enum value and fills everything else with 0.

This has been fine for now but a more refined system can be imagined.

Proposal

I propose that types may declare a default value. If they do, users may write variables without initialization:

var i: uint32;

If they don't, there are different cases for a default default value (so the default thing we do for default values):

Product Types

Product types have a default default value if and only if all their member types have default values.

Sum Types

For sum types there are three different strategies to be discussed:

Generated Types

Generated Types have a default if and only if they declare a constructor without any arguments.

Code Example

I propose one of the following ways for declaring explicit default values, all backwards compatible by leaving out the new syntax. Other proposals or better variations are welcome.

Variant 1: Assignment

This variant is the most straight-forward one, but can become cluttered the easiest.

struct vec2d {
  var x: int32;
  var y: int32;
} = vec2d(1, 2)

alt OptionalInt32 {
  Some: int32 | None
} = .None()

export native-type int32 alias-of int32_t header "stdint.h" = 0

Variant 2.a: Haskell-style properties

In this variant and 2.b types can be followed by a block of properties. This would be forward-compatible with more extensions to types.

In Haskell, where is a common way of creating a block of follow up declarations like helper functions, type class instance functions, etc. Recommended style is inline or indented block style.

struct vec2d {
  var x: int32;
  var y: int32;
} where default: vec2d(1, 2)

alt OptionalInt32 {
  Some: int32 | None
} where default: .None()

export native-type int32 alias-of int32_t header "stdint.h" where default: 0

Variant 2.a: Json-style properties

Instead of where <block> we could also use some json-like syntax with brackets. This seems a bit more familiar, but is also more confusing since there are two blocks right after one another.

struct vec2d {
  var x: int32;
  var y: int32;
} {
  default: vec2d(1, 2)
} 

alt OptionalInt32 {
  Some: int32 | None
}  {
  default: .None()
}

export native-type int32 alias-of int32_t header "stdint.h" {
  default: 0
}

Variant 3: Separate declaration

This variant is the cleanest, however it is harder to parse, use, and may create the impression that the declaration is a real statement.

struct vec2d {
  var x: int32;
  var y: int32;
} 

default(vec2d) = vec2d(1, 2)

alt OptionalInt32 {
  Some: int32 | None
} 

default(OptionalInt32) = .None()

export native-type int32 alias-of int32_t header "stdint.h" 

default(int32) = 0

Variant 3.a: Embrace the state

Indeed, default(type: Kind): type could be an actual Expression that is read/writeable at runtime, if something like that is desired:

export native-type int32 alias-of int32_t header "stdint.h" 
default(int32) = 0;

every button_one.pressed {
  default(int32) = 1 - default(int32);
  let a: int32;
  // prints 1, 0, 1, 0, ... on each button press
  println(`${a}`);
}

This would then be implemented like this:

// MitaTypes.h:
int32_t int32_default = 0;

// Application.c:
/* ... */
Retcode_T handleEveryButtonOnePressed() {
  int32_default = 1 - int32_default;
  int32_t a = int32_default;
  printf("%d\n", a);
}
tkutz commented 5 years ago

+1 for solution (1) and (2.a haskell).

(2.a json) is a bit too much curly braces everywhere. Curly braces make sense to group things together, but when you have only one default statement inside of them, it feels overloaded with braces.

(3) gives quite some degree of freedom to the user. I am not sure if we really want this. You need to derive the default value in runtime as it can be changed everywhere. For me, a default value definition belongs closely to the type definition and is something static.