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

Add whereType transformer #60

Merged
merged 7 commits into from
Feb 12, 2019
Merged

Add whereType transformer #60

merged 7 commits into from
Feb 12, 2019

Conversation

natebosch
Copy link
Member

This needs to be implemented as it's own class instead of using
one of the helpers like fromHandlers because it has unique generic
type requirements. We want to avoid requring the call site to re-specify
the generic type of the input stream. The return type needs to be a
StreamTransformer<Null, R> to allow to satisfy static checking.
However the argument to bind at runtime will never be a Stream<Null>
so we need to define it explicitly to widen the allowed argument.

This needs to be implemented as it's own class instead of using
one of the helpers like `fromHandlers` because it has unique generic
type requirements. We want to avoid requring the call site to re-specify
the generic type of the input stream. The return type needs to be a
`StreamTransformer<Null, R>` to allow to satisfy static checking.
However the argument to `bind` at runtime will never be a `Stream<Null>`
so we need to define it explicitly to widen the allowed argument.
/// Errors from the source stream are forwarded directly to the result stream.
StreamTransformer<Null, R> whereType<R>() => _WhereType<R>();

class _WhereType<R> extends StreamTransformerBase<Null, R> {

Choose a reason for hiding this comment

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

The null is a code smell here, what does it represent? can it also be generic, or should it maybe be Object?

Choose a reason for hiding this comment

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

Oh I see you commented about this, I don't fully understand the issue here but I can check out the repo and try it out which would probably make it more obvious.

Copy link
Member Author

Choose a reason for hiding this comment

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

See some discussion here:
dart-lang/language#180 (comment)

concrete scenario:

  var numbers = Stream<num>.fromIterable([1, 1.5, 2]);
  var ints = numbers.transform(whereType<int>());

In this case the transform excepts an argument assignable to StreamTransformer<num, S>. Since the whereType call doesn't have a way to statically know the num there, it either needs us to be explicit about it (numbers.transform(whereType<num, int>()), or to use some default that can fill in for any T that may have been on the stream. Null is the only possibility there for now, I'm not sure if we've figured out what the user referenceable bottom type will be after NNBD...

Copy link

@jakemac53 jakemac53 Feb 12, 2019

Choose a reason for hiding this comment

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

I would at least leave a lengthy doc comment here, its going to be pretty bizarre for anybody reading this in the future so some context would certainly help :).

Copy link
Member Author

Choose a reason for hiding this comment

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

Added a note to the doc comment, how does that look to you? I think for further details the git blame should point here which will have extra detail.

Choose a reason for hiding this comment

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

I think what you added is fine


class _WhereType<R> extends StreamTransformerBase<Null, R> {
@override
Stream<R> bind(Stream<Object> values) {

Choose a reason for hiding this comment

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

nit: maybe inputs instead of values? values is fairly ambiguous

Copy link
Member Author

Choose a reason for hiding this comment

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

This matches the naming pattern used elsewhere in the package.

Stream<T> bind(Stream<S> values) {

Choose a reason for hiding this comment

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

Do you think its actually a good name though?

Copy link
Member Author

Choose a reason for hiding this comment

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

I think it's about as good as anything else. A value or event is what is emitted by a stream when we don't know anything more specific about it, and I generally like to name a stream as the plural of whatever flows through it. I don't like the name inputs because I wouldn't call the thing emitted by the stream an input - I wouldn't name the argument to the listen callback that.

Choose a reason for hiding this comment

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

I don't like the name inputs because I wouldn't call the thing emitted by the stream an input

In the case of a stream transformer I would disagree - it is transforming one stream into another stream, so it makes sense to me to consider one the "inputs" stream and one the "outputs" stream.

If I see the word "values" in the middle of the method its not clear what that represents. Is that the outputs or the inputs? But if I see "inputs" I can immediately assume its the input stream.

In any case I don't care enough to withhold an lgtm, but I do think "inputs" is better :).

I wouldn't name the argument to the listen callback that.

Right, because listen doesn't transform anything, it only listens for values and there is only one stream in scope. With a transformer there are two streams in the scope of the bind method.

controller.onCancel = () {
var toCancel = subscription;
subscription = null;
if (!valuesDone) return toCancel.cancel();

Choose a reason for hiding this comment

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

It seems like it would be cleaner to null out the subscription field when the values are done, and get rid of this valuesDone field entirely.

Copy link
Member Author

Choose a reason for hiding this comment

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

I think I was worried about the onListen getting called again in a broadcast stream after the values where already done and the subscription != null condition not blocking us from doing work we shouldn't - but it looks like maybe StreamController handles this for us and won't call onListen again anyway. Done

@jakemac53
Copy link

jakemac53 commented Feb 12, 2019

As a more general comment, stream does already have a where method... a stream transformer seems quite heavy for this versus just doing stream.where((thing) => thing is SomeType)?

@natebosch
Copy link
Member Author

a stream transformer seems quite heavy for this versus just doing stream.where((thing) => thing is SomeType)?

That doesn't end up with the correct static or runtime type. You either need to chain a .cast() call, or use a hack like stream.expand<SomeType>((thing) => thing is SomeType ? [thing] : []). Both of these incur some amount of additional runtime overhead, either to construct a list or to do the cast that will be guaranteed to succeed.

/// Errors from the source stream are forwarded directly to the result stream.
StreamTransformer<Null, R> whereType<R>() => _WhereType<R>();

class _WhereType<R> extends StreamTransformerBase<Null, R> {

Choose a reason for hiding this comment

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

I think what you added is fine


class _WhereType<R> extends StreamTransformerBase<Null, R> {
@override
Stream<R> bind(Stream<Object> values) {

Choose a reason for hiding this comment

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

I don't like the name inputs because I wouldn't call the thing emitted by the stream an input

In the case of a stream transformer I would disagree - it is transforming one stream into another stream, so it makes sense to me to consider one the "inputs" stream and one the "outputs" stream.

If I see the word "values" in the middle of the method its not clear what that represents. Is that the outputs or the inputs? But if I see "inputs" I can immediately assume its the input stream.

In any case I don't care enough to withhold an lgtm, but I do think "inputs" is better :).

I wouldn't name the argument to the listen callback that.

Right, because listen doesn't transform anything, it only listens for values and there is only one stream in scope. With a transformer there are two streams in the scope of the bind method.

controller.close();
});
if (!values.isBroadcast) {
controller.onPause = subscription.pause;
controller.onResume = subscription.resume;
}
controller.onCancel = () {
var toCancel = subscription;
subscription?.cancel();

Choose a reason for hiding this comment

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

this used to return this value, not sure if it matters or not

Copy link
Member Author

@natebosch natebosch Feb 12, 2019

Choose a reason for hiding this comment

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

Hmm, I'm not sure either. There was some weirdness with an analyzer hint around this at one point that could have been why I had it in the fromHandlers implementation, and I copied most of that here.

@natebosch natebosch merged commit 1029f4c into master Feb 12, 2019
@natebosch natebosch deleted the where-type branch February 12, 2019 23:43
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.

2 participants