-
Notifications
You must be signed in to change notification settings - Fork 91
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Prototype of method-forward paradigm #229
Conversation
Sorry for the churn. I based this off my UUID/serialization branch because I wanted to show how this interacts with that work. This means that you should only look at the last commit. |
installedcapacity = 0.0, | ||
econ = EconRenewable()) | ||
rc = RenewableCurtailment(name, status, bus, installedcapacity, econ) | ||
return RenewableFullDispatch(PowerSystems.name(rc), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This fixes what I think is a bug. If you call RenewableFullDispatch() you actually get a value that is of type RenewableCurtailment.
if :name in fn | ||
[push!(devices,d) for d in collection if d.name == name] | ||
if :name in fn || :base in fn | ||
[push!(devices,d) for d in collection if PowerSystems.name(d) == name] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that this approach means that we will likely see tons of name collisions, and so will have to prefix function calls with "PowerSystems.".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I must be missing something, why do we have to prefix PowerSystems.
to the name
function?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We only have to do it when name collides with a local variable, as it does here. Other possibilities are to change the local variable to something like "name_" or call the function "get_name".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Of course, my bad. I'm fine with renaming the local variable here.
Adding a core dependency on another package is less than optimal. I understand the need to have a better interface but reading the Also, this discussion is pretty clear about why in Julia hacked inheritance is not advisable and why these perceived issued The comments from StefanKarpinsky here are also useful https://discourse.julialang.org/t/why-is-it-impossible-to-subtype-a-struct/19876/11 Here is the issue in Julia about Abstract Types with Fields JuliaLang/julia#4935 (comment) Also this http://www.stochasticlifestyle.com/type-dispatch-design-post-object-oriented-programming-julia/ It is worthwhile re-thinking some aspects of the way we define the fields in the structs and the type structure to make it more efficient, but I am not convinced this is the proper way. |
We could actually just copy the function macro and dependent code into PowerSystems. It isn't much code. I linked it here for simplicity. We should only focus on the use of it. The discourse thread you linked shares the same types of discussions as the ones the ReusePatterns author linked to. They talk specifically about our problem where multiple concrete types share the same fields. Here are the main points:
I think this is likely the most pragmatic solution to solve the problem. Dheepak and I spent a lot of time discussing it. I'm still very open to other options. Also, I think that this PR shows that making this change will be very painful. It's a lot of changess. |
@jd-lara to add more context, let's say someone wants to use
Let's say someone wants to make a
and if we change
This will allow us to make changes to the fields of However, this is provided developers that extend
A user that extends
However, it can be tedious to define all the functions for every extension. This is where having the
any function defined on This is probably all obvious to you @jd-lara, I'm mainly writing it out so that others using The only downside of this is that users of @jd-lara do you have suggestions for alternatives that increase code-reuse and allow extensions without be tedious and putting the onus on other developers? Perhaps we can discuss this over a conference call to iron out the details. |
Uses the ReusePatterns.jl @forward macro to remove field duplication in structs that have common sets of fields.
888c1d6
to
bd902e5
Compare
@daniel-thom option 2 is probably the most Julian way of tackling this. I am still unconvinced that we should support this type forwarding feature natively and if someday this capability makes into the Julia code base we can add it. I haven't seen any discussion to try to do it in the short run. Even though defining interfaces can be tedious, making a type system that can be extended with interfaces more simply is an excellent design goal if we are not there yet. Also, it is clear we should move to a method forward use mode and rely less on the dot notation. This is a good improvement. However, I think this is unrelated to the use of macros to extended the types. @kdheepak We have had this conversation about code re-use multiple times, and this solution all are trying to do is force Julia to work like OOP, and the discussion in the forum is long about how this just means that there is an improper type system design. The case of DataFrames is pretty well discussed. My proposal to this "problem" is to do something similar to what is done in DifferentialEquations.jl (I have to prototype this better). Overload some functions like struct MyCustomRenewable{T<:RenewableGen} <: RenewableGen
base_type::T
myfield::Float64
my_other_fied::Float64
end t = RenewableCurtailment()
foo = MyCustomRenewable(t, 10.0, 10.0)
MyCustomRenewable{RenewableCurtailment}:
base_type: RenewableCurtailment(name="init")
myfield: 10.0
my_other_fied: 10.0 In this way, a lot of methods that exist already for Renewable Energy can be extended. Additional to this we can have a generic type for prototyping devices that is like this struct GenericInjection <: Injection
name::String
bus::Bus
available::Bool
data::Dict{Symbol, Any}
end and enable a type that can have any data template encoded into the Dict for users who want to add a very unconventional device. One of the core questions here is which capabilities we want to enable such that users can extend the type structure for their use and how we want the type structure to be extended when contributing to the code. I believe most use cases so far are the former. |
@daniel-thom @jd-lara @kdheepak , where are we on this? If this is our path forward, I'd like to pursue it soon. If more work is required to identify the solution, I'll rearrange some priorities. |
@claytonpbarrows I think we agree on principle about going on this path. The implementation is not clear yet. I have been reading about the use of the @daniel-thom mentioned that there are some severe performance penalties with some of the "official" approaches to solve this and we probably should document that and ask in discourse. |
Dheepak proposed an implementation in a separate repo (internal link removed). I am 100% onboard with that. I do not know of any performance penalties with it. |
This is not a changeset that would be merged into our codebase. It is just a prototype to evaluate whether we want to proceed with this approach.
The problem we're trying to solve is that many of our structs share common fields. This can be problematic when PowerSystems consumers want to extend their own types. They have to copy all of the fields. More importantly, if we ever added a new field and used that field in an exported method, those consumers would get unclear runtime errors.
One part of this solution is to move away from defining field access (value.field) as part of our public API. We would need to have accessor methods defined for every field. Instead of typing "generator.name" you would type "name(generator)".
This changeset implements the paradigm for the subtypes of RenewableGen. The common fields are in the new struct RenewableGenBase. The forward macro from ReusePatterns.jl forwards all methods to that type.
The rest of the changes were to make the tests pass or illustrate how many lines of code we will have to change.
Thanks to Dheepak for finding and proposing the "forward" macro solution.