InitialValues.jl
InitialValues
InitialValues.INIT
InitialValues.InitialValue
InitialValues.asmonoid
InitialValues.hasinitialvalue
InitialValues.isknown
InitialValues.@def
InitialValues.@def_monoid
InitialValues.@disambiguate
InitialValues
— ModuleInitialValues.jl: Canonical default initial values and identity elements for Julia
InitialValues.jl provides a generic singleton initial value InitialValue(f)
that can be used as a₀
in f(a₀, x)
. For a binary operator op
, it means that InitialValue(op)
acts like the identity for any type of x
:
julia> using InitialValues
julia> InitialValue(+) + 1
1
julia> 1.0 + InitialValue(+)
1.0
julia> foldl(+, 1:3, init=InitialValue(+))
6
Following methods are defined for the binary operators in Base
:
julia> InitialValue(*) * 1
1
julia> InitialValue(&) & 1
1
julia> InitialValue(|) | 1
1
julia> min(InitialValue(min), 1)
1
julia> max(InitialValue(max), 1)
1
julia> Base.add_sum(InitialValue(Base.add_sum), 1)
1
julia> Base.mul_prod(InitialValue(Base.mul_prod), 1)
1
InitialValue
is not called Identity
because it is useful to define it for functions that are not binary operator (symmetric in signature). For example, push!!
in BangBang.jl defines
julia> using BangBang
julia> push!!(InitialValue(push!!), 1)
1-element Array{Int64,1}:
1
This provides a powerful pattern when combined with foldl
:
julia> foldl(push!!, (1, missing, 2.0), init=InitialValue(push!!))
3-element Array{Union{Missing, Float64},1}:
1.0
missing
2.0
Transducers.jl extensively uses InitialValue
.
As binary operators like *
in Base
are heavily overloaded, creating generic definitions such as above could have introduced method ambiguities. To protect against such situation, InitialValues.jl is tested using Aqua.jl.
InitialValues.InitialValue
— TypeInitialValue(OP)
Create a generic (left) identity for a binary operator op
. For general binary function, it provides an identity-like generic default value (see BangBang.push!!
).
Examples
julia> using InitialValues
julia> InitialValue(*) isa InitialValues.InitialValue
true
julia> InitialValue(*) * 1
1
julia> InitialValue(*) * missing
missing
julia> InitialValue(*) * "right"
"right"
julia> InitialValue(*) * :actual_anything_works
:actual_anything_works
julia> foldl(+, 1:3, init=InitialValue(+))
6
julia> float(InitialValue(*))
1.0
julia> Integer(InitialValue(+))
0
InitialValues.INIT
— ConstantINIT :: InitialValue
A generic initial value. Unlike InitialValue
, this does not detect an error when INIT
is used with unintended operations.
Examples
julia> using InitialValues
julia> InitialValue(+) * 0 # `InitialValue(op)` must be used with `op`
ERROR: MethodError: no method matching *(::InitialValues.InitialValueOf{typeof(+)}, ::Int64)
[...]
julia> INIT * 123
123
julia> foldl(+, 1:3, init=INIT)
6
InitialValues.@def
— MacroInitialValues.@def op [y = :x]
Define a generic (left) identity for a binary operator op
. Specify the second argument for a binary function in general.
InitialValues.@def op
is expanded to
op(::GenericInitialValue{typeof(op)}, x) = x
InitialValues.hasinitialvalue(::Type{typeof(op)}) = true
For operations like push!
, it is useful to define the returned value to be different from x
. This can be done by using the second argument to the maco; i.e., InitialValues.@def push! [x]
is expanded to
push!(::GenericInitialValue{typeof(push!)}, x) = [x]
InitialValues.hasinitialvalue(::Type{typeof(push!)}) = true
Note that the second argument to op
is always x
.
InitialValues.@def_monoid
— MacroInitialValues.@def_monoid op
Define a generic identity for a binary operator op
. InitialValues.@def_monoid op
is expanded to
op(::GenericInitialValue{typeof(op)}, x::GenericInitialValue{typeof(op)}) = x
op(x, ::GenericInitialValue{typeof(op)}) = x
op(::GenericInitialValue{typeof(op)}, x) = x
InitialValues.hasinitialvalue(::Type{typeof(op)}) = true
InitialValues.@disambiguate
— MacroInitialValues.@disambiguate op OtherType
Disambiguate the method introduced by @def_monoid
.
It is expanded to
op(::GenericInitialValue{typeof(op)}, x::OtherType) = x
op(x::OtherType, ::GenericInitialValue{typeof(op)}) = x
InitialValues.hasinitialvalue
— FunctionInitialValues.hasinitialvalue(op) :: Bool
Examples
julia> using InitialValues
julia> all(InitialValues.hasinitialvalue, [
*,
+,
&,
|,
min,
max,
Base.add_sum,
Base.mul_prod,
])
true
julia> InitialValues.hasinitialvalue((x, y) -> x + y)
false
InitialValues.isknown
— FunctionInitialValues.isknown(::InitialValue) :: Bool
Examples
julia> using InitialValues
julia> InitialValues.isknown(InitialValue(+))
true
julia> InitialValues.isknown(InitialValue((x, y) -> x + y))
false
InitialValues.asmonoid
— Functionasmonoid(op) -> op′
"Add" (adjoin) an identity element to the semigroup op
if necessary and return the monoid op′
.
Examples
julia> using InitialValues
julia> asmonoid(*) === * # do nothing if `InitialValue` is already defined
true
julia> append!′ = asmonoid(append!);
julia> xs = [];
julia> append!′(InitialValue(append!′), xs) === xs
true
julia> foldl(append!′, [xs, [1], [2, 3]], init=InitialValue(append!′))
3-element Array{Any,1}:
1
2
3
julia> ans === xs # `xs` is modified
true