-
Notifications
You must be signed in to change notification settings - Fork 1
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
The @provide/provided mechanism for optional interface functions. #1
Comments
Would this be enough? I think ChainRules.jl also does something like that. |
Hmm... yeah @rejuvyesh , we could just tell people to use Is there value in having Edit: I don't think |
I can't find it either. I think I saw some discussions in Julia slack's #autodiff channel, but can't find it anymore. |
I would let others pitch in. Depends on how the errors look like. |
One difference is that someone could make |
According to this blog post by the author of Tricks.jl, throwing that method error might be an anti-pattern? When an algorithm or user tries to call a method, if the environment doesn't provide it, then a method error will be thrown by Julia. So, I see the advantages of having some kind of
The second bullet is the more compelling reason to me to include all the logic for |
Very good thoughts.
We have this in POMDPs: https://juliapomdp.github.io/POMDPs.jl/stable/requirements/. The main difficulty is that an algorithm-writer has to manually declare all of the requirements up front, and this may be somewhat error-prone, especially if they have to declare the types of all the arguments. (The other option is to write a very heavy-duty analyzer that looks for the interface throughout the code, and this seems difficult)
Yes, the user can absolutely do that.
There is actually not too much logic (and it should be straightforward to support kwargs and where as well) IMO the most important reason for this is actually to enable efficient behavior for different ways that problems might be implemented (IIUC this is the reason for Base.IteratorSize). For example, in the future, we might have both CommonRLInterface.setstate!(env, s) and CommonRLInterface.clone(env) in the interface. For some envs, it might be very difficult to clone, and for others, it might be very difficult to set the state, so they might only be able to implement one. If someone implementing MCTS wants to handle both cases elegantly and with high performance, you need either |
Actually, it seems very plausible that someone might want to make provided(CommonRLInterface.actionmask, env::PythonGymWrapper) = hasattr(env.pyenv, "actionmask") I think that is a good enough argument to prefer |
Regarding the antipattern from the blog, though, it will be very tempting for someone to write: if !provided(clone, env)
error("You need to provide clone!")
end and this will be terrible because it will silence the method error. 😿 😿 . The same person might write something worse without |
That's a neat idea with An alternative strategy could be to provide testing utilities that can be used by authors of solvers and environments, but are not in the way when actually running a solver on an environment. I am thinking of something similar to an annotated function check_interface_methods(env_type, action_type = Int)
# mandatory interface
hasmethod(step!, (env_type, action_type)) || @error "step! error message"
hasmethod(reset!, (env_type,)) || @error "reset! error message"
....
# optional interface
hasmethod(clone, (env_type,)) || @warn "clone warning message"
....
end Solver authors could also implement a method like required_interface(::typeof(solver)) = (clone,) and we change The solver itself would never do any checks and simply throw What I like about this idea is that the What do you think about this? |
Yeah something closer to what @jbrea described is what I was thinking. Except instead of if myfunc ∉ provided_interface(::typeof(env))
error("Cannot find $myfunc!")
end I thinking shifting the dynamic from what the environment provides to what the solver requires would circumvent the temptation for anyone to use these functions for any other purpose than to check if a solver and environment are compatible. Though I would also add that if |
Ok, seems like there are two side questions being raised:
The question we need to answer in this thread could be stated as "Do we want a traits-like mechanism for telling what parts of the interface have been implemented"? The main purpose of this would be for dealing with multiple options for similar behavior (e.g. the |
I added a few more with this tag I think there could be a ton. |
Thanks for the clarification. If we want it, I agree that Now, specifically for the example of MCTS and struct CloneEnv{S,E}
init::S
state::S
env::E
end
CloneEnv(state, env) = CloneEnv(copy(state), state, e)
reset!(e::CloneEnv) = e.state .= e.init # to be discussed, if we want this
function step!(e::CloneEnv, a)
setstate!(e.env, e.state)
result = step!(e.env, a)
e.state = getstate(e.env)
result
end
# to be implemented by the author of Env
clone(e::Env) = CloneEnv(getstate(e), e) such that MCTS can rely on Do you envision other examples where a trait-like mechanism may be desirable? |
I totally missed this comment. I'm also satisfied that we need something like |
Possibly. I think this is another option that has tradeoffs. Can you clarify this: who implements
|
Yep. This looks convincing. It gives a lot of fexibility. The author of MCTS could for example decide to write either
If we adopt #6 (which I like) we could consider having a package
Yes. |
Ok, I'm going to mark this as a made-decision, let's let it rest for a few days while we wait for version 0.1 to get registered, and then we'll put it in version 0.2. If anyone still has any comments or concerns though, feel free to add them! |
What do we think of the @provide mechanism? That is the experimental/risky part of this package. Does anyone foresee big problems. Feel free to play around with it and see if it is intuitive.
The text was updated successfully, but these errors were encountered: