InitialValues.jl

InitialValuesModule

InitialValues.jl: Canonical default initial values and identity elements for Julia

Stable Dev GitHub Actions Codecov Coveralls Aqua QA

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.

source
InitialValues.InitialValueType
InitialValue(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
source
InitialValues.INITConstant
INIT :: 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
source
InitialValues.@defMacro
InitialValues.@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.

source
InitialValues.@def_monoidMacro
InitialValues.@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
source
InitialValues.@disambiguateMacro
InitialValues.@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
source
InitialValues.hasinitialvalueFunction
InitialValues.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
source
InitialValues.isknownFunction
InitialValues.isknown(::InitialValue) :: Bool

Examples

julia> using InitialValues

julia> InitialValues.isknown(InitialValue(+))
true

julia> InitialValues.isknown(InitialValue((x, y) -> x + y))
false
source
InitialValues.asmonoidFunction
asmonoid(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
source