Skip to content

Commit d3d209c

Browse files
author
Stephan Sahm
committed
fixed promote_type and added tests for it
also changed double quotes `` to single `
1 parent 9af8889 commit d3d209c

12 files changed

Lines changed: 291 additions & 51 deletions

src/Const.jl

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,14 @@ Iterators.flatten(c::Const) = c
2121

2222
Base.eltype(::Type{<:Const}) = Any
2323

24-
# Const is covariate
2524
Base.convert(::Type{Const{T}}, x::Const) where {T} = Const(Base.convert(T, x.value))
25+
# Const is covariate
26+
# promote_rule only works on concrete types, more general checks Type{<:Const} may overwrite
27+
# unintentionally more specific promote_rule types
2628
promote_rule(::Type{Const{T}}, ::Type{Const{S}}) where {T, S<:T} = Const{T}
29+
promote_rule(::Type{Const{T}}, ::Type{Const}) where T = Const
30+
promote_rule(::Type{Const}, ::Type{Const}) = Const
2731

28-
# we need this for safety, if someone overwrites typejoin for Unions with Const
29-
Base.typejoin(::Type{Const{T}}, ::Type{Const{T}}) where T = Const{T}
30-
Base.typejoin(::Type{<:Const}, ::Type{<:Const}) = Const
32+
# we need this for safety, if someone overwrites promote_typejoin for Unions with Const
33+
Base.promote_typejoin(::Type{Const{T}}, ::Type{Const{T}}) where T = Const{T}
34+
Base.promote_typejoin(::Type{<:Const}, ::Type{<:Const}) = Const

src/ContextManager.jl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ function which expects one argument, which itself is a function. Think of it lik
99
function contextmanagerready(cont)
1010
# ... do something before
1111
value = ... # create some value to work on later
12-
result = cont(value) # pass the value to the continuation function (think like ``yield``)
12+
result = cont(value) # pass the value to the continuation function (think like `yield`)
1313
# ... do something before exiting, e.g. cleaning up
1414
result # IMPORTANT: always return the result of the `cont` function
1515
end
@@ -31,6 +31,8 @@ end
3131
# ContextManager is just a wrapper
3232
# pass through function call syntax
3333
(c::ContextManager)(cont) = c.f(cont)
34+
Base.run(cont, c::ContextManager) = c(cont)
35+
Base.run(c::ContextManager) = c(identity)
3436

3537

3638
function Base.eltype(::Type{<:ContextManager{F}}) where F

src/DataTypesBasic.jl

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ Approach 1.
99
===========
1010
1111
Advantages: Providing definitions for the concrete is actually enough for typeinference
12-
Disadvantages: The typeinference like for ``Dict(:a => Some(1), :b => None())`` would infer
13-
``Dict{Symbol, Any}`` instead of `Dict{Symbol, Union{Some, None}}`.
12+
Disadvantages: The typeinference like for `Dict(:a => Some(1), :b => None())` would infer
13+
`Dict{Symbol, Any}` instead of `Dict{Symbol, Union{Some, None}}`.
1414
1515
The internal details for this is that in many cases `Base.promote_typejoin(type1, type2)` is used to come up
1616
with a common type.
@@ -20,7 +20,7 @@ Disadvantages: The typeinference like for ``Dict(:a => Some(1), :b => None())``
2020
Approach 2.
2121
===========
2222
23-
Advantages: ``Dict(:a => Some(1), :b => None())`` would indeed infer ``Dict{Symbol, Option}``
23+
Advantages: `Dict(:a => Some(1), :b => None())` would indeed infer `Dict{Symbol, Option}`
2424
Disadvantages: you need to be careful to always implement functionalities first on separate functions unique to the
2525
sealed type, and then point generic functions to the specific one via `genericfunction(o::Option) = optionfunction(o)`
2626
@@ -32,19 +32,25 @@ module DataTypesBasic
3232
export Const, Identity,
3333
Option, issomething, iftrue, iffalse, getOption, # isnothing, Nothing, Some comes from Base
3434
Either, either, isleft, isright, getleft, getright, getleftOption, getrightOption, flip_left_right,
35+
OptionEither,
3536
Try, Thrown, @Try, @TryCatch, issuccess, isexception, MultipleExceptions,
3637
ContextManager, @ContextManager
3738

39+
using Compat
3840
using IsDef
3941

42+
# type definitions
4043
include("Nothing.jl")
4144
include("Const.jl")
4245
include("Identity.jl")
4346
include("Option.jl")
4447
include("Try.jl")
4548
include("Either.jl")
4649
include("ContextManager.jl")
47-
include("convert.jl")
50+
include("OptionEither.jl")
4851

52+
# interoperability between types
53+
include("promote_type.jl")
54+
include("convert.jl")
4955

5056
end # module

src/Either.jl

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,32 +4,6 @@ Either{L, R}(x::R) where {L, R} = Identity(x)
44
Either{L}(x::R) where {L, R} = Identity(x)
55
Either{L}(x::L) where {L} = Const(x)
66

7-
8-
# TypeJoin should work with Identity and Either
9-
10-
# typejoin Const & Identity
11-
Base.typejoin(::Type{Const{L}}, ::Type{Identity{R}}) where {L, R} = Either{L, R}
12-
Base.typejoin(::Type{Identity{R}}, ::Type{Const{L}}) where {L, R} = Either{L, R}
13-
# typejoin Const & Either
14-
Base.typejoin(::Type{Const{L}}, ::Type{<:Either{L, R}}) where {L, R} = Either{L, R}
15-
Base.typejoin(::Type{<:Either{L, R}}, ::Type{Const{L}}) where {L, R} = Either{L, R}
16-
Base.typejoin(::Type{<:Const}, ::Type{<:Either{<:Any, R}}) where {R} = Either{<:Any, R}
17-
Base.typejoin(::Type{<:Either{<:Any, R}}, ::Type{<:Const}) where {R} = Either{<:Any, R}
18-
# typejoin Identity & Either
19-
Base.typejoin(::Type{Identity{R}}, ::Type{<:Either{L, R}}) where {L, R} = Either{L, R}
20-
Base.typejoin(::Type{<:Either{L, R}}, ::Type{Identity{R}}) where {L, R} = Either{L, R}
21-
Base.typejoin(::Type{<:Identity}, ::Type{<:Either{L}}) where {L} = Either{L}
22-
Base.typejoin(::Type{<:Either{L}}, ::Type{<:Identity}) where {L} = Either{L}
23-
# typejoin Either & Either
24-
Base.typejoin(::Type{<:Either{L, R}}, ::Type{<:Either{L, R}}) where {L, R} = Either{L, R}
25-
Base.typejoin(::Type{<:Either{L}}, ::Type{<:Either{L}}) where {L} = Either{L}
26-
Base.typejoin(::Type{<:Either{<:Any, R}}, ::Type{<:Either{<:Any, R}}) where {R} = Either{<:Any, R}
27-
Base.typejoin(::Type{<:Either}, ::Type{<:Either}) = Either
28-
29-
# Conversion should also work with Either
30-
Base.convert(::Type{Either{L, R}}, x::Identity) where {L, R} = Identity(Base.convert(R, x.value))
31-
Base.convert(::Type{Either{L, R}}, x::Const) where {L, R} = Const(Base.convert(L, x.value))
32-
337
Base.eltype(::Type{Either{L, R}}) where {L, R} = R
348
Base.eltype(::Type{Either{<:Any, R}}) where {R} = R
359
Base.eltype(::Type{Either}) = Any

src/Identity.jl

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,14 @@ Base.foreach(f, a::Identity) = begin f(a); nothing; end
2222
Base.map(f, a::Identity) = Identity(f(a.value))
2323
Base.Iterators.flatten(a::Identity) = convert(Identity, a.value)
2424

25-
# Identity is covariate
2625
Base.convert(::Type{Identity{T}}, x::Identity) where {T} = Identity(Base.convert(T, x.value))
26+
# Identity is covariate
27+
# promote_rule only works on concrete types, more general checks Type{<:Const} may overwrite
28+
# unintentionally more specific promote_rule types
2729
promote_rule(::Type{Identity{T}}, ::Type{Identity{S}}) where {T, S<:T} = Identity{T}
30+
promote_rule(::Type{Identity{T}}, ::Type{Identity}) where T = Identity
31+
promote_rule(::Type{Identity}, ::Type{Identity}) = Identity
2832

2933
# we need this for safety, if someone overwrites typejoin for Unions with Identiy
30-
Base.typejoin(::Type{Identity{T}}, ::Type{Identity{T}}) where T = Identity{T}
31-
Base.typejoin(::Type{<:Identity}, ::Type{<:Identity}) = Identity
34+
Base.promote_typejoin(::Type{Identity{T}}, ::Type{Identity{T}}) where T = Identity{T}
35+
Base.promote_typejoin(::Type{<:Identity}, ::Type{<:Identity}) = Identity

src/Option.jl

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,6 @@ For further discussion see https://discourse.julialang.org/t/is-this-a-bug-some-
2424
Base.:(==)(a::Some, b::Some) = a.value == b.value
2525
Base.hash(a::Some) = hash(a.value)
2626

27-
# we need to overwrite convert, because in the case that no conversion is possible, we currently get the super uninformative error
28-
# ERROR: could not compute non-nothing type
29-
# Stacktrace:
30-
# [1] nonnothingtype_checked(::Type) at ./some.jl:29
31-
# [2] convert(::Type{Union{Nothing, Some{T}} where T}, ::Int64) at ./some.jl:34
32-
# [3] top-level scope at none:0
33-
# importantly, we should only add clauses for Type{Option} and not Type{<:Option} to not interfere with existing code
34-
Base.convert(::Type{Option}, x::Option) = x
35-
# Option{T} seems to be already covered by normal Union, Some, Nothing conversions, no need to provide them
36-
3727
function iftrue(func::Function, b::Bool)
3828
b ? Identity(func()) : nothing
3929
end

src/OptionEither.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
const OptionEither{L, R} = Union{Nothing, Const{L}, Identity{R}}
2+
3+
# TODO anything more todo here?

src/convert.jl

Lines changed: 84 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,106 @@
1-
# conversions among Identity, Option, Try, Either, Vector
2-
# =======================================================
1+
# conversions among concrete types
2+
# ================================
33

44
# Vector
5+
# ------
6+
57
Base.convert(::Type{<:Vector}, x::Identity) = [x.value]
68
Base.convert(::Type{<:Vector}, x::Nothing) = []
79
Base.convert(::Type{<:Vector}, x::Const) = []
10+
# ContextManager is executed if someone asks for a Vector from ContextManager
11+
Base.convert(::Type{<:Vector}, x::ContextManager) = [x(identity)]
812

913
# Const
14+
# -----
15+
1016
Base.convert(::Type{<:Const}, x::Nothing) = Const(nothing)
1117
function Base.convert(::Type{Const}, x::Vector)
1218
@assert !isempty(x) "can only convert empty Vector to Nothing, got $(x)"
1319
Const([])
1420
end
1521

1622
# Nothing
23+
# -------
24+
1725
function Base.convert(::Type{Nothing}, x::Vector)
1826
@assert !isempty(x) "can only convert empty Vector to Nothing, got $(x)"
1927
nothing
2028
end
2129

2230
# Identity
31+
# --------
32+
2333
# Nothing and Const are just passed through when asked to convert to identity
34+
# this is intended special behaviour in order for Identity + Nothing/Const to work together as Option/Either
2435
Base.convert(::Type{Identity}, x::Union{Nothing, Const}) = x
36+
# ContextManager is executed
37+
Base.convert(::Type{<:Identity}, x::ContextManager) = Identity(x(identity))
38+
39+
40+
# ContextManager
41+
# --------------
42+
43+
Base.convert(::Type{<:ContextManager}, x::Identity) = @ContextManager cont -> cont(x.value)
44+
45+
# Note that we do not support convert from Nothing/Const to ContextManager.
46+
# ContextManager is similar to Identity a Container which always contains a single element,
47+
# however, unlike Identity, we cannot simply pass-through Nothing/Const and preserve the empty container by this trick.
48+
# To map over ContextManager, you NEED to create a new ContextManager, if you don't want to run the contextmanager
49+
# immediately. Hence, also flatmap needs to create another ContextManager, i.e. single element container.
50+
51+
# Vector is neither supported, as it rather corresponds to Continuable which can have several calls to `cont`
52+
# while ContextManager should only have one call to `cont`.
53+
54+
55+
# conversions among Union Types
56+
# =============================
57+
58+
# Option
59+
# ------
60+
61+
# we need to overwrite convert, because in the case that no conversion is possible, we currently get the super uninformative error
62+
# ERROR: could not compute non-nothing type
63+
# Stacktrace:
64+
# [1] nonnothingtype_checked(::Type) at ./some.jl:29
65+
# [2] convert(::Type{Union{Nothing, Some{T}} where T}, ::Int64) at ./some.jl:34
66+
# [3] top-level scope at none:0
67+
# importantly, we should only add clauses for Type{Option} and not Type{<:Option} to not interfere with existing code
68+
Base.convert(::Type{Option}, x::Option) = x
69+
70+
Base.convert(::Type{Option{T}}, x::Identity) where T = Identity(Base.convert(T, x))
71+
Base.convert(::Type{Option{T}}, x::Identity{T}) where T = x # nothing to convert
72+
Base.convert(::Type{Option{T}}, x::Nothing) where T = x # nothing to convert
73+
Base.convert(::Type{Option}, x::Nothing) = x # nothing to convert
74+
Base.convert(::Type{Option}, x::Identity) = x # nothing to convert
75+
76+
77+
# Either
78+
# ------
79+
80+
Base.convert(::Type{Either{L, R}}, x::Identity) where {L, R} = Identity(Base.convert(R, x.value))
81+
Base.convert(::Type{Either{L, R}}, x::Identity{R}) where {L, R} = x # nothing to convert
82+
Base.convert(::Type{Either{L, R}}, x::Const) where {L, R} = Const(Base.convert(L, x.value))
83+
Base.convert(::Type{Either{L, R}}, x::Const{L}) where {L, R} = x # nothing to convert
84+
85+
Base.convert(::Type{Either{<:Any, R}}, x::Identity) where {R} = Identity(Base.convert(R, x.value))
86+
Base.convert(::Type{Either{<:Any, R}}, x::Identity{R}) where {R} = x # nothing to convert
87+
Base.convert(::Type{Either{L, <:Any}}, x::Const) where {L} = Const(Base.convert(L, x.value))
88+
Base.convert(::Type{Either{L, <:Any}}, x::Const{L}) where {L} = x # nothing to convert
89+
90+
91+
# OptionEither
92+
# ------------
93+
94+
Base.convert(::Type{OptionEither{L, R}}, x::Identity) where {L, R} = Identity(Base.convert(R, x.value))
95+
Base.convert(::Type{OptionEither{L, R}}, x::Identity{R}) where {L, R} = x # nothing to convert
96+
Base.convert(::Type{OptionEither{L, R}}, x::Const) where {L, R} = Const(Base.convert(L, x.value))
97+
Base.convert(::Type{OptionEither{L, R}}, x::Const{L}) where {L, R} = x # nothing to convert
98+
99+
Base.convert(::Type{OptionEither{<:Any, R}}, x::Identity) where {R} = Identity(Base.convert(R, x.value))
100+
Base.convert(::Type{OptionEither{<:Any, R}}, x::Identity{R}) where {R} = x # nothing to convert
101+
Base.convert(::Type{OptionEither{L, <:Any}}, x::Const) where {L} = Const(Base.convert(L, x.value))
102+
Base.convert(::Type{OptionEither{L, <:Any}}, x::Const{L}) where {L} = x # nothing to convert
103+
104+
Base.convert(::Type{OptionEither{L, R}}, x::Nothing) where {L, R} = x # nothing to convert
105+
Base.convert(::Type{OptionEither}, x::Nothing) = x # nothing to convert
106+
Base.convert(::Type{OptionEither}, x::Identity) = x # nothing to convert

src/promote_type.jl

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# promote_type/promote_typejoin already works for Identity and Nothing (aka Options)
2+
# ==========================================================================
3+
4+
# nothing todo, as `Nothing` is already super well supported for promote_type and promote_typejoin
5+
6+
7+
# promote_type/promote_typejoin should work with Identity and Const (aka Either)
8+
# ==========================================================================
9+
10+
# promote_type only has to deal with concrete types, including Unions in our case
11+
12+
# promote_type Const & Identity
13+
# -----------------------------
14+
15+
Base.promote_rule(::Type{C}, ::Type{I}) where {C<:Const, I<:Identity} = Union{C, I}
16+
17+
18+
# promote_type Const & Either
19+
# -----------------------------
20+
21+
Base.promote_rule(::Type{Const}, ::Type{Either{L, R}}) where {L, R} = Either{<:Any, R}
22+
Base.promote_rule(::Type{Const{L}}, ::Type{Either{L, R}}) where {L, R} = Either{L, R}
23+
Base.promote_rule(::Type{Const{L1}}, ::Type{Either{L2, R}}) where {L1, R, L2 <: L1} = Either{L1, R}
24+
Base.promote_rule(::Type{Const{L2}}, ::Type{Either{L1, R}}) where {L1, R, L2 <: L1} = Either{L1, R}
25+
26+
Base.promote_rule(::Type{Const}, ::Type{Either{L, <:Any}}) where {L} = Either
27+
Base.promote_rule(::Type{Const{L}}, ::Type{Either{L, <:Any}}) where {L} = Either{L, <:Any}
28+
Base.promote_rule(::Type{Const{L1}}, ::Type{Either{L2, <:Any}}) where {L1, L2 <: L1} = Either{L1, <:Any}
29+
Base.promote_rule(::Type{Const{L2}}, ::Type{Either{L1, <:Any}}) where {L1, L2 <: L1} = Either{L1, <:Any}
30+
31+
Base.promote_rule(::Type{Const}, ::Type{Either}) = Either
32+
33+
34+
# promote_type Identity & Either
35+
# -----------------------------
36+
37+
Base.promote_rule(::Type{Identity}, ::Type{Either{L, R}}) where {L, R} = Either{L, <:Any}
38+
Base.promote_rule(::Type{Identity{R}}, ::Type{Either{L, R}}) where {L, R} = Either{L, R}
39+
Base.promote_rule(::Type{Identity{R1}}, ::Type{Either{L, R2}}) where {L, R1, R2 <: R1} = Either{L, R1}
40+
Base.promote_rule(::Type{Identity{R2}}, ::Type{Either{L, R1}}) where {L, R1, R2 <: R1} = Either{L, R1}
41+
42+
Base.promote_rule(::Type{Identity}, ::Type{Either{<:Any, R}}) where {R} = Either
43+
Base.promote_rule(::Type{Identity{R}}, ::Type{Either{<:Any, R}}) where {R} = Either{<:Any, R}
44+
Base.promote_rule(::Type{Identity{R1}}, ::Type{Either{<:Any, R2}}) where {R1, R2 <: R1} = Either{<:Any, R1}
45+
Base.promote_rule(::Type{Identity{R2}}, ::Type{Either{<:Any, R1}}) where {R1, R2 <: R1} = Either{<:Any, R1}
46+
47+
Base.promote_rule(::Type{Identity}, ::Type{Either}) = Either
48+
49+
50+
# promote_type Either & Either
51+
# -----------------------------
52+
53+
# we need to be cautious here, as we cannot dispatch on Type{<:Either{<:Any, R}} or similar, because R might not be defined
54+
Base.promote_rule(::Type{Either{L, R}}, ::Type{Either{L, R}}) where {L, R} = Either{L, R}
55+
Base.promote_rule(::Type{Either{L1, R}}, ::Type{Either{L2, R}}) where {L1, R, L2 <: L1} = Either{L1, R}
56+
Base.promote_rule(::Type{Either{L, R1}}, ::Type{Either{L, R2}}) where {L, R1, R2 <: R1} = Either{L, R1}
57+
Base.promote_rule(::Type{Either{L1, R1}}, ::Type{Either{L2, R2}}) where {L1, R1, L2 <: L1, R2 <: R1} = Either{L1, R1}
58+
Base.promote_rule(::Type{Either{L2, R1}}, ::Type{Either{L1, R2}}) where {L1, R1, L2 <: L1, R2 <: R1} = Either{L1, R1}
59+
60+
Base.promote_rule(::Type{Either{L, <:Any}}, ::Type{Either{L, <:Any}}) where {L} = Either{L, <:Any}
61+
Base.promote_rule(::Type{Either{L1, <:Any}}, ::Type{Either{L2, <:Any}}) where {L1, L2 <: L1} = Either{L1, <:Any}
62+
63+
Base.promote_rule(::Type{Either{<:Any, R}}, ::Type{Either{<:Any, R}}) where {L, R} = Either{<:Any, R}
64+
Base.promote_rule(::Type{Either{<:Any, R1}}, ::Type{Either{<:Any, R2}}) where {L, R1, R2 <: R1} = Either{<:Any, R1}
65+
66+
# NOTE we cannot use `Base.promote_rule(::Type{<:Either}, ::Type{<:Either}) = Either` as apparently this interferes
67+
# with more concrete implementations of promote_rule
68+
# promote_type seem to assume, that you really only define promote_rule for concrete types
69+
Base.promote_rule(::Type{Either}, ::Type{Either}) = Either
70+
71+
72+
# promote_typejoin
73+
# -----------------------------
74+
75+
# We also support `promote_typejoin`, as the semantics between promote_typejoin and promote_type overlap
76+
# in our case of Unions (similar to how they are already defined for Nothing and Missing in Base).
77+
# In addition note that `promote_typejoin` has to be defined for both flipped and non-flipped versions.
78+
# We solve this by referring to the symmetric `promote_type` instead of the asymmtric `promote_rule`.
79+
Base.promote_typejoin(::Type{E1}, ::Type{E2}) where {E1<:OptionEither, E2<:OptionEither} = promote_type(E1, E2)
80+
# TODO typejoin should actually not combine Identity{AbstractString} and Identity{String} to Identity{AbstractString}
81+
# hence we really need to redefine everything alike to be safe.
82+
83+
84+
# promote_type/promote_typejoin should work with all three Identity, Nothing and Const (aka OptionEither)
85+
# ==========================================================================
86+
87+
# nothing todo, as `Nothing` is already super well supported for promote_type and promote_typejoin

test/convert.jl

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using Test
2+
using DataTypesBasic
3+
4+
@test convert(Vector, Identity("hello")) == ["hello"]
5+
@test convert(Vector, nothing) == []
6+
@test convert(Vector, Const(43)) == []
7+
@test convert(Vector, @ContextManager cont -> cont(2)) == [2]
8+
9+
@test convert(Identity, nothing) == nothing
10+
@test convert(Identity, Const(:error)) == Const(:error)
11+
@test convert(Identity, @ContextManager cont -> cont(2)) == Identity(2)
12+
@test_throws MethodError convert(Identity, [1,2,3])
13+
14+
@test run(convert(ContextManager, Identity(4))) == 4
15+
@test_throws MethodError convert(ContextManager, nothing)
16+
@test_throws MethodError convert(ContextManager, Const(:error))
17+
@test_throws MethodError convert(ContextManager, [1,2,3])

0 commit comments

Comments
 (0)