Skip to content
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

Wrap single observer to a separate expression #1093

Merged
merged 6 commits into from
May 18, 2020
Merged

Conversation

kitchoi
Copy link
Contributor

@kitchoi kitchoi commented May 18, 2020

Closes #1076

This PR moves the construction of a single observer to a separate expression object.
Expression remains to be user-facing: it aims at providing a user-friendly interface, e.g. instance methods for composing expressions.

The internal expression objects are hidden from the user, and all implement a common interface called _IExpression. (That interface is rather basic at the moment). Despite the name, Expression is not an instance of _IExpression. An alternative naming suggestion (for either renaming Expression or renaming _IExpression is very welcome).

Checklist

  • Tests
  • Update API reference (docs/source/traits_api_reference)
  • Update User manual (docs/source/traits_user_manual)
  • Update type annotation hints in traits-stubs


@_IExpression.register
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, nothing does issubclass or isinstance check. The registeration is unused. The registration is only here for code readability and discovery (e.g. it is easier to see what implement the interface).

@mdickinson
Copy link
Member

Please can you give some examples of how a user might use Expression?

@kitchoi
Copy link
Contributor Author

kitchoi commented May 18, 2020

Sure.

A user will typically create an instance of Expression using one of the top-level functions such as trait.
e.g. trait("name") returns an instance of Expression.

The parse function from parsing can create an instance of Expression from a string:
e.g. parse("a.b.c") returns an instance of Expression.

Then they can extend it using the instance methods on Expression. e.g. trait("name").trait("child") returns a new instance of Expression.

With that, I don't expect Expression.__init__ to be called directly by the users because there are more convenient ways. That said, the user can, if they really wanted to, instantiate an empty expression with Expression(), that is fine. They could create one with the internal object Expression(_expression=...), but the naming of the internal variable and docstring hopefully make it clear that such usage is not part of the public interface so breakage is possible.

@mdickinson
Copy link
Member

Right, but we're specifically making Expression user-facing. I'm wondering what a user would use it for.

I don't think we actually need the Expression class at all.



def _create_graphs(expression, graphs=None):
""" Create ObserverGraphs from a given expression.
@_IExpression.register
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should be able to get rid of this class entirely.

@kitchoi
Copy link
Contributor Author

kitchoi commented May 18, 2020

Please can you give some examples of how a user might use Expression?

Ah, you are asking about "using" the expression. I am sorry, I misunderstood your question earlier.
An expression is to be used together with HasTraits.observe, e.g.

@observe(trait("parent").trait("child"))
def updated(self, event):
    pass

Alternatively we could just let trait("name") return an instance of _SingleObserveExpression (renamed it to SingleObserverExpression). Likewise, trait("name").trait("attr") will return an instance of _SeriesExpression (renamed to SeriesExpression). Then all the instance methods, e.g. then, will need to be inherited by SingleObserverExpression and SeriesExpression and so forth. Users reading the API documentation will need to find the method on the type of expressions being created. If we wrap all these in a thin facade, then there is only one class users need to go to in order to find documentation.

@mdickinson
Copy link
Member

Alternatively we could just let trait("name") return an instance of _SingleObserveExpression (renamed it to SingleObserverExpression). [...]

Yes, I think this is the way to go.

@kitchoi
Copy link
Contributor Author

kitchoi commented May 18, 2020

Currently, the user manual can just point to this Expression class and cross-reference its API documentation. If we are to remove this, all references to Expression will refer to an interface, not just docstrings in observe, but user manuals.

While experienced developers can understand interfaces and therefore the Expression would seem redundant, the absence of this facade increases the learning curve for those who are new to Python and/or object oriented programming. Considering the goal is to minimize the cognitive load for these users, would you reconsider keeping this facade for that?

@mdickinson
Copy link
Member

I think the documentation concerns are secondary here. We should start with a simple, clean object structure, and then we can figure out how to best document it. Complicating the code to simplify the docs would be counterproductive.

The way I'd do this would be to have an Expression base class (technically an abstract base class, but it doesn't need to be actually defined as inheriting from abc.ABC) containing the common code (e.g., the implementation of the then method), and have the concrete subclasses like SingleObserverExpression inherit from Expression. Do away with the interface entirely: just fold the methods into the Expression abstract base class.

@mdickinson mdickinson mentioned this pull request May 18, 2020
4 tasks
@mdickinson
Copy link
Member

mdickinson commented May 18, 2020

In case it's not clear what I mean, I made an example PR in #1094 to demonstrate; it's not a huge step away from the existing code. (Note that I didn't do any renaming: just trying to communicate the shape of the suggested changes, not trying to get all the details right.)

@kitchoi
Copy link
Contributor Author

kitchoi commented May 18, 2020

No problem. I understand exactly what you meant, and have made the exact same changes you have in #1094 (to the point they are identical). Would you like me to push those changes here from your PR?

@mdickinson
Copy link
Member

No, ignore my PR. I'll close it. :-)

@@ -25,25 +27,8 @@ class Expression:
``HasTraits.observe`` method or the ``observe`` decorator.

An Expression is typically created using one of the top-level functions
provided in this module, e.g.``trait``.
provided in this module, e.g. ``trait``.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Drive-by: Without the space Sphinx refuses to display trait as code.

@kitchoi kitchoi requested a review from mdickinson May 18, 2020 13:16
@kitchoi
Copy link
Contributor Author

kitchoi commented May 18, 2020

No, ignore my PR. I'll close it. :-)

Thank you for diving in though. Much appreciated.

new = Expression()
new._prior_expression = _ParallelExpression([self, expression])
return new
return self
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we drop this branch? Is it required for correctness, or is it more for optimization?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(And if it's required for correctness, what happens with something like expr1 | expr2 | expr1?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an optimization and yes we can drop it.

Copy link
Member

@mdickinson mdickinson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LVGTM. One outstanding minor question / comment.

@kitchoi kitchoi merged commit ef2483d into master May 18, 2020
@kitchoi kitchoi deleted the 1076-expression-rewrap branch May 18, 2020 14:33
@kitchoi kitchoi restored the 1076-expression-rewrap branch May 18, 2020 14:33
@kitchoi kitchoi deleted the 1076-expression-rewrap branch May 18, 2020 14:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Restructure Expression objects
2 participants