diff --git a/Project.toml b/Project.toml index 1e46baa2..772b35ed 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "Unitful" uuid = "1986cc42-f94f-5a68-af5c-568840ba703d" -version = "1.18.0" +version = "1.9.0" [deps] ConstructionBase = "187b0558-2788-49d3-abe0-74a17ed4e7c9" diff --git a/src/Unitful.jl b/src/Unitful.jl index e8919035..60290498 100644 --- a/src/Unitful.jl +++ b/src/Unitful.jl @@ -27,6 +27,7 @@ import LinearAlgebra: istril, istriu, norm import Random export logunit, unit, absoluteunit, dimension, uconvert, ustrip, upreferred +export WithUnits, WithDims export @dimension, @derived_dimension, @refunit, @unit, @affineunit, @u_str export Quantity, DimensionlessQuantity, NoUnits, NoDims diff --git a/src/utils.jl b/src/utils.jl index 5d717015..52e5ad38 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -243,6 +243,60 @@ struct DimensionError <: Exception y end +""" + WithDims(q::Quantity) + WithDims(u::Units) +Returns a subtype of [`Unitful.Quantity`](@ref) with the dimensions constrained to the +dimension of `q` or `u`. +Useful to build unitful interfaces that don't constrain the numeric type or the unit, just the dimension of a quantity. + +Examples: + +```jldoctest +julia> using Unitful, Unitful.DefaultSymbols; import Unitful.hr +julia> circumference_of_square(side::WithDims(m)) = 4*side; +julia> circumference_of_square((1//2)m) # works +2//1 m +julia> circumference_of_square((1//2)km) # also works +2//1 km +# You can also constrain the return type. The numeric type is usually inferred automatically. +julia> kinetic_energy(mass::WithDims(kg), velocity::WithDims(m/s))::WithDims(J) = mass*velocity^2; +julia> kinetic_energy(1000kg, 100km/hr) +10000000 kg km^2 hr^-2 +``` + +See also [`Unitful.WithUnits`](@ref). +""" +WithDims(q::Quantity) = Quantity{T, dimension(q), U} where {T<:Real, U<:Unitlike} +WithDims(u::Units) = Quantity{T, dimension(u), U} where {T<:Real, U<:Unitlike} + +""" + WithUnits(q::Quantity) + WithUnits(u::Units) +Returns a subtype of [`Unitful.Quantity`](@ref) with the dimensions and units constrained to the +dimension and units of `q` or `u`. +Useful to build unitful interfaces that don't constrain the unit, but not the numeric type of a quantity. + +Examples: + +```jldoctest +julia> using Unitful, Unitful.DefaultSymbols; import Unitful.hr +julia> circumference_of_square(side::WithUnits(m)) = 4*side; +julia> circumference_of_square((1//2)m) # works +2//1 m +julia> # circumference_of_square((1//2)km) # doesn't work, constrained to exactly meters + +# You can also constrain the return type. The numeric type is usually inferred automatically. +julia> kinetic_energy(mass::WithUnits(kg), velocity::WithUnits(m/s))::WithUnits(J) = mass*velocity^2 |> x->uconvert(J, x) +julia> kinetic_energy(1000kg, uconvert(m/s, 100km/hr)) +62500000//81 J +``` + +See also [`Unitful.WithDims`](@ref). +""" +WithUnits(q::Quantity) = Quantity{T, dimension(q), unit(q)} where {T<:Real} +WithUnits(u::Units) = Quantity{T, dimension(u), typeof(u)} where {T<:Real} + Base.showerror(io::IO, e::DimensionError) = print(io, "DimensionError: $(e.x) and $(e.y) are not dimensionally compatible."); diff --git a/test/runtests.jl b/test/runtests.jl index 525462a3..ae6d8fd5 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -2080,6 +2080,24 @@ Base.OrderStyle(::Type{Num}) = Base.Unordered() @test uconvert(u"°C", Num(373.15)u"K") == Num(100)u"°C" end +@testset "WithDims, WithUnits" begin + # built-in types + @test 1m isa WithDims(m) + @test 1.0m isa WithDims(m) + @test 1//1m isa WithDims(m) + @test 1.0m isa WithUnits(m) + # user-defined types + @test Num(1.0)m isa WithDims(m) + @test Num(1.0)km isa WithDims(m) + @test !(Num(1.0)s isa WithDims(m)) + @test Num(1.0)m isa WithUnits(m) + @test !(Num(1.0)km isa WithUnits(m)) + @test !(Num(1.0)s isa WithUnits(m)) + # composite units + @test 1kg*(1.0m/s)^2 isa WithDims(J) + @test 1kg*(1.0m/s)^2 isa WithUnits(kg*m^2/s^2) +end + @testset "Traits" begin @testset "> ArithmeticStyle" begin @test Base.ArithmeticStyle(1m) === Base.ArithmeticWraps()