Julia – 34 – tipi – 3

Tipi primitivi A primitive type is a concrete type whose data consists of plain old bits. Classic examples of primitive types are integers and floating-point values. Unlike most languages, Julia lets you declare your own primitive types, rather than providing only a fixed set of built-in ones. In fact, the standard primitive types are all defined in the language itself:

The number of bits indicates how much storage the type requires and the name gives the new type a name. A primitive type can optionally be declared to be a subtype of some supertype. If a supertype is omitted, then the type defaults to having Any as its immediate supertype. The declaration of Bool above therefore means that a boolean value takes eight bits to store, and has Integer as its immediate supertype. Currently, only sizes that are multiples of 8 bits are supported. Therefore, boolean values, although they really need just a single bit, cannot be declared to be any smaller than eight bits.

The types Bool, Int8 and UInt8 all have identical representations: they are eight-bit chunks of memory. Since Julia’s type system is nominative, however, they are not interchangeable despite having identical structure. A fundamental difference between them is that they have different supertypes: Bool‘s direct supertype is Integer, Int8‘s is Signed, and UInt8‘s is Unsigned. All other differences between Bool, Int8, and UInt8 are matters of behavior – the way functions are defined to act when given objects of these types as arguments. This is why a nominative type system is necessary: if structure determined type, which in turn dictates behavior, then it would be impossible to make Bool behave any differently than Int8 or UInt8.

Tipi compostiComposite typesare called records, structs, or objects in various languages. A composite type is a collection of named fields, an instance of which can be treated as a single value. In many languages, composite types are the only kind of user-definable type, and they are by far the most commonly used user-defined type in Julia as well.

In mainstream object oriented languages, such as C++, Java, Python and Ruby, composite types also have named functions associated with them, and the combination is called an “object”. In purer object-oriented languages, such as Ruby or Smalltalk, all values are objects whether they are composites or not. In less pure object oriented languages, including C++ and Java, some values, such as integers and floating-point values, are not objects, while instances of user-defined composite types are true objects with associated methods. In Julia, all values are objects, but functions are not bundled with the objects they operate on. This is necessary since Julia chooses which method of a function to use by multiple dispatch, meaning that the types of all of a function’s arguments are considered when selecting a method, rather than just the first one (see Methods [prossimamente] for more information on methods and dispatch). Thus, it would be inappropriate for functions to “belong” to only their first argument. Organizing methods into function objects rather than having named bags of methods “inside” each object ends up being a highly beneficial aspect of the language design.

Composite types are introduced with the struct keyword followed by a block of field names, optionally annotated with types using the :: operator:

Fields with no type annotation default to Any, and can accordingly hold any type of value.

New objects of type Foo are created by applying the Foo type object like a function to values for its fields:

When a type is applied like a function it is called a constructor. Two constructors are generated automatically (these are called default constructors). One accepts any arguments and calls convert() to convert them to the types of the fields, and the other accepts arguments that match the field types exactly. The reason both of these are generated is that this makes it easier to add new definitions without inadvertently replacing a default constructor.

Since the bar field is unconstrained in type, any value will do. However, the value for baz must be convertible to Int:

You may find a list of field names using the fieldnames function.

You can access the field values of a composite object using the traditional foo.bar notation come ho già fatto.

Composite objects declared with struct are immutable; they cannot be modified after construction. This may seem odd at first, but it has several advantages:

It can be more efficient. Some structs can be packed efficiently into arrays, and in some cases the compiler is able to avoid allocating immutable objects entirely.

It is not possible to violate the invariants provided by the type’s constructors.

Code using immutable objects can be easier to reason about.

An immutable object might contain mutable objects, such as arrays, as fields. Those contained objects will remain mutable; only the fields of the immutable object itself cannot be changed to point to different objects.

Where required, mutable composite objects can be declared with the keyword mutable struct, to be discussed in the next section.

Composite types with no fields are singletons; there can be only one instance of such types:

The === function confirms that the “two” constructed instances of NoFields are actually one and the same. Singleton types are described in further detail prossimamente.

There is much more to say about how instances of composite types are created, but that discussion depends on both Parametric Types [prossimamente] and on Methods [prossimamente], and is sufficiently important to be addressed in its own section: Constructors [prossimamente].