-
Notifications
You must be signed in to change notification settings - Fork 415
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
Unfold/Disaggregate #246
Comments
It would be great if we could use a type with better member names that class UnfoldMemo<TState, TResult> {
public TState State { get; }
public TResult Result { get; }
public UnfoldMemo(TState state, TResult result) {
State = state;
Result = result;
}
}
static class UnfoldMemo {
public static UnfoldMemo<TState, TResult>Create<TState, TResult>(TState state, TResult result) => new UnfoldMemo(state, result);
} |
Or make it completely generic so you can build on top and use anonymous types, tuples and what not. I would go with the following signature: public static IEnumerable<TResult> Unfold<TState, T, TResult>(
TState state,
Func<TState, T> generator,
Func<T, bool> predicate,
Func<T, TState> stateSelector,
Func<T, TResult> resultSelector) Usage-wise, your examples would become: var naturalNumbers =
Unfold(0, x => new { x, s = x + 1 }, _ => true, e => e.s, e => e.x);
var naturalNumbersAsString =
Unfold(0, x => new { x, s = x + 1 }, _ => true, e => e.s, e => e.x.ToString());
var squaresLowerThan100 =
Unfold(1, x => new { x = x * x, s = x + 1 }, e => e.x < 100, e => e.s, e => e.x); Granted they are longer but there's one benefit in the last case of Anyway, if you add an overload using public static IEnumerable<T> Unfold<TState, T>(
TState state, Func<TState, Tuple<T, TState>> generator) =>
Unfold(state, generator, e => e != null, e => e.Item2, e => e.Item1); then your examples would be as you proposed them: var naturalNumbers = Unfold(0, n => Tuple.Create(n, n + 1));
var naturalNumbersAsString = Unfold(0, n => Tuple.Create(n.ToString(), n + 1));
var squaresLowerThan100 = Unfold(1, n => (n * n) < 100 ? Tuple.Create(n * n, n + 1) : null); Here's the one for Fibonacci series: var fibonacciNumbersLowerThan100 = // 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89
Unfold(new { x = 0, y = 1 },
s => new { r = s.x, x = s.y, y = s.x + s.y },
s => s.r < 100,
s => new { s.x, s.y },
s => s.r); Note that there's no I wonder, though, if we should add the public static IEnumerable<T> Unfold<TState, T>(
TState state, Func<TState, (T, TState)?> generator) =>
Unfold(state, generator, e => e != null, e => e.Value.Item2, e => e.Value.Item1); The Fibonacci series example will then read quite nice and short (the only ugly part being var fibonacciNumbersLowerThan100 = // 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89
Unfold((x: 0, y: 1), s => s.x < 100 ? (s.x, (s.y, s.x + s.y)) : ((int, (int, int))?) null); |
As for name, I would go with either |
I really like this signature and its use! Although user writes more, it is of course cleaner.
in the original signature we can simple do
I wondered the same. I think is valid create Ah, one question: how a user that uses Visual Studio with C# 6 would see the
Can't we simple return
Between these 2, I prefer |
Yes, of course, technically you can but then you introduce a statement lambda as the optimal path. Perhaps if declaration expressions ever make it back into C# then this point will be mute.
They will see them as
The problem here is the conditional operator ( var fibonacciNumbersLowerThan100 = // 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89
Unfold((x: 0, y: 1), s => s.x < 100 ? ((int, (int, int))?) (s.x, (s.y, s.x + s.y)) : null); I'd rather add the ceremony to the far end and next to Name-wise |
I'm actually surprised I've been using MoreLINQ for years and I had never noticed Fold 😮 . Apparently using I think striving for consistence with other ecosystems is a good idea, so my vote would be for Maybe Fold could be renamed (for 3.0), so that the confusion disappears? I googled for names relating to what this method does, and I didn't find anything. Python supports casting lists to tuples natively, haskell and f# don't appear to have a method for it. |
F# doesn't need it because it has pattern matching for lists and arrays. You could look at var csv = @"1,2,3
4,5,6
7,8,9";
var q =
from s in csv.Split('\n')
select s.Trim()
.Split(',')
.Select(int.Parse)
.Fold((x, y, z) => x + y * z) Isn't |
Technically While we define if |
Yes, go for it.
They do the same thing. The |
@leandromoh I did a very rough implementation in LINQPad earlier to test out some ideas, so throwing that in here in case you can use any bits for your PR (don't mean to jump the gun)… static partial class MoreEnumerable
{
// Following overload requires C# 7
public static IEnumerable<T> Unfold<TState, T>(
TState state, Func<TState, (T, TState)?> generator) =>
Unfold(state, generator, e => e != null, e => e.Value.Item2, e => e.Value.Item1);
public static IEnumerable<T> Unfold<TState, T>(
TState state, Func<TState, Tuple<T, TState>> generator) =>
Unfold(state, generator, e => e != null, e => e.Item2, e => e.Item1);
public static IEnumerable<TResult> Unfold<TState, T, TResult>(
TState state,
Func<TState, T> generator,
Func<T, bool> predicate,
Func<T, TState> stateSelector,
Func<T, TResult> resultSelector)
{
while (true)
{
var step = generator(state);
if (!predicate(step))
break;
yield return resultSelector(step);
state = stateSelector(step);
}
}
} Since the seal's broken, all warranties are void of course. 😆 |
@atifaziz Why do you call it "very rough implementation"? My draft was exactly like yours 😆 |
@leandromoh Well, there's only so many ways in which you can implement something as simple as |
I see. Thanks for the explanation @atifaziz. The PR was submitted. |
@leandromoh @fsateler I'm scheduling this for 2.4.0 which is presently slated for end of April (the minor versions for 2.0 are lining up nicely with month numbers so far for this year 😄). If we can wrap up #260 today then I'll consider it for 2.3.0 that's scheduled for this Friday. |
Unfold
function (present and many functional languages like Haskell and F#), is a dual tofold/aggregate
.Unfold
builds a list from a seed value. It takes a function which either returnsnull
if it is done producing the list, or returns the tuple(x, y)
,x
which is prepended to the list, andy
is used as the next element in the recursive call.x
andy
may be of different types.Unfold
signature is:IEnumerable<TResult> UnFold<TSource, TResult>(TSource seed, Func<TSource, Tuple<TResult, TSource>> generator)
Examples of usage:
for some cases, the following overload may also be usefull/readable. it bassically split the function into 3 functions:
IEnumerable<TResult> UnFold<TSource, TResult>(TSource seed, Func<TSource, bool> predicate, Func<TSource, TResult> resultGenerator, Func<TSource, TSource> seedGenerator)
for example, we can do
var fibonacciNumbersLowerThan100 = UnFold(Tuple.Create(0, 1), n => n.Item1 < 100, n => n.Item1, n => Tuple.Create(n.Item2, n.Item1 + n.Item2));
instead of
var fibonacciNumbersLowerThan100 = UnFold(Tuple.Create(0, 1), n => n.Item1 < 100 ? Tuple.Create(n.Item1, Tuple.Create(n.Item2, n.Item1 + n.Item2)) : null);
I'm planning to submit a PR for this.
What do you think?
The text was updated successfully, but these errors were encountered: