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

TrySingle : xs → Zero | (x, One) | Many #540

Closed
jimmcslim opened this issue Oct 11, 2018 · 9 comments
Closed

TrySingle : xs → Zero | (x, One) | Many #540

jimmcslim opened this issue Oct 11, 2018 · 9 comments
Assignees
Milestone

Comments

@jimmcslim
Copy link
Contributor

I'm wondering whether the community would think that TrySingle would be a useful addition to MoreLINQ.

public static Cardinality TrySingle<T>(this IEnumerable<T> values, out T result)
public enum Cardinality { Zero, One, Many }

Where the behaviour of TrySingle is to attempt to return the first, single element from the enumerable. If the enumerable has a single element, out result is populated with the item, and Cardinality.One is returned. However, if there are zero items in the enumerable then the method returns Cardinality.Zero; if there are multiple items in the enumerable then the method returns Cardinality.Many. In both of these cases the out result is null.

A consumer of this method can then distinctly handle the Zero or Many cases in whichever special way is required.

I'm happy to submit a PR if this is worthwhile.

@atifaziz
Copy link
Member

Just curious, Take(2).ToArray() wouldn't cut it? The array length would provide the cardinality.

@jimmcslim
Copy link
Contributor Author

True, that could be the implementation perhaps.

@atifaziz
Copy link
Member

If we are going to add this, I would like to avoid introducing the Cardinality enum. I think the following is a lot more generic, a pure function and avoids by-ref, output, parameters:

public static TResult
    TrySingle<T, TCardinality, TResult>(this IEnumerable<T> values,
        TCardinality zero, TCardinality one, TCardinality many,
        Func<TCardinality, T, TResult> resultSelector)

In other words, the caller defines the three cardinality cases and the resultSelector will get to know which one was the case. The original signature and enum you proposed can then be implemented locally as follows:

public static Cardinality TrySingle<T>(this IEnumerable<T> values, out T result)
{
    Cardinality cardinality;
    (cardinality, result) =
        values.TrySingle(Cardinality.Zero,
                         Cardinality.One,
                         Cardinality.Many,
                         ValueTuple.Create);
    return cardinality;
}

@MkazemAkhgary
Copy link

MkazemAkhgary commented Jan 3, 2019

very nice suggestion, but I think Cardinality is overkill. what about this?

public static IEnumerable<T> ExtractFirst<T>(this IEnumerable<T> values, out T result)
{
    result = default;

    using (var enumerator = values.GetEnumerator())
    {
        if (enumerator.MoveNext())
        {
            result = enumerator.Current;
            return GetRest();
        }
        return null;

        IEnumerable<T> GetRest()
        {
            while (enumerator.MoveNext())
            {
                yield return enumerator.Current;
            }
        }
    }
}

If null is returned, source contains no element.
If returned enumerable is empty, source contains single element.

returned enumerable is like source.Skip(1) but that one skipped element is the out parameter.

alternative signature:

public static T ExtractFirst<T>(this IEnumerable<T> values, out IEnumerable<T> rest);
public static bool ExtractFirst<T>(this IEnumerable<T> values, out T first, out IEnumerable<T> rest);

The last signature works nicely with ?: operator.

source.ExtractFirst(out var first, out var rest)
      ? // work with first and rest
      : Enumerable.Empty<T>();

@atifaziz
Copy link
Member

atifaziz commented Jan 4, 2019

@MkazemAkhgary Your ExtractFirst implementation (which appears to be CADR-like) has two major flaws:

  1. It disposes the enumerator.
  2. It changes the enumerator into an enumerable when they're not the same thing.

If null is returned, source contains no element.
If returned enumerable is empty, source contains single element.

If these are the rules, then it suffices to have something like this:

public static IEnumerator<T> TryMoveToFirst<T>(this IEnumerable<T> values)
{
    var enumerator = values.GetEnumerator();
    try
    {
        return enumerator.MoveNext() ? enumerator : null;
    }
    catch
    {
        enumerator.Dispose();
        throw;
    }
}

The Current property of the returned enumerator will contain the first element so you don't need a separate out-argument to capture it. Disposing the returned enumerator will be the caller's responsibility.

@MkazemAkhgary
Copy link

MkazemAkhgary commented Jan 4, 2019

Good point on disposed enumerator. I totally missed that. apart from that changing enumerator to enumerable may cause underlying enumerator to never get disposed if the enumerable is not consumed completely.

My intention how ever was to make the syntax as short as possible. having to dispose the enumerator on caller makes some clutter.

I have to admit that if c# evolves some pattern matching in future (switch expressions) then usage with Cardinality enum would be nice and clean.


This one has limited usage but it fixes the problems that you mentioned.

public static bool ExtractFirst<T>(this IEnumerable<T> values, out T first, out T[] rest);
public static bool ExtractFirst<T>(this IEnumerable<T> values, int restLength, out T first, out T[] rest);

@atifaziz atifaziz changed the title TrySingle? TrySingle : xs → Zero | (x, One) | (x, Many) Feb 9, 2019
@atifaziz
Copy link
Member

#590 could address this too and since there's been no further follow up from @jimmcslim, I'm going to close this issue.

@jimmcslim jimmcslim mentioned this issue Oct 8, 2019
@atifaziz
Copy link
Member

atifaziz commented Oct 8, 2019

Re-opening this issue now that there is a PR (#612) open for it.

@atifaziz atifaziz reopened this Oct 8, 2019
@atifaziz atifaziz added this to the vNext milestone Oct 17, 2019
@atifaziz atifaziz modified the milestones: vNext, 3.3.0 Oct 24, 2019
@atifaziz atifaziz changed the title TrySingle : xs → Zero | (x, One) | (x, Many) TrySingle : xs → Zero | (x, One) | Many Oct 28, 2019
@atifaziz
Copy link
Member

atifaziz commented Dec 13, 2019

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants