You are viewing an older version of the site. Click here to view
the latest version of this page. (This may be a dead link, if so, try the root page of the docs
here.)
MethodScript is an optionally strongly typed language. This means that variables and procedure/closure input and outputs
declare the '''type''' that they are, and only values of that type are allowed to be assigned to that variable, or
returned by the procedure/closure. It is optionally strongly typed, because - outside of strict mode - it is not
required that you provide a type.
Copy Code
We are actually doing two separate operations in this one line, we are ''declaring'' a new variable,
Copy Code
Now, we would get the error: "ms.lang.CastException: @s is of type ms.lang.string, but a value of type ms.lang.array
was assigned to it."
This can help prevent future errors in your code, by preventing use of a value that isn't intended. Sometimes this can
help catch bugs that would otherwise be extremely difficult to catch otherwise. A clearer example of this will be shown
later.
== Procedure and Closure Typing ==
Both the inputs and output of both procedures and closures can be typed.
Copy Code
In both these cases, we have declared the type on the input and output. We have stated that the procedure _myProc must
return a value of type string, and accept an input parameter of type int. Likewise, we have stated that the closure must
return a value of type int, and takes an input of type string. (Incidentally, we have also defined the variable @c,
which must be of type closure.)
===
Copy Code
== Putting it together ==
This becomes much clearer when we start adding types everywhere. Consider the following example:
Copy Code
In this example, if the value exported to 'username' had been set as something other than a string, we would fail on
line 2. Perhaps accidentally, we had stored an array to the value, in that case, the user would be messaged the whole
array. This can prevent us from continuing with the code after we have gotten into an unexpected state. Once these type
errors are moved into the compiler, it makes even more sense, because then we would be able to catch these errors even
faster, and not require the code to ever be run before we found the error.
== Subclasses ==
Each defined type is actually a subclass of one or more ''parent types'', (known as ''superclasses'')
with the exception of the root type,
Copy Code
This is valid because
Copy Code
==
Copy Code
We can prove this by using the {{function|always_trace}} function:
Copy Code
Values that are of type auto bypass the compilation checks, though they may still cause errors at runtime.
Copy Code
Initially, one might wonder the difference between using
Copy Code
Effectively,
Copy Code
== Helper Functions ==
When dealing with types, it's useful to note some various helper functions.
===
Copy Code
===
Copy Code
{{function|typeof}} returns the ClassType object, which we can then use to gather data about a particular type:
Copy Code
== Caveats ==
There are unfortunately a few loose ends that are caveats at this stage of implementation, which are planned to be
implemented, but aren't complete yet.
Copy Code
The reason this works is because currently, the type system is ''type hinting'', not strong typing. Since the type
system is implemented at runtime, rather than at compile time, we don't look at the second line of code until after
the first line is run. In this case, we see that @m is in fact a string, and so we allow it to be assigned to @s.
Eventually, this will be corrected, and will be an error. However, @m actually does contain a string, so how would we
make this work? Through an act called ''casting''. When we ''cast'' a value, we tell the compiler that we are positive
that the current value is of the given type. We may have determined this through clever use of instanceof or simply
hardcoded a particular value, but we're essentially telling the compiler to trust us, and don't cause an error.
The syntax will be:
Copy Code
Casting upward is not allowed, because subclasses are always a valid instance of a superclass.
Copy Code
Note that some casts will be invalid anyways. If there's simply no way for a value to be an instance of the particular
type, that cast won't be allowed.
Copy Code
=== Cross Casting ===
Cross casting is a runtime conversion of values from one type to another. Currently, cross casting is implemented
opaquely in primitive objects, but is not "exposed" to the end user in a formal way. Consider the following code:
Copy Code
In this case, we are comparing a string and an integer to each other, and find that they are in fact equal. Why is this?
This is due to cross casting. We effectively convert the string value to an integer, then compare it. Formally speaking,
this is due to ''cross casting''. Eventually, string will be formally declared as cross castable to integers, and vice
versa. This means that when we try to cast, we will make a runtime conversion to the expected value.
Copy Code
This casting is not automatic for typed variables, you must explicitely provide the cast to do the conversion. However,
this is not true for values of type
Copy Code
However, this functionality is not yet implemented, meaning the code will cause an error, despite it intended to be
valid code. We currently get
Copy Code
This will continue to work even after this feature is implemented, so there is no code migration risk by doing it this
way. It's also worth noting that literals in code will be considered auto for the purposes of cross casting, so things
like this will also eventually work:
Copy Code
If you would like to read more about how this feature is intended to work, see the design document
[[Cross_Casting|here]].
=== Declarations within Expressions ===
Currently, the following is possible, but is officially considered "undefined behavior", meaning that it is subject to
change or removal in future versions:
Copy Code
Do not rely on this behavior, as assignments are intended to be separate statements. Instead, rewrite the code to the
following:
Copy Code
This is easier to read in the future anyways.
Note: Currently, the implementation is in the runtime, meaning that you have to run the code before you'll see errors.
Work is ongoing to make this part of the compiler itself, so the errors can be caught earlier in the process.
== Variable Typing ==
The simplest example is this:
string @s = "string";

1 {{object|string}} @s = "string";
@s
,
and stating that it is of type string
. Secondly, we are assigning the string "string"
to the
variable. This seems a bit silly at first, since it seems like we're duplicating information, after all, we can clearly
see that it's a string being assigned. However, consider later code that then uses @s, and perhaps tries to re-assign
the value.
@s = array();

1 @s = {{function|array}}();
string proc _myProc(int @a) {
return(string(@a));
}
closure @c = int closure(string @in) {
return(integer(@in));
};

1 {{object|string}} {{keyword|proc}} {{function|_myProc}}({{object|int}} @a) {
2 {{function|return}}({{function|string}}(@a));
3 }
4
5 {{keyword|closure}} @c = {{object|int}} {{keyword|closure}}({{object|string}} @in) {
6 {{function|return}}({{function|integer}}(@in));
7 };
void
Type ===
Specifically for procedures and closures, it may be that they do not return a value at all. These are said to
''return void'', meaning that they don't have a return type. You can explicitely declare this with the void
type.
void proc _myProc() {
msg('Hello World!');
return(); // This is optional, procs and closures that don't have a return are implied to return void.
}

1 {{object|void}} {{keyword|proc}} {{function|_myProc}}() {
2 {{function|msg}}('Hello World!');
3 {{function|return}}(); // This is optional, procs and closures that don't have a return are implied to return void.
4
5 }
string proc _getUsername() {
return(import('user'));
}
string @username = _getUsername();
msg("Hello @username!");

1 {{object|string}} {{keyword|proc}} {{function|_getUsername}}() {
2 {{function|return}}({{function|import}}('user'));
3 }
4
5 {{object|string}} @username = {{function|_getUsername}}();
6 {{function|msg}}("Hello @username!");
mixed
, which has no superclasses. For any given type,
it is always allowed to replace it with a subtype of that class with no error. Consider the following:
int @i = 1;
number @n = @i;

1 {{object|int}} @i = 1;
2 {{object|number}} @n = @i;
int
is a subtype of number
. We can discover the superclasses for a
given type a number of ways, but we can use {{function|reflect_type}} to get this information.
We can get the full superclass chain with the following code:
ClassType @type = int; // We could use class_type('ms.lang.int') if we wanted to grab the type dynamically
msg("Finding superclasses for @type");
while(true) {
@super = reflect_type(@type)['superclasses'];
if(length(@super) == 0) {
break();
}
msg(@super);
@type = @super[0]; // Just grab the first one, there can be multiple though.
}

01
02 {{object|ClassType}} @type = {{object|int}}; // We could use class_type('ms.lang.int') if we wanted to grab the type dynamically
03
04
05 {{function|msg}}("Finding superclasses for @type");
06
07 {{keyword|while}}({{keyword|true}}) {
08 @super = {{function|reflect_type}}(@type)['superclasses'];
09 {{keyword|if}}({{function|length}}(@super) == 0) {
10 {{function|break}}();
11 }
12 {{function|msg}}(@super);
13 @type = @super[0]; // Just grab the first one, there can be multiple though.
14
15 }
auto
Type and mixed
Type ==
There is a special type called auto
, which essentially means "don't use the typing system on this value."
In fact, values that are declared without a type are actually declared as type auto
! These two declarations
are exactly the same:
@b = 'test';
auto @c = 'test';

1 @b = 'test';
2 {{keyword|auto}} @c = 'test';
always_trace(@b); // <<main code>>:Interpreter:1.1: auto (actual type ms.lang.string, length: 4) @b: test
always_trace(@c); // <<main code>>:Interpreter:1.1: auto (actual type ms.lang.string, length: 4) @c: test

1 {{function|always_trace}}(@b); // <<main code>>:Interpreter:1.1: auto (actual type ms.lang.string, length: 4) @b: test
2
3 {{function|always_trace}}(@c); // <<main code>>:Interpreter:1.1: auto (actual type ms.lang.string, length: 4) @c: test
auto @s = 'string';
array @a = @s; // Not a compile error but will be a runtime error.

1 {{keyword|auto}} @s = 'string';
2 {{object|array}} @a = @s; // Not a compile error but will be a runtime error.
auto
and mixed
. Using mixed still
follows the type system, and requires manual casting to convert values.
mixed @m = 'string'; // Valid, string is a subtype of mixed
string @s = @m; // Currently valid, but will become invalid with the compiler type system. (See caveats below.)
string @r = string(@m); // Will always be valid.
auto @a = 'string';
string @t = @a; // Will always be valid.

1 {{object|mixed}} @m = 'string'; // Valid, string is a subtype of mixed
2
3 {{object|string}} @s = @m; // Currently valid, but will become invalid with the compiler type system. (See caveats below.)
4
5 {{object|string}} @r = {{function|string}}(@m); // Will always be valid.
6
7
8 {{keyword|auto}} @a = 'string';
9 {{object|string}} @t = @a; // Will always be valid.
auto
types are assumed to be the correct type no matter how they're being used. Types stored
in mixed
must first be cast to the correct type. Both approaches have merit, and so it depends on what
you're doing as to what mechanism you should use.
== null
==
Null is a special value which can be assigned to all types. Also, note that ''forward declarations'', that is, where
you define a value but don't set it, default to null.
string @a;
msg(@a); // null
string @b = null;
msg(@b); // null

1 {{object|string}} @a;
2 {{function|msg}}(@a); // null
3
4
5 {{object|string}} @b = {{keyword|null}};
6 {{function|msg}}(@b); // null
instanceof
===
{{function|instanceof}} tells you if the ''concrete type'' of a value is of a certain type.
This is a runtime operation, so it has nothing to do with the type that the variable was declared with, but the type
that the value itself is right now.
mixed @m = 'string';
if(@m instanceof string) {
msg("It's a string");
} else if(@m instanceof int) {
msg("It's an int");
}

1 {{object|mixed}} @m = 'string';
2 {{keyword|if}}(@m {{keyword|instanceof}} {{object|string}}) {
3 {{function|msg}}("It's a string");
4 } {{keyword|else}} {{keyword|if}}(@m {{keyword|instanceof}} {{object|int}}) {
5 {{function|msg}}("It's an int");
6 }
typeof
===
Similar to {{function|instanceof}} we can simply find out the current concrete type.
mixed @m = 'string';
msg(typeof(@m)); // ms.lang.string

1 {{object|mixed}} @m = 'string';
2 {{function|msg}}({{function|typeof}}(@m)); // ms.lang.string
mixed @m = 'string';
msg(reflect_type(typeof(@m)));
/*{
fqcn: ms.lang.string,
interfaces: {ms.lang.Iterable},
isNative: true,
name: string,
package: ms.lang,
superclasses: {ms.lang.primitive},
typeDocs: {{
docs: A string is a value that contains character data. The character encoding is stored with the string as well.,
since: 3.0.1
}}
}*/
// We can also get other information about it with always_trace:
msg(always_trace(@m));
// <<main code>>:Interpreter:1.26: ms.lang.mixed (actual type ms.lang.string, length: 6) @m: string

01 {{object|mixed}} @m = 'string';
02 {{function|msg}}({{function|reflect_type}}({{function|typeof}}(@m)));
03 /*{
04 fqcn: ms.lang.string,
05 interfaces: {ms.lang.Iterable},
06 isNative: true,
07 name: string,
08 package: ms.lang,
09 superclasses: {ms.lang.primitive},
10 typeDocs: {{
11 docs: A string is a value that contains character data. The character encoding is stored with the string as well.,
12 since: 3.0.1
13 }}
14 }*/
15
16
17 // We can also get other information about it with always_trace:
18
19
20 {{function|msg}}({{function|always_trace}}(@m));
21 // <<main code>>:Interpreter:1.26: ms.lang.mixed (actual type ms.lang.string, length: 6) @m: string
Note: Not all of the code listed below actually works, or is an example of bad code, and is for demonstration purposes
only. Read the text carefully before applying any examples here.
=== Casting ===
Casting is the act of bypassing various compiler checks when dealing with subclasses. This bypasses some of the type
safety, but is required in some cases. Consider the following code from one of the examples above.
mixed @m = 'string'; // Valid, string is a subtype of mixed
string @s = @m; // Currently valid, but will become invalid with the compiler type system.

1 {{object|mixed}} @m = 'string'; // Valid, string is a subtype of mixed
2
3 {{object|string}} @s = @m; // Currently valid, but will become invalid with the compiler type system.
mixed @m = 'string';
string @s = @m as string;

1 {{object|mixed}} @m = 'string';
2 {{object|string}} @s = @m {{keyword|as}} {{object|string}};
string @s = 'string';
mixed @m = @s; // No cast necessary, string is a mixed.

1 {{object|string}} @s = 'string';
2 {{object|mixed}} @m = @s; // No cast necessary, string is a mixed.
string @s = 'string';
array @a = @s as array; // Compile error, string cannot be cast to array, since string is not a subtype of array.

1 {{object|string}} @s = 'string';
2 {{object|array}} @a = @s {{keyword|as}} {{object|array}}; // Compile error, string cannot be cast to array, since string is not a subtype of array.
msg('1' == 1); // true

1 {{function|msg}}('1' == 1); // true
string @s = '1';
int @i = @s as int; // Cross cast, since int does not decend from string, but string declares it can cross cast to int

1 {{object|string}} @s = '1';
2 {{object|int}} @i = @s {{keyword|as}} {{object|int}}; // Cross cast, since int does not decend from string, but string declares it can cross cast to int
auto
, whether explicitely typed as such or implicitely typed due to no
type being provided. Consider the same code as above but with @s being auto:
auto @s = '1';
int @i = @s; // At runtime, we see that @s is a string, but we are expecting an int. Since @s is auto, we automatically
// cross cast it, making this effectively the same as the above example.

1 {{keyword|auto}} @s = '1';
2 {{object|int}} @i = @s; // At runtime, we see that @s is a string, but we are expecting an int. Since @s is auto, we automatically
3
4 // cross cast it, making this effectively the same as the above example.
ms.lang.CastException: @i is of type ms.lang.int, but a value of
type ms.lang.string was assigned to it.
In the meantime, to work around this, you must explicitely cast the value using one of the various manual cast methods
that each primitive has. In this case, {{function|integer}}.
auto @s = '1';
int @i = integer(@s);

1 {{keyword|auto}} @s = '1';
2 {{object|int}} @i = {{function|integer}}(@s);
int @i = '1';
// This will work even further in the future, once effectively final calculations are implemented
string @s = '1';
int @i = @s; // We know that @s is equal to '1' at this point, because it hasn't been changed anywhere above here
// and the declaration, so we can treat it as if we had typed int @i = '1';

1 {{object|int}} @i = '1';
2
3 // This will work even further in the future, once effectively final calculations are implemented
4
5 {{object|string}} @s = '1';
6 {{object|int}} @i = @s; // We know that @s is equal to '1' at this point, because it hasn't been changed anywhere above here
7
8 // and the declaration, so we can treat it as if we had typed int @i = '1';
int @a = 1 + (int @b = 3);
msg(@a); // 4
msg(@b); // 3

1 {{object|int}} @a = 1 + ({{object|int}} @b = 3);
2 {{function|msg}}(@a); // 4
3
4 {{function|msg}}(@b); // 3
int @b = 3;
int @a = 1 + @b;
msg(@a); // 4
msg(@b); // 3

1 {{object|int}} @b = 3;
2 {{object|int}} @a = 1 + @b;
3 {{function|msg}}(@a); // 4
4
5 {{function|msg}}(@b); // 3
Find a bug in this page? Edit this page yourself, then submit a pull request.