Skip to content

Commit

Permalink
Optional piracy on Base
Browse files Browse the repository at this point in the history
  • Loading branch information
perrutquist committed Jun 24, 2020
1 parent 6a27c32 commit 35e064c
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 1 deletion.
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

This module provides singular datatypes named Zero and One. All instances of each datatype are identical, and represent the values zero and one, respectively. This is a light-weight alternative to [StaticNumbers.jl](https://github.com/perrutquist/StaticNumbers.jl) when only these two values are needed.

`Zero` and `One` are subtypes of `Integer`. The most common operations, such as `+`, `-`, `*`, `/`, `<`, `>`, etc. are defined. Operations like `*` propagate the `Zero` type to their return values in a way that is correct for numbers, but not for IEEE 754 `Inf` and `NaN`. For example, `Zero()*x` reduces to `Zero()` at compile-time which has the effect that `Zero()*Inf` becomes `Zero()` rather than `NaN`. A value with this behaviour is sometimes referred to a "strong zero".
`Zero` and `One` are subtypes of `Integer`. The most common operations, such as `+`, `-`, `*`, `/`, `<`, `>`, etc. are defined. Operations like `*` propagate the `Zero` or `One` type to their return values in a way that is correct for numbers, but not for IEEE 754 `Inf` and `NaN`. For example, `Zero()*x` reduces to `Zero()` at compile-time which has the effect that `Zero()*Inf` becomes `Zero()` rather than `NaN`. A value with this behaviour is sometimes referred to a "strong zero".

Since the value of a `Zero` or `One` is known at compile-time, the complier might be able to make optimisations that might not be possible otherwise.

Expand All @@ -15,3 +15,10 @@ Attempting to divide by `Zero()` will throw a `DivideError` rather than returnin
(A compile-time zero in the denominator is usually a sign that a piece of code needs to be re-written to work optimally.)

The `testzero` function can be used to change the type when a variable is equal to zero. For example `foo(testzero(a), b)` will call `foo(a,b)` if `a` is nonzero. But if `a` is zero, then it will call `foo(Zero(),b)` instead. The function `foo` will then be complied specifically for input of the type `Zero` and this might result in speed-ups that outweigh the cost of branching.

The command
```
Zeros.@pirate Base
```
can be used to enable a few more method definitions, such as `+()` (the sum of zero terms)
and `*()` (the product of zero factors).
5 changes: 5 additions & 0 deletions src/Zeros.jl
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ AbstractFloat(::Zero) = 0.0
AbstractFloat(::One) = 1.0

zero(::StaticBool) = Zero()
zero(::Type{<:StaticBool}) = Zero()
one(::StaticBool) = One()
one(::Type{<:StaticBool}) = One()

Complex(x::Real, ::Zero) = x

Expand Down Expand Up @@ -159,6 +161,7 @@ string(z::StaticBool) = Base.print_to_string(z)

Base.Checked.checked_abs(x::StaticBool) = x
Base.Checked.checked_mul(x::StaticBool, y::StaticBool) = x*y
Base.Checked.mul_with_overflow(x::StaticBool, y::StaticBool) = (x*y, false)
Base.Checked.checked_add(x::StaticBool, y::StaticBool) = x+y

if VERSION < v"1.2"
Expand All @@ -167,4 +170,6 @@ if VERSION < v"1.2"
flipsign(::Zero, x::Unsigned) = Zero()
end

include("pirate.jl")

end # module
28 changes: 28 additions & 0 deletions src/pirate.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"""
Zeros.@pirate Base
Extend some functions in Base to return `Zero()` or `One()`, even when called without
any arguments of types "owned" by Zeros.jl. (This is sometimes referred to as "type piracy".)
The function calls s `+()` and `*()` (without arguments) will return `Zero()` and `One()`
respectively. The same will be true for `zero()` and `one()` without arguments.
The functions `zero(Any)` and `one(Any)` will also return `Zero()` and `One()`, respectively,
which in turn will make `sum([])` and `prod([])` return those values.
Finally, `sin(π)` and `tan(π)` will return `Zero()`, fixing a bug in Base.
"""
macro pirate(m::Symbol)
esc(pirate_code(Val(m)))
end

pirate_code(::Val{:Base}) = quote
Base.:+() = Zero()
Base.:*() = One()
Base.zero() = Zero()
Base.one() = One()
Base.zero(::Type{Any}) = Zero()
Base.one(::Type{Any}) = One()
Base.sin(::Irrational{:π}) = Zero()
Base.tan(::Irrational{:π}) = Zero()
end
13 changes: 13 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -174,3 +174,16 @@ include("mycomplex_example.jl")
@testset "mycomplex_example.jl" begin
@test MyImaginary(2)*MyImaginary(3) === MyReal(-6)
end

Zeros.@pirate Base

@testset "piracy" begin
@test +() === Z
@test *() === I
@test zero() === Z
@test one() === I
@test sum([]) == 0
@test prod([]) == 1
@test sin(π) == 0
@test tan(π) == 0
end

0 comments on commit 35e064c

Please sign in to comment.