Unlike object-oriented languaes (eg. C++ and Python), where classes own methods, in Julia there are no classes in the onject-oriented sense:
This might be a bit strange for python users: classes look "inside-out" with method definitions accompanying data types.
For encapsulation, use Modules!
Let's define an ordered pair of reals
struct OrderedPair <: Number
x::Real
y::Real
OrderedPair(x::Real, y::Real) = x > y ? error("out of order") : new(x,y)
end
The encapsulated function: OrderedPair(x,y) = x > y ? error("out of order") : new(x,y)
defines a constructor that ensure that the ordered pair stays ordered. In Python, this would be handled by __init__(self, x, y)
The is a one-line function definition here is equivalent to:
function OrderedPair(x,y)
if x > y
error("out of order")
end
new(x,y)
end
Let's define some math: addition and subtraction:
__add__
and __sub__
Base.+
and Base.-
We also need conversion and promotion rules for our custom data type. This way our OrderedPair
is a first-class citizen ... just like Float64
:)
import Base: +, -, convert, promote_rule
function +(a::OrderedPair, b::OrderedPair)
x_new = a.x + b.x
y_new = a.y + b.y
OrderedPair(x_new, y_new)
end
# One-liners can seem a bit magical
-(a::OrderedPair, b::OrderedPair) = OrderedPair(a.x - b.x, a.y - b.y)
# Getting these right might require some experimenting. Note how OrderedPair extends Number.
convert(::Type{OrderedPair}, x::Real) = OrderedPair(x, x)
promote_rule(::Type{OrderedPair}, ::Type{<:Real}) = OrderedPair
promote_rule (generic function with 125 methods)
We can now use our order pairs in addition and subtraction:
p1 = OrderedPair(1, 2)
p2 = OrderedPair(10, 20)
OrderedPair(10, 20)
p1 + p2
OrderedPair(11, 22)
Our constructor ensures that the ordered pair type remains consistent (ie. ordered)
p1 - p2
out of order Stacktrace: [1] error(s::String) @ Base ./error.jl:33 [2] OrderedPair(x::Int64, y::Int64) @ Main ./In[1]:5 [3] -(a::OrderedPair, b::OrderedPair) @ Main ./In[2]:10 [4] top-level scope @ In[5]:1 [5] eval @ ./boot.jl:360 [inlined] [6] include_string(mapexpr::typeof(REPL.softscope), mod::Module, code::String, filename::String) @ Base ./loading.jl:1094
p2 - p1
OrderedPair(9, 18)
What are conversion and promotion used for? Let's say we want to convert the number 20
to an ordered pair -- that's where conversion is used:
convert(OrderedPair, 20.)
OrderedPair(20.0, 20.0)
Promotion defines what type the result of two different inputs' data types should have. Together with conversion we can add a single number to both parts of the pair:
p1 + 20.
OrderedPair(21.0, 22.0)
UnionAll
Data Types¶We can use curly braces and the where
keyword to define generic types. You can think of these as basically C++ templates. The <:
symbol restricts the possible inputs to the generic type T
struct OrderedPair2{T} <: Number where T <: Number
x::T
y::T
OrderedPair2(x::T, y::T) where T = x > y ? error("out of order") : new{T}(x,y)
end
This now generates different specializations based on the type of x
, and y
p = OrderedPair2(1., 2.)
OrderedPair2{Float64}(1.0, 2.0)
p2 = OrderedPair2(1, 2)
OrderedPair2{Int64}(1, 2)