-
-
Notifications
You must be signed in to change notification settings - Fork 148
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
Support for open generic types #200
Comments
When Fluxor initializes, it creates an instance of every registered state (via feature), every reducer (statics are wrapped in an instance), and every effect class. These are then added to a list per store (effects) or per feature (reducers). There is no way this can be done with open generics. The only way I can see this being possible is if I added methods in the |
I think the possibility of explicitly registering generic type instantiations would be extremely helpful, as I currently have far too much boiler-plate when I need to simply 'register' another type to work w/ my generic state. Would it be possible to infer related effects & reducers if, for a given generic state GenericState, the library accepted the registration of only the state/type pair? Something like AddFluxor(o=> o.RegisterGenericState(GenericState, typeof(sometype)) would be great for me. |
I know that Autofac can indeed do a similar thing:
So it is able to create types based on when those types are specified. You know at DI time when an
Assuming you perform some kind of validation, once you inject this you'd want to make sure you have a Feature / Reducer available with this type, right? So maybe you can take a "lazy-load" approach to where you keep open generics in a separate registry/container until they are used, and just have a separate code path for them for validation and creation. I'm greatly oversimplifying. I apologize if it's not helpful. I'm unfortunately unfamiliar with the implementation details. I'm sure it's not this simple. But I'm hoping it offers some perspective. |
Yes, similar to that, but instead of registering a generic type for possible instantiation as: builder.RegisterGeneric(typeof(MyGeneric<>)).As(typeof(IMyGeneric<>)); I'd like to register the concrete types that are valid for a given generic state interface. That way Fluxor could presumably detect reducers/effects which reference the generic state, and instantiate concrete instances of reducers/effects for each registered type at load. @mrpmorris Obviously I'm not sure of feasibility here. Happy to help investigate if useful. |
I don't use the IServiceCollection to determine which instances to create because [ReducerMethod] and [EffectMethod] can be static, and therefore I use a wrapper class that is instantiated at the point of discovery. Maybe I could add something like
Seems a bit verbose though. Maybe I could add an additional extension like so
Then you'd do this
|
For the reducer and effect method attributes, I'm assuming you use reflection to find those, not the service collection. In that case, I'd think you could take the registration-free approach and build a registry in memory of every method you find. Then match the parameters to the type T you get from IState. Basically call Type.GetGenericTypeDefinition on the type passed to IState and compare that to the parameter type in the reducer method? Again I'm sure I'm over simplifying. But I think the goal should be to match these without requiring the user to register stuff. There's already enough boilerplate IMHO. |
@mrpmorris Either of those options would work well for me. The verbosity of the first one doesn't seem like too much of an issue, and either solves the root problem. The second is nice and clean, however. Personally, I like the explicit registration and discovery-time instantiation. It prevents the library from needing to figure out anything too complicated at runtime. |
@rcdailey what do you consider to be boilerplate? |
The boilerplate I'm talking about is that open generic Fluxor types need to be registered explicitly when the closed types do not. I'd prefer the open generic implementations be transparently found, which matches existing semantics. Maybe instead of boilerplate, we should call it just an inconsistency. I'm not sure if a reflection-based search & find solution for open generics is needed. I mostly shared that approach just to communicate my point (that if possible and reasonable to do so, having Fluxor find the types would be ideal). My disclaimer has always been that I have no idea of the implementation of Fluxor, so my implementation advice is quite possibly completely unhelpful. My overall feeling about boilerplate with Fluxor is that I'm already creating 4 types for relatively simple publish & subscribe semantics for a single value. Compared to similar logic I'd implement in Rx with On the flip side, I fully accept that I'm either using Fluxor for the wrong job, and/or that I haven't learned enough about Fluxor and my opinion might not be fair. But either way that's how I feel at the moment. At the end of the day, you're solving the root problem so I won't strongly complain about this. I appreciate the fact that you're engaged in the discussion and willing to discuss solutions. Thank you for that! |
@rcdailey If you had a state MyFeature, how would you know which class types to use for T when creating the store and creating states. |
Could someone please give me a real-life example of what you need to achieve? It'll need state / reducer / effects, then I can work out the best thing to do. |
Your last question got me thinking about this a bit harder. I realized there are probably complications if you have two classes deriving from In my case, I made my use case a little more specific:
Initially my request seemed reasonable but that was only because I was thinking of a single open generic implementation of reducer/feature/etc. But you've pointed out the obvious flaw here, or at least, helped to lead me to that realization. I think given this, the explicit registration of open generics as you proposed earlier is more than reasonable. Also it wouldn't be fair to call it an inconsistency, since the challenges (and thus requirements) are different between non-generic / closed generic implementations and open generic ones. Thanks for patiently walking through this with me to help me understand more about the challenges. As for a real-life example, I did give you one in the first post, although I admit it is out of context. But the context is pretty simple I think: I was using Fluxor to drive events across my components representing an active configuration chosen by the user. I have one component that allows the user to select 1 configuration in a list of many. Other components depend on this selection ("Active Config") in order to know what to present to the user. Hope that helps. |
I have a few controls that this applies to in production, but I think generics are often a natural when implementing grid controls. Let's say that you want to create a type-aware grid component Right now, the mechanics of this work well in Fluxor, but you're required to have defined a concrete implementation for each type, effect, and reducer that you'd like use w/ your generics. That means that I have a bunch of useless declared I want to just tell Fluxor that various Generic/type combos exist, so that they can be 'discovered' and instantiated without needing useless declaration files. I'm happy to put together a simple repro if it's easier to understand what I mean. |
I had put together this repo when you had helped w/ generics before, which shows the sort of declaration that it would be nice to avoid: https://github.com/ashern/fluxor_generic/blob/master/Store/IntReducers.cs Instead of requiring a concrete declaration file like IntReducers.cs, it'd be nice to do the same thing with Fluxor config. AddFluxor(o=> o.RegisterGenericReducer(GenericReducers, typeof(int))) Likewise for effects, etc. |
We won't be able to have open generics, but I think I will be able to allow you to specify closed generics without having to create classes.
Which I can simplify further later so you can create one class to register the state + reducers + effects in a generic way, and then do something like o.Register(MyRegClass But I'd like some concrete examples. What is ActiveConfig, what is in it, what reducers / effects would be there? |
Again please see the examples in my first post. I do not have any effects. |
I'm interested in what common state is in ActiveConfig, and an example of what a reducer would do to it |
@ashern I don't think I'd have state for a grid. That's a UI concern, not state. |
@mrpmorris I'm just describing a general concept. I have a collection of type-aware stuff in a couple applications that do various things, and it works well to tie them to strongly-typed instances of state which share generically-implemented effects & reducers. It's a pretty clean pattern that's working well in production, but there's just a bit more code required than necessary to allow Fluxor to use a generic implementation with any specific type. It'd be nice to have more direct control as you suggest over the discovery process. No big deal either way, as it's more a developer convenience than anything. |
@ashern I understand the requirement, I am just trying to discover a real-life use case. If you could give me a real example, I'd appreciate it. |
I appreciate your engagement, @mrpmorris. It's tough for me to give too deep a real life example without getting more into the weeds about projects than I'm able to. I apologize that these are somewhat canned examples. Let's say that you have an existing ecosystem of well defined types representing different business objects (financial instruments, for example). Let's also assume that it's a natural, from the business side, to maintain an equivalent state for each different type, such that you maintain a state object per type. In the simplest form, each state might contain a single generic property, TInstrument SomeFinancialInstrument. You might then have a reducer to update that value and an effect to operate on that object via a service, both of which you'd like to handle in a strongly typed way, but that you'd like to only implement once for all of the various types that you'd potentially encounter. I know this is all a bit hand-wavy. Happy to join your git chat this afternoon if you'd like to discuss further. |
@mrpmorris Peter, could you help me understand what a real-life example would provide you that you haven't been provided yet with the current examples? I think this would help us better understand what specific information you're looking for. For example, is there something unclear about how an Action, State, Feature, and/or Reducer would be defined in the open-generic use-case? |
@rcdailey It would provide me with understanding. If I am simply told to implement solution X without understanding the requirement then I won't be confident that the solution I am implementing is ideal. What I need to see is an example of what I would find in the generic state, what kind of generic actions there would be, and how a generic reducer would be expected to alter that state given the generic action. When I understand what is trying to be achieved (rather than how you are trying to achieve it) then I can help. |
@ashern Thanks! Please private msg me on gitter to discuss and I will reply as soon as I can. |
sorry to hijack, but here's my own concrete example where open generics might be helpful. Although I've solved the issue with the action public record QueriedApiAction<TResult>(IQuery<TResult> Query); And the IQuery public interface IQuery<TResult> { } The Generic effect public class QueriedApiActionEffect<TResult> : Effect<QueriedApiAction<TResult>>
{
private readonly IApiService_apiService;
public QueriedApiActionEffect(
IApiService apiService)
{
_apiService = apiService;
}
public override async Task HandleAsync(
QueriedApiAction<TResult> action,
IDispatcher dispatcher
)
{
var result = await _apiService.FetchAsync(action.Query);
dispatcher.Dispatch(
new ReceivedFromApiAction<TResult>(result)
);
}
} My http service public interface IApiService
{
Task<TResult> FetchAsync<TResult>(IQuery<TResult> query) ;
Task PostAsync<TCommand>(TCommand command) ;
} The result get dispatched public record ReceivedFromApiAction<TResult>(TResult Result); Let me know if you need to see how this would be used... |
So my "real" example would be what I put in my OP. Here it is again:
This obviously doesn't compile, but rather than how I'm doing it, this shows you what I'm doing as well. It's a very simple case. My state, I really don't know how else I can explain my situation to you. Maybe my case is too simple to be useful. Hopefully the others have a more sophisticated example. |
@rcdailey you are dispatching a state object as an action? You should create a proper action like "ReplaceStateAction" or something, so it describes what your intentions are. Otherwise, what does it mean to Person a Person, or Car a Car? ReplaceCarAction would make sense. This confusion is why I thought you were being illustrative. |
Not exactly. I'm dispatching an action. The action and the state happen to be the same object because I was eliminating duplicate code and reducing boilerplate. I'm expecting that the only time the state comes into play was during reducer processing. But I'm also unsure if it would break Fluxor to have them both be the same object. |
"The action and the state happen to be the same object....also unsure if it would break Fluxor to have them both be the same object" It won't break Fluxor at all, but it's not very intuitive. It's like trying to PurchaseOrder(verb) a PurchaseOrder(noun) when really you want to ReplaceConfigAction(verb) ActiveConfig(noun). Your code was confusing, which is why I thought it wasn't "real", especially when you also said it won't compile. |
I've started to put something together that covers features + reducers + effects, but I am still unsure as to a use.... I really need a simple stand-alone example that makes sense before I could merge this into master and release it. |
@ashern Do you have any real-life examples? Although the code is done, I am reluctant to add complexity without seeing a good reason to add generics. @rcdailey You could achieve what you want if you had a single feature state for configs. Have your generic action descend from a non-generic abstract class - you could then have your single reducer react to that non-generic class and call a method on the action to see what key you want to use to update the value in a dictionary.
|
@mrpmorris Hi Peter, sorry for the delay, just back from vacation. From my perspective, it potentially makes sense to use generic objects in state anywhere that you might use them regularly. Those use cases tend to be domain-specific. For example- Let's say that I own a chain of discount stores, and I have existing libraries to handle the back-end logic of managing the stores. I own 3 separate stores, and along with selling misc. merchandise, each store has idiosyncratic services that it offers to the public. One operates a bait and tackle department, which needs special functionality for live bait. One operates a sausage grill, which needs special functionality to account for spoilage, etc. All of this idiosyncratic functionality exposes different idiosyncratic properties for each physical location. To keep track of the properties of each discount store, there's a poco object representing each. There's a base object, DiscountStore which tracks properties common to all stores, and each store with idiosyncratic needs has a defined child-type derived from DiscountStore; BaitAndTackleStore, SausageGrillStore, etc. If I wanted to put a new web front end on my system, it might make sense to have a separate Fluxor state for each of my store locations of some generic state type Hopefully that's a satisfactory example. Generic functionality is useful anywhere you'd like to share state functionality across different types. |
@ashern Could you git checkout this branch and see if it does what you need? |
Thank you, @mrpmorris. I'll run it through some tests this weekend. |
Hey @mrpmorris, this update looks awesome, any thoughts on when you may release 4.2? Thanks! |
I would like to reopen this thread, to contribute a little bit, and to motivate to better solutions than mine... :-) The author asked for a realistic scenario where we would need to simplify what another user called 'the boilerplate', or maybe better, the 'ceremony' to setup, sometimes, repetitive and similar scenarios in the same application. So I'm giving all of you one, very rough, that I wrote in my spare time (over time I want to add other features, but for the moment I think it's better to show it in total simplicity). The situation is then the following, typical web application in a Web API scenario. We have a class Customer, whose details are not important, just like we will have Invoices, Users, and many different entities (on a database-backed server side application). We want to implement the remote request as a Fluxor state, and we want also to repeat this process for all the entities we want to use client-side. This is what I came up with, to create a generic layer:
As you can see, I used (of course) the support offered by Given this implementation, it's now easy to use it quickly with different entity types, as in:
We don't need to repeat any other code at all, so the boilerplate/ceremony is really kept at a minimum. In a blazor page we can then use these services like in:
Resulting finally in a totally strongly-typed code. There is still a lot of work to do, to bring this code to production level, but I think that it's a good starting point: I welcome anyone that wants to help improving this solution, even only with ideas. Thank you everybody for reading this. |
...and of course an example of starting the load process is in the following:
|
Is this currently supported? I'm having issues where my reducers are not being called by the dispatcher. Currently just searching on the web to see if I can find any solution before asking for help. |
Hi Did you add calls to ScanTypes for the reducer and effect classes? |
I wrote it, and I got it working easily. I'm trying to bring the code to a full library, but I don't have much time, so I'm very slow. Let me know if it doesnt' work yet, I will try to help... |
I have a set of configuration objects that I want to send around, but I don't want to implement the Action/Event/Feature/Reducer code multiple times for it. There's already enough boilerplate with these 4 concepts, doing it multiple times for every similar or same type is just a bit much IMHO.
Here's an example set of classes for what I was trying to do. This code may not work, I gave up on using Fluxor for this and instead am using an injected generic interface for this. But hopefully it communicates the general idea.
I'm just using this for sharing a single piece of state across the code base.
The text was updated successfully, but these errors were encountered: