Closed vijayiyer05 closed 9 months ago
neuron.Neuron
to neuron.Session
to better convey class meaning (and to avoid stuttering)WRT neuron.Session
, my only hesitation is that it might imply you could have more than one NEURON session, which you can't do at this point.
WRT neuron.Session, my only hesitation is that it might imply you could have more than one NEURON session, which you can't do at this point.
Given this limitation, I would suggest to use the Singleton pattern for the class. In short: make constructor private & make a static method with a persistent variable.
neuron.Neuron
to neuron.Session
& add separate function to start a sessionNrnRef
class: add MatLab wrapper object for direct value assignment and retrieval (e.g. in python we use t[0]
)Re naming conventions, a few patterns can be seen from averaging across various classes implemented in MathWorks products:
Worth taking a look at the 'new' subsref: https://www.mathworks.com/help/matlab/ref/matlab.mixin.indexing.redefinesdot-class.html
Suggest to try it for 1-2 of the classes, if it's straightforward
Could also take a look at https://nl.mathworks.com/help/matlab/ref/libpointer.html and related pages of matlab working with c to see how they define their interfaces
- [ ] Rename
neuron.Neuron
toneuron.Session
& add separate function to start a session
For the separate function, how about keeping it simple such as neuron.launch
. Given it's a singleton, I don't think there's a need to put 'Session' in the function name.
- [ ] Rename
neuron.Neuron
toneuron.Session
& add separate function to start a sessionFor the separate function, how about keeping it simple such as
neuron.launch
. Given it's a singleton, I don't think there's a need to put 'Session' in the function name.
Looking back at this, I should clarify: main thought was that I don't think it's a golden rule to use 'session' in the Session
object factory method name even though that's the name of the object returned. 'Launch' came to mind first but that's not the main point.
I'm still confused by the rationale for this. It's going to be a someone for the foreseeable future. What's the advantage of returning a Singleton that requires a separate initialization step as opposed to just returning it initialized? Splitting it in two just seems like extra work.
I'm still confused by the rationale for this. It's going to be a someone for the foreseeable future. What's the advantage of returning a Singleton that requires a separate initialization step as opposed to just returning it initialized? Splitting it in two just seems like extra work.
I agree the constructor can 'know' that it's a singleton & just initialize itself if sensible (as it is here). No need for a separate initializer function. But that doesn't address the multiple constructor call matter. Note the constructor is typically hidden from the user in the singleton pattern, i.e., there's only one function to know.
Personally I wish there were a way to implement a Singleton constraint without a separate factory method. But currently a MATLAB constructor has no way to enforce that it's the only one.
That said, can you remind me if NEURON itself implements a singleton constraint? If only one NEURON session is possible under the hood, it's likely possible to avoid re-implementing a singleton constraint in the MATLAB wrapper.
Status update:
@ramcdougal ready for review/merge
@edovanveen, great that you've made progress on the command chaining. Is there a particular script file you can point me to where some syntax was improved or made possible by this improved command chaining?
@vijayiyer05 Yes, you can take a look at subsref
in one of the c++ object wrapper classes (for example Object.m
). After taking care of the input (the first element, or the first two elements of S
), the function neuron.chained_method()
is called, which does: [varargout{1:nargout}] = varargs{:}.subsref(S(n_processed+1:end));
. In other words, subsref
is called iteratively, until its input argument S
runs out of elements to process.
@vijayiyer05 Ah it seems that I misunderstood your question. An example of syntax that is possible now is:
>>n = neuron.launch();
>>v_max = n.Vector().append(10).append(11).append(12).max();
>>disp(v_max);
12
This example is from example_vector.m
.
Previously, this would have had to be something like:
>>n = neuron.launch();
>>v = n.Vector();
>>v.append(10);
>>v.append(11);
>>v.append(12);
>>v_max = v.max();
>>disp(v_max);
12
Thank you Edo for explaining. And I can see the added chained call in example.m
.
I could imagine some subjective disagreement on whether such a degree of chaining is advisable. In your experience (or that of @ramcdougal) would such chaining be common syntax for a Python user?
If the main advantage is making things a one-liner, it could be done as:
>> v=n.Vector(); v.append(10); v.append(11); v.append(12); v_max = v.max()
Are there other more complex (& realistic) use cases you (or @ramcdougal) can imagine where compound indexing is more 'necessary'?
As an example, one good use case for subsref (or more recently: customized object indexing) in my experience is navigating a complex/nested data structure with a mix of indexes and field names to reach the desired subtree or leaf. Without compound indexing, one has to create various intermediate variables & the semicolon-delimited one-liner approach above becomes untenable.
It's of course never literally necessary. However, sometimes it is convenient to allow things that one might conceptualize as single concepts to be expressed in a single statement.
(I would argue that multiple statements on one line don't convey the same conceptual relatedness that one single statement conveys.)
The classic example that people use all the time in Python is to create a Vector
and tell it to record some thing:
v = h.Vector().record(soma(0.5)._ref_t)
When you invoke a method on an object you don't necessarily return a modified version of an object; you may return a new object.
Being used to other programming languages, I don't understand why it would never not be allowed. Whether or not it should be used in a specific context is a different question.
Thanks @ramcdougal for your thoughts on this. Sorry I didn't catch it sooner.
Interesting re the importance you see of chaining for conceptual relatedness.
When you say methods can either return a modified version or a new object, can you comment on what's more typical in Python in your experience? On the MATLAB side, yes both could be true, but it's always one or the other for a given class, i.e., a class is either a handle class or a value class.
I haven't fully internalized the general case of dot notation to answer your broader puzzle, but what comes to mind is that a method (or property access) by dot notation could return a non-object structure or an object of a different type. So overall 'recursive chaining' seems only feasible in some circumstances.
Still interested to hear thoughts from @edovanveen on this. Again, thanks for weighing in!
@vijayiyer05 All supported Neuron functions (for now, at least) can only return a string, a double, or another Neuron object. So as long as all matlab wrappers support chained method calls, there should not be an issue.
The return type shouldn't matter unless this is a subsref
limitation. That which comes after the dot acts on whatever object the previous item returned.
In Python, the PlotShape.plot
method returns an instance of a plotly or matplotlib graph, and so anything that's dotted on to that would have to be an e.g. plotly graph method not a PlotShape method.
@ramcdougal in this case, subsref-chaining does not work on classes without a subsref implementation... Because dynamic methods don't actually exist in matlab.
Great discussion, thank you both!
Umbrella issue for exploring/tracking/dispensing ideas to enhance the consistency & usability of the NEURON Toolbox command-line interface (CLI)