User Defined Types

Claro's type system already provides a very expansive expressive power to represent arbitrarily complex data structures, so, technically speaking, there is no hard requirement for a user to ever define any new types in order write any program. However, by using only the builtin primitive and collection types, you will not be able to leverage Claro's static type validation to ensure that semantic differences between values with structurally equivalent types are actually maintained.

This section will attempt to clarify how you can make use of user defined types to enforce semantic constraints throughout your program.

Declaring a New Type

The example below demonstrates the declaration of a new type that wraps int.

Fig 1:


newtype Foo : int

In fact, user defined types can wrap any type - even other user defined types:

Fig 2:


# The order of `newtype` declarations is unimportant.
newtype Baz : int
newtype Foo : Bar
newtype Bar : Baz

Instantiating an Instance of a User Defined Type

Claro automatically provides a one-arg constructor that allows the user defined type to be instantiated by wrapping the declared type.

Fig 3:


var f = Foo(1);
print(f);

Output:

Foo(1)

User Defined Types "Wrap" an Instance of Another Type

Because Claro's builtin types already enable modelling any arbitrary data structure, the purpose of user defined types is solely to "wrap" an existing type in a statically enforceable, semantic layer that distinguishes instances of the user defined type, from the type that is being wrapped. As such, Claro does not do any automatic conversions from the wrapped type to the unwrapped type.

So, although newtype Foo : int simply wraps int, it is not interchangeable with int and therefore operations like + are not supported for Foo even though they are for int.

Fig 4:


newtype Foo : int
var f = Foo(1);
print(f + 9);

Compilation Errors:

user_defined_types_EX4_example.claro:3: Invalid type: found <Foo>, but expected one of (<int, long, float, double>).
print(f + 9);
      ^
1 Error

"Unwrapping" a User Defined Type

The wrapped type can be accessed by explicitly using the builtin unwrap() function.

Fig 5:


newtype Foo : int
var f = Foo(1);
print(unwrap(f) + 9);

Output:

10

Compile Time Enforcement

In the Aliases section an example was given that demonstrates the pitfall of the overuse of aliases. One primary source of errors could be addressed by simply declaring a new type for each of MPH, Hours, and Miles. In this case, this statically prevents accidentally passing args to the function out of order:

Fig 6:


newtype MPH : double
newtype Hours : double # Arguably you should be using `duration::Duration`.
newtype Miles : double

function timeTraveled(speed: MPH, distanceTraveled: Miles) -> Hours {
  return Hours(unwrap(distanceTraveled) / unwrap(speed));
}

# Claro can identify this type mismatch.
print(timeTraveled(Miles(60.0), MPH(15.0)));

Compilation Errors:

user_defined_types_EX6_example.claro:10: Invalid type:
	Found:
		Miles
	Expected:
		MPH
print(timeTraveled(Miles(60.0), MPH(15.0)));
                   ^^^^^^^^^^^
user_defined_types_EX6_example.claro:10: Invalid type:
	Found:
		MPH
	Expected:
		Miles
print(timeTraveled(Miles(60.0), MPH(15.0)));
                                ^^^^^^^^^
2 Errors

The above error message would lead you to correct the order of arguments and thereby fix the problem:

Fig 7:


print(timeTraveled(MPH(15.0), Miles(60.0)));

Output:

Hours(4.0)