8000 [Proposal] Fluent parametrisation · Issue #635 · morelinq/MoreLINQ · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

[Proposal] Fluent parametrisation #635

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

Open
Orace opened this issue Nov 2, 2019 · 6 comments
Open

[Proposal] Fluent parametrisation #635

Orace opened this issue Nov 2, 2019 · 6 comments

Comments

@Orace
Copy link
Contributor
Orace commented Nov 2, 2019

This is a proposal to unify lookalike methods.

An example of use with Zip is :

  source1.Zip(source2)   // ValueType creation overload
    .PadSource1With(foo) // zip parametrisation
    .PadSource2With(bar) // zip parametrisation
    .Where((a,b) => ...) // zip consumption
    . ...

Here are the methods signatures:

  ZipEnumerable<T1, T2, TResult> Zip(this IEnumerable<T1> source1, IEnumerable<T2> source2, Func<T1, T2, TResult> func) => new ZipEnumerable(source1, source2, func);
  ZipEnumerable<T1, T2, (T1, T2)> Zip(this IEnumerable<T1> source1, IEnumerable<T2> source2); // ValueType creation overload

// ZipEnumerable is a valid and honest IEnumerable
class ZipEnumerable<T1, T2, TResult> : IEnumerable<TResult>
{
  // immutable configuration
  readonly ZipConfiguration _configuration;

  ZipEnumerable(IEnumerable<T1> source1, IEnumerable<T2> source2, Func<T1, T2, TResult> func)
  {
  }

  // Return a enumerator that behaves according to the configuration
  public IEnumerator<TResult> GetEnumerator()
  {
  ...
  }

  // Configuration is fluent so updating it create an updated configuration and a new ZipEnumerable
  ZipEnumerable<T1, T2, TResult> PadSource1With(T1 padValue) => new ZipEnumerable<T1, T2, TResult>(_source1, _source2, _func, _configuration.WithPadSource1Value(padValue));

... // more configuration methods
}

The fluent parametrisation pattern have some rules:

  • configuration are immutable (for modification, create a new modified copy)
  • any configuration is attainable from any other configuration (any set can eventually be unset)
  • ?

Finally I expose the possibilities for Zip here as an example:

We have much more flexibility, for each source we can use one of those behavior :

  • End the result sequence on source sequence end. (It's the default behavior and, when used for all sources, it's equivalent to ZipShortest)
  • throw if the source is too short
  • perform padding with a static value
  • perform padding with the last value (we take care of empty source)
  • ...
source1.Zip(source2)
  .ThrowOnSource1Short(); // Throw only if source1 is too short
                          // stop if source2 too short (default behavior)

source1.Zip(source2)
  .ThrowOnSource1Short() // Throw only if source1 is too short
  .PadSource2WithLastOr(default); // Pad source2 with its last value (or the default value if it's empty)

source1.Zip(source2, source3, source4)
  .ThrowOnShort() // ask for equi zip
  .PadSource3With(foo); // This override the previous call and if source3 is too short it will be padded.

var zip1 = source1.Zip(source2); // zip1 is ZipShortest
var zip2 = zip1.PadSource1With(bar); // does not modify zip1

source1.Zip(source2); //ZipShortest (_do not do padding rule_)
source1.Zip(source2).ThrowOnShort(); // EquiZip
source1.Zip(source2).PadWithDefault(); // ZipLongest
@atifaziz
Copy link
Member
atifaziz commented Nov 6, 2019

I understand the motivation and sometimes I think it's a good way (see AwaitCompletion and family) but it's too allocate-y for the simple cases.

@Orace
Copy link
Contributor Author
Orace commented Nov 6, 2019

Simple cases will not need parametrization.

@atifaziz
Copy link
Member
atifaziz commented Nov 6, 2019

What are the non-simple ones? Most of your examples cite Zip but we discussed the allocate-iness of that in PR #639 review.

@Orace
Copy link
Contributor Author
Orace commented Nov 6, 2019

Current Zip methods do not manage complexes cases, like a zipped sequence of the exact length of the first input.

I do tried an implementation of ZipWhile #640 but the cases handling is not convincing.

In the search of a good way to add functionality, the fluent parameterization is a convenient way to build the ugly "should we continue" predicate from #640.

It's allocate object on query creation (but not more than any linq request).

And I believe that the ZipWhile #640 implementation is pretty much efficient, the "should we continue" predicate is only called when one or more source sequence(s) just have ended.

https://github.com/Orace/MoreLINQ/blob/d7388201e84ce0bb0ddd94a40756b7b67dd800bd/MoreLinq/Zip.g.cs#L334-L350

For a Zip<T1,..TN> it is at most N-1 call to the predicate (we don't call it when all sequence are consumed).

For first.Zip(second) it's called at most 1 time.

An other example of use is Window #629.

  input.Window(10)
    .WithLeft()
    .WithRight()
    .WithEndPadding(0);
    .WithFirstValuePadding();

Or Lead and Lag

  input.Lead(10).WithPadding(padValue);
  input.Lead(10).WithLastValuePadding();
  input.Lead(10).WithoutOverflow();

@Orace
Copy link
Contributor Author
Orace commented Nov 13, 2019

Interleave may also have fluent parametrization.

  • WithUnbalancedPadStrategy(paddingValue = default)
  • WithUnbalancedThrowStrategy() (EquiInterleave [Proposal] EquiInterleave #695 )
  • WithUnbalancedStopStrategy() (InterleaveShortest ?)
  • WithUnbalancedSkipStrategy()

@omatrot
Copy link
omatrot commented Jan 24, 2022

I would love to see this proposal going live as I need to work on IEnumerable<dynamic> (not POCO) the same way we see it here. Lag is particularly needed.

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

No branches or pull requests

3 participants
0