Types

MiniD is dynamically typed, meaning that there are types, but variables are not typed, only the values they hold. So it's perfectly legal to write

local x = 5;
x = "hello";
x = [4, 5, 6];

As far as the strength of the typing is concerned, MiniD leans toward the strong side. This means that the language tends not to perform implicit conversions between types. One exception is that if a mathematical operation is performed on one integer and one floating-point number, in which case the integer will be implicitly converted to a float.

There are two categories of types: value types and reference types.

Value Types

Value types have value semantics, that is, they are copied when they are assigned into a destination. Value types are not allocated on the heap unlike in some languages (like Python), so you don't have to worry about, for example, a loop full of number-crunching generating a ton of garbage.

TypeDescription
nullAbsence of a useful value
boolTruth value
intSigned 2's complement native-word-sized integer
floatDouble-precision IEEE 754 floating-point number
charSingle UTF-32 codepoint

null is the value with which all uninitialized variables are initialized. null is a type of its own, and has only one value, which is null. null always equals (and is identical to (using "is")) null. In multiple assignments, extra variables on the left-hand side are given the value null. When calling functions, if not all parameters are given values, they are given null instead.

A bool can only hold the values true and false. You cannot perform any kind of mathematical operations on them. You can only assign their value, test for equality, and compare them (false is less than true).

An int is defined as the host machine's native signed integer type, with the additional constraint that it must be at least 32 bits.

A float is the same as a double found in many C-style languages: a double-precision IEEE 754 floating-point number.

In Lua, there is only one number type, and it (usually) corresponds to a double-precision floating point number. While this simplifies the implementation and also allows for any value that a 32-bit integer can store to be stored, the problem with this is speed. Most calculations, in general, are performed using integers. Things like loop and array indices really only make sense with integers. It seems rather inefficient to use a 64-bit floating-point type to loop from 1 to 10.

A char is like D's dchar. It can hold any UTF-32 codepoint. You can compare characters, but they will be compared by value and not lexicographically. Characters can be concatenated together to form strings, and can be concatenated with strings as well.

Reference Types

Reference types are allocated on the heap, and when assigned into a destination, only a reference to the same object is copied and not the object itself.

TypeDescription
stringA UTF-32 string of text
tableA set of key-value pairs, where keys can be any type (except null and boolean)
arrayA list of values of any type, indexable by consecutive integers starting at 0
functionA reference to a function closure
objectA user-defined object
namespaceA special sort of table for holding declared variables
threadA collaborative thread of execution
nativeobjA lightweight reference to a host-application object
weakrefA weak reference to any reference-typed object

Strings are fairly obvious. In MiniD, they are UTF-32, for two reasons: one, there are no messy multibyte encodings to deal with as in UTF-8 or UTF-16. And two, using UTF-32 means that MiniD strings can hold virtually any character in any language ever to have existed (or not existed, as well as all kinds of other confusingly-included symbols). Can't say the same about ASCII, or Latin-1, or whatever the current locale 8-bit character set happens to be.

Note that implementations are free to represent strings internally using physically smaller encodings (i.e. switching to UTF-8 if nothing above U+0007F were used). The only requirement is that the language sees strings as if they were a sequence of UTF-32 characters.

In MiniD, unlike in D, strings are immutable. When concatenating strings, a new string is created; same with appending strings. Because this can make string manipulation rather awkward or inefficient, MiniD provides the StringBuffer object in the base library to serve as a mutable string type.

Strings are reference types but their immutability places them in an odd place somewhere between a value and a reference type. Implementations are required to "intern" strings; that is, for a given string value, there will be exactly one string object that holds it. This way, common strings (such as field and global names) will not be unnecessarily duplicated, and a test for equality can internally be replaced with a simple test for identity.

Tables are a useful container type. They are a kind of map, mapping from any type except null to any type except null. Not being able to have null values seems like a disadvantage, but in fact, if a table is indexed and the key does not exist, null is returned, which means it's more like null values are stored for free.

Tables do not have quite the power that they do in Lua, however; most of the object-oriented aspects that can be achieved in Lua are accomplished using objects in MiniD.

Arrays are what you'd expect - a sequential list of values of any type, indexed by integers. Indices start at 0, and go to the length of the array minus one (as in most C-style languages).

Arrays exist, like integral types, for both performance and conceptual reasons. In Lua 5, tables can act like arrays, based on an algorithm that detects if the table is being used as an array, in which case the values will be stored in a true array instead of in the hash map. This is complex, and a fancy algorithm will never be as fast as the real thing. When you need a list of values indexed by (contiguous) integers, use an array; otherwise, use a table.

Slices of arrays always point into the array from which they were sliced, making it possible to manipulate subsections of arrays (i.e. sort just a small part of an array). Concatenating arrays always creates a new array object. Appending to an array (either with ~= or with the append method) will attempt to expand the array in place, but if there is not enough room, will reallocate the array.

Functions are references to function closures, either written in MiniD or created by the host program. A function closure has everything it needs to properly execute the function. See Functions for more information on closures.

Objects are user-defined object types. Unlike in D, which has classes and instances of those classes, MiniD uses a prototype-based object system where there is no distinction between a class and an instance (there is also no distinction between inheritance and instantiation). See Objects for more information.

Namespaces are a lot like tables. However, their keys are restricted to strings, and they can hold null values. The ability to hold null values is important, because unlike tables, attempting to access something from a namespace that doesn't exist will throw an error. These are used mostly as symbol tables, for things like global variables, modules, and object fields.

Threads are created when you use a coroutine expression. They represent a separate thread of execution with its own call stack. Threads in MiniD work collaboratively; that is, a thread will only stop executing when it uses a yield expression or when it returns from its main function. See the functions section for more info.

Native Objects are a very lightweight references to objects held by the host application. What that actually means may depend upon the implementation. For example, in the reference D implementation, native objects are used in order to hold references to D objects that are allocated on the host app's heap, and are handled specially so that the host application will not collect the referenced D objects. In a C implementation, they might just be simple pointers, and in fact might be implemented as value types instead. In any case, native objects cannot be created by script code and have no operations defined for them other than identity.

Weak References are a way to get a reference to another reference types without controlling its lifetime. Normally, if an object is transitively accessible through any chain of references from either the stack or the globals, that object is considered alive and is not collected by the GC, and if it's not accessible, it will be collected. Weak references allow you to sort of keep tabs on an object without the GC thinking that you want it to be alive. You can create a weak reference to an object, and if no "strong" references to that object exist, it will be collected and the weak reference will be updated to reflect that. Weak references cannot be created to value types as that has no meaning.

Weak references must by their nature be interned: for any reference type object, there can be only one weak reference object that references it. Therefore, if two weak references are identical, they refer to the same object.

Changes from MiniD 1

int is now defined as the native word size of the host.

The class and instance types have been replaced by the object type.

The nativeobj and weakref types have been introduced.