The Type System is really powerful and can be used to make code much more clean and maintainable.
In this example, I've created an abstract type TempUnit
to represent temperature units, specifically Fahrenheit and Celcius. I've then created two structs for Fahrenheit
and Celcius
which just have one field, deg
to store the degree values for each.
abstract type TempUnit end
struct Fahrenheit <: TempUnit
deg::Float64
end
struct Celcius <: TempUnit
deg::Float64
end
For a conversion function, one may consider having a function which would take the type to convert to and then the value we want to convert, but the following approach is much cleaner.
We start off with a function which says if the target type is the same as the value we're trying to convert, then we just return the value provided as an argument. This is also why we needed the abstract type which both Fahrenheit
and Celcius
are subtypes of. This ensures we don't impact any other types, but this code is uniquely working with the temperature unit types we've built today.
function convert(::Type{T}, deg::T) where {T <: TempUnit}
return deg
end
Next, I've written one function for converting fahrenheit and a second function for converting celcius. Because we've set the type constraint in the function definition itself, we know we are only working within a specific context.
function convert(::Type{Fahrenheit}, deg::Celcius)
Fahrenheit(9/5 * deg.deg + 32.0)
end
function convert(::Type{Celcius}, deg::Fahrenheit)
Celcius(5/9 * (deg.deg - 32.0))
end
And we test them out...
boiling = Fahrenheit(212.0)
freezing = Celcius(0.0)
@show convert(Celcius, freezing)
> convert(Celcius, freezing) = Celcius(0.0)
@show convert(Fahrenheit, boiling)
> convert(Fahrenheit, boiling) = Fahrenheit(212.0)
@show convert(Fahrenheit, freezing)
> convert(Fahrenheit, freezing) = Fahrenheit(32.0)
@show convert(Celcius, boiling)
> convert(Celcius, boiling) = Celcius(100.0)
Adding Kelvin is simple enough too.
struct Kelvin <: TempUnit
n::Float64
end
function convert(::Type{Celcius}, n::Kelvin)
Celcius(n.n - 273.15)
end
function convert(::Type{Kelvin}, deg::Celcius)
Kelvin(deg.deg + 273.15)
end
absolutezero = Kelvin(0.0)
@show convert(Celcius, absolutezero)
> convert(Celcius, absolutezero) = Celcius(-273.15)
A little bit of clean up and adding of additional computational functionality.
abstract type TempUnit end
struct Fahrenheit <: TempUnit
v::Float64
end
struct Celcius <: TempUnit
v::Float64
end
struct Kelvin <: TempUnit
v::Float64
end
# temperature conversion functions
import Base.convert
convert(::Type{T}, deg::T) where {T <: TempUnit} = deg
convert(::Type{Fahrenheit}, deg::Celcius) = Fahrenheit(9/5 * deg.v + 32.0)
convert(::Type{Celcius}, deg::Fahrenheit) = Celcius(5/9 * (deg.v - 32.0))
convert(::Type{Celcius}, n::Kelvin) = Celcius(n.v - 273.15)
convert(::Type{Kelvin}, deg::Celcius) = Kelvin(deg.v + 273.15)
import Base.:+, Base.:-, Base.:*, Base.:/
Base.:+(a::T, b::S) where {T <: Number, S <: TempUnit} = S(a + b.v)
Base.:+(a::S, b::T) where {T <: Number, S <: TempUnit} = S(a.v + b)
Base.:-(a::T, b::S) where {T <: Number, S <: TempUnit} = S(a - b.v)
Base.:-(a::S, b::T) where {T <: Number, S <: TempUnit} = S(a.v - b)
Base.:*(a::T, b::S) where {T <: Number, S <: TempUnit} = S(a * b.v)
Base.:*(a::S, b::T) where {T <: Number, S <: TempUnit} = S(a.v * b)
Base.:/(a::T, b::S) where {T <: Number, S <: TempUnit} = S(a / b.v)
Base.:/(a::S, b::T) where {T <: Number, S <: TempUnit} = S(a.v / b)
@testset "temperature conversions" begin
boiling = Fahrenheit(212.0)
freezing = Celcius(0.0)
absolutezero = Kelvin(0.0)
@test convert(Celcius, freezing) == Celcius(0.0)
@test convert(Celcius, boiling) == Celcius(100.0)
@test convert(Fahrenheit, boiling) == Fahrenheit(212.0)
@test convert(Fahrenheit, freezing) == Fahrenheit(32.0)
@test convert(Celcius, absolutezero) == Celcius(-273.15)
end
@testset "temperature struct computations" begin
@test Celcius(0.0) + 4.0 == Celcius(4.0)
@test Celcius(2.0) * 2 == Celcius(4.0)
end