@WschW, thanks for pointing it out. I completely missed it…
The functionalities are very similar to @quasiabstract, with the following differences:
@protostruct store the field names and types in a global register, while @quasiabstract uses Julia introspection facilities;
in StructuralInheritance both the abstract and the concrete tape name are required in the code (respectively, to annotate function types and to call the constructor), while in ReusePattern I define constructors with the same name as the abstract type, hence the user may simply forget about the name of the concrete type.
I added the link to the list of package providing similar functionalities in the README.md.
the most useful part of ReusePatterns.jl is probably its README.md
+a_big_number. I was going to say that, but you beat me to it. It’s the beginning of a review of approaches.
Even review and redesigning of simpler parts of the language; eg rationalizing sorting or searching was a large undertaking.
My impression is that it will take a lot of research (practical research) and experience to hit on design patterns. The tools will grow in solidity as effective patterns become clear.
I’m not the one to do it. I was trained as a physicist, not a software engineer. I’m struggling now to design good interfaces for random walk simulations. Thinking hard about interfaces is new to me. (I’ve written a ton of simulations over the years, but it’s never very resuable.) Doing it when the options are not clear is even harder. But, I appreciate you gathering resources.
Those are the major differences but there are two more minor differences which hopefully will change.
@quasiabstract does not yet support the creation or inheritance of parametric types.
julia> using ReusePatterns
julia> @quasiabstract struct A{T}
f1::T
end
ERROR: UndefVarError: T not defined
Stacktrace:
[1] top-level scope at none:0
@protostruct has a vulnerability that @quasiabstract does not have:
julia> using StructuralInheritance
julia> g(x) = Int
g (generic function with 1 method)
julia> @protostruct struct B
f1::g(1)
end
ProtoB
julia> g(x::Int) = Float64
g (generic function with 2 methods)
julia> @protostruct struct C <: B end
ProtoC
julia> @doc C
No documentation found.
Summary
≡≡≡≡≡≡≡≡≡
struct C <: ProtoC
Fields
≡≡≡≡≡≡≡≡
f1 :: Float64
Supertype Hierarchy
≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡
C <: ProtoC <: ProtoB <: Any
Approaching what Mixers.jl shouldn’t be too difficult for ReusePatterns.jl UnionAll types store type parameters in the var field, and getting the TypeVar of a field type is just a small change, though it does require utilizing an undocumented and non-exported function from base.
julia> fieldtype(Complex,1)
Real
julia> fieldtype(Base.unwrap_unionall(Complex),1)
T<:Real
However, way that parametrics work in Mixers does not allow for inheriting from a specialized parameter this is something you may want to do when inheriting structure in a non-mixin way for example:
julia> using StructuralInheritance
julia> @protostruct struct A{T}
fieldFromA::T
end
ProtoA
julia> @protostruct struct B{D} <: A{Complex{D}}
fieldFromB::D
end "SomeOtherPrefix"
SomeOtherPrefixB
julia> @protostruct struct C <: B{Int}
fieldFromC
end
ProtoC
julia> @doc C
No documentation found.
Summary
≡≡≡≡≡≡≡≡≡
struct C <: ProtoC
Fields
≡≡≡≡≡≡≡≡
fieldFromA :: Complex{Int64}
fieldFromB :: Int64
fieldFromC :: Any
Supertype Hierarchy
≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡
C <: ProtoC <: SomeOtherPrefixB{Int64} <: ProtoA{Complex{Int64}} <: Any
This is more complex than what Mixers.jl does.
Another concern for inheritance is qualifying types, for example we can see that Mixers currently does not qualify types:
julia> using Mixers
[ Info: Precompiling Mixers [2a8e4939-dab8-5edc-8f64-72a8776f13de]
julia> module M_1
using ..Mixers
struct A end
@premix struct B; f1::A; end
end
Main.M_1
julia> M_1.@B struct C end
ERROR: UndefVarError: A not defined
Stacktrace:
[1] top-level scope at none:0
julia>
This is not an issue for ReusePatterns.jl (it directly uses types from introspection), or for StructuralInheritance.jl (it qualifies function and type names).
“One gotcha is the need to put empty curly braces on a struct with no parametric fields, if it is going to have parametric fields after @mix or @premix. This keeps Mixers.jl code simple, and is a clear visual reminder that the struct is actually parametrically typed”
I thought about inserting the brackets in the macro, but actually I like that I can tell when there are parametric types. But I would accept a pull request if people really hated that behaviour.
that example does not use parametric fields, this is an issue of type qualification which shows which module a type was defined in, taking the same code and putting everything in one module works just fine:
julia> struct A end
julia> using Mixers
julia> struct A end
julia> @premix struct B; f1::A; end
@B (macro with 1 method)
julia> @B struct C end
julia>
As for the first example, if I understand what is happening. Inheriting from a specialised parameter is interesting, but Mixers can’t do inheritance like that because it’s made to allow multiple inheritance! I often have a couple of mixins chained and multiple trait dispatches on the same struct.
I was meaning to say this earlier, I think there are three fundamental approaches here - link field reuse to abstract types as both ReusePatterns and StructuralInheritance do, or link field reuse to traits, or do neither and leave connecting fields and behaviours up to the user.
Mixers does neither. Although I will probably add a @traitmix macro option to automate the trait generation, as that’s what I do most of the time.
Turns out you can just qualify A in the mixin and your example works fine:
julia> using Mixers
module M_1
julia> module M_1
using ..Mixers
struct A end
@premix struct B; f1::M_1.A; end
end
Main.M_1
julia> M_1.@B struct C end
julia>
Hi everyone,
I was looking for type inheritance in Julia and find out your discussion.
For me and for most relatively experienced developers, POO is the natural approach when creating what we call here “Composite Types”.
I don’t really care if it’s a generated-by-macro composition behind the scenes, as long as I have a simple way to do it.
What I think is a problem is you have too many solutions for this simple problem.
So far I’ve found these packages that could potentially solve my problem:
For composition, you don’t really need a package. Some packages have a @forward macro to make method dispatch easier, but it’s not that big of a deal to just write it out. And as a bonus, it is very readable.
Pardon my question, here, but, aren’t there plenty of OOP languages already? If you prefer that to multiple dispatch, what draws you to Julia? Ditching old fashioned OOP for something more powerful is sort of the point.
I came to Julia for efficiency and ease of use, not for multiple dispatch.
I take all the functionalities that Julia has to offer and if OOP is not that efficient I’m okay with that.
I said that what happened behind the scenes is not really important for me.
Actually, with the number of packages designed especially for rewriting OOP code into Julia’s efficient type system, this is clearly something that developers want.
Don’t count me in your list I just want slightly more code reuse. I’m sure most of the competing solutions you mention will die in the next few years anyway (except mine of course , which has nothing to do with OOP)