My issue is essentially this: I have a type I would like to instantiate at some point with a validity check in an inner constructor, but before that I need to collect the data needed to actually build the type. I figured using the builder pattern commonly used by Rust packages might do the trick, even if it wasn’t the most efficient method ever.
The (rather massive) type that I am trying to build is defined as follows:
"""
The result of building an exercise.
"""
struct Tehtävä{
Nimi <: AbstractString,
Tehtävänanto <: AbstractString,
Ratkaisu <: Union{Missing, <:AbstractString},
Vastaus <: Union{Missing, <:AbstractString},
Avainsanat <: Vector{<:AbstractString},
Assignment <: AbstractString,
Solution <: Union{Missing, <:AbstractString},
Answer <: Union{Missing, <:AbstractString},
Keywords <: Vector{<:AbstractString},
} <: AbstraktiTehtävä
nimi ::Nimi
tehtävänanto ::Tehtävänanto
ratkaisu ::Ratkaisu
vastaus ::Vastaus
avainsanat ::Avainsanat
assignment ::Assignment
solution ::Solution
answer ::Answer
keywords ::Keywords
function Tehtävä{
Nimi ,
Tehtävänanto,
Ratkaisu ,
Vastaus ,
Avainsanat ,
Assignment ,
Solution ,
Answer ,
Keywords ,
}(
nimi ::Nimi ,
tehtävänanto ::Tehtävänanto,
ratkaisu ::Ratkaisu ,
vastaus ::Vastaus ,
avainsanat ::Avainsanat ,
assignment ::Assignment ,
solution ::Solution ,
answer ::Answer ,
keywords ::Keywords ,
) where {
Nimi <: AbstractString,
Tehtävänanto <: AbstractString,
Ratkaisu <: Union{Missing, <:AbstractString},
Vastaus <: Union{Missing, <:AbstractString},
Avainsanat <: Vector{<:AbstractString},
Assignment <: AbstractString,
Solution <: Union{Missing, <:AbstractString},
Answer <: Union{Missing, <:AbstractString},
Keywords <: Vector{<:AbstractString},
}
if isempty(nimi)
error("Tehtävältä puuttuu nimi…")
end
if isempty(tehtävänanto) && isempty(assignment)
error("Tehtävän $nimi tehtävänanto puuttuu…")
end
if isempty(avainsanat) && isempty(keywords)
error("Tehtävän $nimi avainsanat puuttuvat…")
else
if true ∈ isempty.((sana for sana ∈ avainsanat))
error("Jokin tehtävän $nimi avainsanoista oli tyhjä…")
end
if true ∈ isempty.((sana for sana ∈ keywords))
error("Jokin tehtävän $nimi avainsanoista oli tyhjä…")
end
end
new(
nimi ,
tehtävänanto ,
ratkaisu ,
vastaus ,
avainsanat ,
assignment ,
solution ,
answer ,
keywords ,
)
end
"""
Generates this type from a give type builder.
"""
function Tehtävä(a::TehtävänArgumentit)
Tehtävä(
a.nimi,
a.tehtävänanto, a.ratkaisu, a.vastaus, a.avainsanat,
a.assignment, a.solution, a.answer, a.keywords
)
end
end
The builder is defined very similarly, except it does not take other builders as arguments:
"""
A builder used to generate the fields of an exercise during parsing.
"""
struct TehtävänArgumentit{
Nimi <: AbstractString,
Tehtävänanto <: AbstractString,
Ratkaisu <: Union{Missing, <:AbstractString},
Vastaus <: Union{Missing, <:AbstractString},
Avainsanat <: Vector{<:AbstractString},
Assignment <: AbstractString,
Solution <: Union{Missing, <:AbstractString},
Answer <: Union{Missing, <:AbstractString},
Keywords <: Vector{<:AbstractString},
}
nimi ::Nimi
tehtävänanto ::Tehtävänanto
ratkaisu ::Ratkaisu
vastaus ::Vastaus
avainsanat ::Avainsanat
assignment ::Assignment
solution ::Solution
answer ::Answer
keywords ::Keywords
function TehtävänArgumentit{
Nimi ,
Tehtävänanto ,
Ratkaisu ,
Vastaus ,
Avainsanat ,
Assignment ,
Solution ,
Answer ,
Keywords ,
}(
nimi ::Nimi ,
tehtävänanto ::Tehtävänanto ,
ratkaisu ::Ratkaisu ,
vastaus ::Vastaus ,
avainsanat ::Avainsanat ,
assignment ::Assignment ,
solution ::Solution ,
answer ::Answer ,
keywords ::Keywords ,
) where {
Nimi <: AbstractString,
Tehtävänanto <: AbstractString,
Ratkaisu <: Union{Missing, <:AbstractString},
Vastaus <: Union{Missing, <:AbstractString},
Avainsanat <: Vector{<:AbstractString},
Assignment <: AbstractString,
Solution <: Union{Missing, <:AbstractString},
Answer <: Union{Missing, <:AbstractString},
Keywords <: Vector{<:AbstractString},
}
new(
nimi,
tehtävänanto, ratkaisu, vastaus, avainsanat,
assignment, solution, answer, keywords
)
end
end
The reason both of these are parametric is that their parameters might vary from Strings
to SubString
s. Now my issue is that I can’t seem to be able to build an instance of the builder type TehtävänArgumentit
. Trying to generate an initial value of TehtävänArgumentit
with the call
tehtävän_argumentit = TehtävänArgumentit(
basename(tiedoston_nimi),
"",
"",
"",
String[],
"",
"",
"",
String[]
)
results in an error
ERROR: LoadError: MethodError: no method matching TehtävänArgumentit(::String, ::String, ::String, ::String, ::Vector{String}, ::String, ::String, ::String, ::Vector{String})
I thought that the UnionAll types in the type specification of TehtävänArgumentit
and Tehtävä
would handle all of the possible sub-type combinations, but this is obviously not the case here. How might I change this to allow the builder to be instantiated while still allowing the types of its fields to vary during construction, as I return new builders from functions f: (builder, new_field_value) ↦ builder_with_modified_field
?