# Julia Notes Day3 -- Composite and Parametric Types

Composite types are called records, structs, or objects in various languages.

```julia
struct Foo
    bar
    baz::Int
    qux::Float64
end

foo = Foo("Hello, world.", 23, 1.5) # Foo("Hello, world.", 23, 1.5)

foo.bar # "Hello, world."
```

> Composite objects declared with `struct` are *immutable*; they cannot be modified after construction.
> 
> 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`.

In cases where one or more fields of an otherwise mutable struct is known to be immutable, one can declare these fields as such using `const` as shown below.

```julia
mutable struct Baz
    a::Int
    const b::Float64
end

baz = Baz(1, 1.5) # Baz(1, 1.5)
baz.a = 3;
baz # Baz(3, 1.5)
```

An important and powerful feature of Julia's type system is that it is parametric: types can take parameters, so that type declarations actually introduce a whole family of new types – one for each possible combination of parameter values.

```julia
struct Point{T<:Real}
	x::T
	y::T
end
```

Without any explicitly provided inner constructors, the declaration of the composite type `Point{T<:Real}` automatically provides an inner constructor, `Point{T}`, for each possible type `T<:Real`, that behaves just like non-parametric default inner constructors do. It also provides a single general outer `Point` constructor that takes pairs of real arguments, which must be of the same type. This automatic provision of constructors is equivalent to the following explicit declaration:

```julia
struct Point{T<:Real}
   x::T
   y::T
   Point{T}(x,y) where {T<:Real} = new(x,y)
end

Point(x::T, y::T) where {T<:Real} = Point{T}(x,y);
```

By default, instances of parametric composite types can be constructed either with explicitly given type parameters or with type parameters implied by the types of the arguments given to the constructor. Here are some examples:

```julia
p1 = Point(7,5) ## implicit T ## Point{Int64}(7,5)
p2 = Point(2,3) ## implicit T ## Point{Int64}(2,3)

Point{Float64}(1,2) ## explicit T ## Point{Float64}(1.0, 2.0)

p1 = Point(5,1.5) 
# ERROR: MethodError: no method matching Point(::Int64, ::Float64)
```

For a more general way to make all such calls work sensibly, all it takes is the following outer method definition to make all calls to the general `Point` constructor work as one would expect:

```julia
Point(x::Real, y::Real) = Point(promote(x,y)...);

# Point(Float64, Int) -> Point(Float64, Float64)
Point(1.5,2) # Point{Float64}(1.5, 2.0)
```

The `promote` function converts all its arguments to a common type – in this case `Float64`. With this method definition, the `Point` constructor promotes its arguments the same way that numeric operators like `+` do, and works for all kinds of real numbers.

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 Julia, all values are objects, but functions are always **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.

```julia
Base.show(io::IO, z::Point) = print(io, "{\n  x=$(z.x)\n  y=$(z.y)\n}")

Point(2.3, 5)
#= 
{
  x=2.3
  y=5.0
}
=#
```
