Everyone can use types in their work, such as Int64
, String
, Float64
, Bool
.
However, if you use these types directly, you will make your code messy and not easy to read. Thus, you will get difficult in debugging your code.
To address this, we need high-level encapsulation and design high-level types for ourselves.
In dynamic type languages, only value has type, not variable.
3::Int64
3
The code above confirms that 3
is Int64
type, so the code here is more like an assertion.(斷言)
x = 3::Int64
3
typeof(x)
Int64
Composite type composes several variables in a type. Thus, we can encapsulate a concept into a type and implement that concept.
struct Rectangle
height::Int64
width::Int64
end
p.s. Naming of a type should follow the camel case.
p.s. Here type can be bound to variables.
rec = Rectangle(4, 5)
Rectangle(4, 5)
rec.height
4
rec.width
5
rec.width = 8
setfield! immutable struct of type Rectangle cannot be changed Stacktrace: [1] setproperty!(::Rectangle, ::Symbol, ::Int64) at ./Base.jl:34 [2] top-level scope at In[8]:1
In the default setting, struct
provides immutable type definition for us. If you want mutable type, use mutable struct
instead.
mutable struct MutableRectangle
height::Int64
width::Int64
end
mrec = MutableRectangle(4, 5)
MutableRectangle(4, 5)
mrec.width = 8
8
f(x::Float64, y::Float64) = 2x + y
f (generic function with 1 method)
f(2.0, 3.0)
7.0
Isn't it looks like a function?
f(x::Number, y::Number) = 2x - y
f(2.0, 3)
1.0
You will found that Julia dispatch f(Float64, Int64)
to the method f(Number, Number)
.
methods(f) # you can check the current methods implemented as this function name
Thus, in Julia, function means the interface f
.
Method means the implementation f(x::Number, y::Number) = 2x - y
.
Programming language, just like natural language, in different context, one word has different meanings.
The most diverse thing is the behavior.
In different context, we have different implementation for different meanings.
In Julia, if there are many methods which has the same function name, which one to dispatch?
Julia will dispatch method accroding to the number of parameters and the parameter types.
Using all parameters in function call to determine which method to call is so called multiple dispatch.
g(x::Float64, y) = 2x + y
g (generic function with 1 method)
g(x, y::Float64) = x + 2y
g (generic function with 2 methods)
g(3.0, 4.0)
MethodError: g(::Float64, ::Float64) is ambiguous. Candidates: g(x::Float64, y) in Main at In[16]:1 g(x, y::Float64) in Main at In[17]:1 Possible fix, define g(::Float64, ::Float64) Stacktrace: [1] top-level scope at In[18]:1
這樣的定義會造成語意不清,g(Float64, Float64)
要執行哪一條呢
g(x::Float64, y::Float64) = 2x + 2y
g(x::Float64, y) = 2x + y
g(x, y::Float64) = x + 2y
g (generic function with 3 methods)
g(2.0, 3)
7.0
g(2, 3.0)
8.0
g(2.0, 3.0)
10.0
Defining methods as usual.
area(x::Rectangle) = x.height * x.width
area (generic function with 1 method)
Override the interface to customize the representation of an oject.
Base.show(io::IO, x::Rectangle) = println("Rectangle(h=$(x.height), w=$(x.width))")
rec
Rectangle(h=4, w=5)
abstract type Shape end
struct Rectangle <: Shape
height::Float64
width::Float64
end
struct Triangle <: Shape
base::Float64
height::Float64
end
area(x::Rectangle) = x.height * x.width
area(x::Triangle) = 0.5 * x.base * x.height
area (generic function with 2 methods)
rec = Rectangle(4, 5)
tri = Triangle(4, 5)
Triangle(4.0, 5.0)
area(rec)
20.0
area(tri)
10.0
Rectangle
and Triangle
are so called concrete type(具體型別), while Shape
is abstract type(抽象型別).
Concrete types can be instantiated(實體化) but abstract type cannot.
Shape()
MethodError: no constructors have been defined for Shape Stacktrace: [1] top-level scope at In[6]:1
All types have super type(父型別).
supertype(Rectangle)
Shape
supertype(Shape)
Any
Especially, concrete type(具體型別) doesn't have subtype. That is, you cannot make a type as the subtype of a concrete type, so all the concrete types are the leaves in type hierarchy.
struct Square <: Rectangle
end
invalid subtyping in definition of Square Stacktrace: [1] top-level scope at /home/yuehhua/.julia/packages/IJulia/DrVMH/src/kernel.jl:52
Any
type is the super type of all types¶Once a new struct if defined, Any
type will be the supertype of that type automatically.
Shape <: Any
true
Union
type¶Union
type is used in the context of allowing more than one types which don't belong to the same type hierarchy.
const IntOrFloat64 = Union{Int64, Float64}
Union{Float64, Int64}
struct Foo
x::IntOrFloat64
end
Foo(5)
Foo(5)
Foo(5.)
Foo(5.0)
Union
type¶foo(x::IntOrFloat64) = println(x)
foo (generic function with 1 method)
foo(5)
5
Union{} <: Rectangle
true
struct Square <: Shape
rec::Rectangle
Square(l) = new(Rectangle(l, l))
end
struct Square
rec::Rectangle
function Square()
...
end
end
sq = Square(8)
Square(Rectangle(8.0, 8.0))
sq.rec.height
8.0
sq.rec.width
8.0
struct Point <: Shape
x::Float64
y::Float64
end
p1 = Point(3.4, 2.9)
p2 = Point(5.0, 2.9)
Point(5.0, 2.9)
struct Square2 <: Shape
rec::Rectangle
Square2(l) = new(Rectangle(l, l))
function Square2(p1::Point, p2::Point)
if p1.x == p2.x
l = abs(p1.y - p2.y)
elseif p1.y == p2.y
l = abs(p1.x - p2.x)
end
new(Rectangle(l, l))
end
end
sq = Square2(p1, p2)
Square2(Rectangle(1.6, 1.6))
Literally, this constructor is defined outside of type definition, and it doesn't differ from other methods.
Thus, you can simply add various constructors outside type definition, as other language's constructor overloading do.
struct Circle <: Shape
radius::Float64
end
Circle(r::Int64) = Circle(float(r))
Circle
Circle(5.0)
Circle(5.0)
Circle(5)
Circle(5.0)
We design the most strict constructor as inner constructor. Certain arguments/information are required to construct a type.
Outer constructors act as additional options for convenience. Outer constructors call inner constructors to construct a object.
mutable struct SelfReferential
obj::SelfReferential
end
sr = SelfReferential()
MethodError: no method matching SelfReferential() Closest candidates are: SelfReferential(!Matched::SelfReferential) at In[32]:2 SelfReferential(!Matched::Any) at In[32]:2 Stacktrace: [1] top-level scope at In[33]:1
mutable struct SelfReferential2
obj::SelfReferential2
SelfReferential2() = (x = new(); x.obj = x)
end
sr2 = SelfReferential2()
SelfReferential2(SelfReferential2(#= circular reference @-1 =#))
sr2 === sr2.obj
true
sr2 === sr2.obj.obj
true
We introduce some special types which are associated to functions.
Most of them are directly a part of function constructs.
When a function doesn't return anything, it returns a nothing
.
nothing
is the singleton of type Nothing
which has only one object.
nothing == nothing
true
typeof(nothing)
Nothing
missing
to represent a not existing value, not nothing
¶missing
is also a singleton of type Missing
.
missing
missing
typeof(missing)
Missing
missing
can be calculated, but nothing
cannot.¶missing + 1
missing
nothing + 1
MethodError: no method matching +(::Nothing, ::Int64) Closest candidates are: +(::Any, ::Any, !Matched::Any, !Matched::Any...) at operators.jl:529 +(!Matched::Complex{Bool}, ::Real) at complex.jl:301 +(!Matched::Missing, ::Number) at missing.jl:115 ... Stacktrace: [1] top-level scope at In[43]:1
It follows our mathematical principles.
missing * Inf
missing
Tuple is a built-in type to wrap a bunch of things.
x = (1, 2., '3', "4")
(1, 2.0, '3', "4")
x[1]
1
x[2:3]
(2.0, '3')
objectid(x)
0x77020e3c50be5e2b
objectid(x[2:3])
0x1e56cfc0abb8aab3
a, b, c = x
(1, 2.0, '3', "4")
a
1
b
2.0
c
'3': ASCII/Unicode U+0033 (category Nd: Number, decimal digit)
b, a = a, b
(1, 2.0)
a
2.0
b
1
Tuple is originally designed for argument passing in Julia. For example, function accepts arguments which are wrapped in a tuple. Function also return a tuple to carry a multiple return values.
h(x, y) = x + y
h (generic function with 1 method)
h(1, 2)
3
return
keyword can be omitted.¶function sumproduct(x, y, z)
return (x + y) * z
end
sumproduct (generic function with 1 method)
function sumproduct(x, y, z)
(x + y) * z
end
sumproduct (generic function with 1 method)
If we want to return multiple values, we use tuple.
function shuffle_(x, y, z)
(y, z, x)
end
shuffle_ (generic function with 1 method)
Or if you want to write it in this way.
function shuffle_(x, y, z)
y, z, x
end
shuffle_ (generic function with 1 method)
shuffle_(1, 2., '3')
(2.0, '3', 1)
x = shuffle_(1, 2., '3')
typeof(x)
Tuple{Float64,Char,Int64}
We can destruct many things, such as tuples, arrays, sets and dictionaries.
x = [1, 2, 3]
shuffle_(x...)
(2, 3, 1)
is equivalent to shuffle_(1, 2, 3)
.
Named tuple is a tuple equipped with name. Name is used to get access to specific value.
x = (a=1, b=2.)
(a = 1, b = 2.0)
Similar to tuple, you can index a named tuple with property prefixed by :
.
x[:a]
1
Or you can use dot operator.
x.a
1
Enum
type¶Enum
type is the enumeration in other languages. Enumeration is used to represent several certain states. For example, the traffic light has three states: red light, yellow light and green light. Enum
type is used to represent states which are not increasing.
@enum TrafficLight red=1 yellow=2 green=3
@enum TrafficLight::UInt8 begin
red = 1
yellow = 2
green = 3
end
TrafficLight(1)
red::TrafficLight = 0x01
What instances are in TrafficLight
?
instances(TrafficLight)
(red, yellow, green)
How many instances are in TrafficLight
?
length(instances(TrafficLight))
3
Integer(red)
0x01
Int(red)
1
string(yellow)
"yellow"
One can define methods on TrafficLight
.
f(x::TrafficLight) = Int(x)
f (generic function with 1 method)
f(green)
3
Compare with Python
There is only public to the type property. Everyone can access those properties, once they get the object.
Julia don't hide any infromation from others.
As analogy, julia's subtype is similar to the concept of inheritance in OOP, so Julia can only accept single inheritance.
struct Apple <: Fruit
...
end
class Jet(Airplane,Weapon):
...
The key difference between Julia and other OOP languages.
class DNA:
def transcribe(self):
...
transcribe(dna::DNA) = ...
translate(rna::RNA, rib::Ribosome) = ...
public interface Repeatable{
public void repeat();
}
repeat([5,10], 2)
4-element Array{Int64,1}: 5 10 5 10
repeat("10", 2)
"1010"