Thread overview | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
March 25, 2014 "fold": a replacement for "reduce" | ||||
---|---|---|---|---|
| ||||
I'm working on something called "fold". It is designed as nothing more than a replacement for "reduce", but where the seed comes *second* in terms of arguments. It allows this: someLongUFCSChain().reduce(intoThis); It might not look like this, but it is a *constant* source of nuisance. In particular, chains that start out like this: someLongUFCSChain().reduce(); Need to be changed into this to add a seed: reduce(someLongUFCSChain(), intoThis); After a couple of tries to "swap" the arguments, it was observed that it simply couldn't be done wihthout silent run-time breakage. So that was not acceptable. The solution: Re-introduce "reduce" under a new name: "fold". Simple as that. -------- I'm taking this naming-changing event as an opportunity to "cleanup" reduce too. One thing that gets on my nerves is that "range.reduce()" is not nothrow, because it throws an exception if the range is empty. I think this is wrong. popping an empty range is an *Error*, and should be validated by the programmer. Because of this, it is currently not possible to use "reduce(range)" in nothrow context. This however, even with a name change, it *is* change of behavior. Do you feel this is acceptable? Do you want this change at all? Or do you think an Exception is fine? |
March 25, 2014 Re: "fold": a replacement for "reduce" | ||||
---|---|---|---|---|
| ||||
Posted in reply to monarch_dodra | On Tuesday, 25 March 2014 at 17:22:45 UTC, monarch_dodra wrote: > I'm working on something called "fold". It is designed as nothing more than a replacement for "reduce", but where the seed comes *second* in terms of arguments. It allows this: > > someLongUFCSChain().reduce(intoThis); > > It might not look like this, but it is a *constant* source of nuisance. In particular, chains that start out like this: > someLongUFCSChain().reduce(); > Need to be changed into this to add a seed: > reduce(someLongUFCSChain(), intoThis); > > After a couple of tries to "swap" the arguments, it was observed that it simply couldn't be done wihthout silent run-time breakage. So that was not acceptable. > > The solution: Re-introduce "reduce" under a new name: "fold". > Simple as that. > > -------- > > I'm taking this naming-changing event as an opportunity to "cleanup" reduce too. One thing that gets on my nerves is that "range.reduce()" is not nothrow, because it throws an exception if the range is empty. > > I think this is wrong. popping an empty range is an *Error*, and should be validated by the programmer. Because of this, it is currently not possible to use "reduce(range)" in nothrow context. > > This however, even with a name change, it *is* change of behavior. Do you feel this is acceptable? Do you want this change at all? Or do you think an Exception is fine? My knee-jerk observation is that the documentation for 'fold' should indicate that it's a left fold, i.e., the sequence of operations associates to the left (in other words, it's sequence-iterative, not sequence-recursive). It's a small thing, but it might help Haskellers and Schemers to orient themselves. http://srfi.schemers.org/srfi-1/srfi-1.html#fold http://srfi.schemers.org/srfi-1/srfi-1.html#fold-right Graham |
March 25, 2014 Re: "fold": a replacement for "reduce" | ||||
---|---|---|---|---|
| ||||
Posted in reply to monarch_dodra | On Tuesday, 25 March 2014 at 17:22:45 UTC, monarch_dodra wrote:
> I'm working on something called "fold". It is designed as nothing more than a replacement for "reduce", but where the seed comes *second* in terms of arguments. It allows this:
>
> someLongUFCSChain().reduce(intoThis);
>
> It might not look like this, but it is a *constant* source of nuisance. In particular, chains that start out like this:
> someLongUFCSChain().reduce();
> Need to be changed into this to add a seed:
> reduce(someLongUFCSChain(), intoThis);
>
> After a couple of tries to "swap" the arguments, it was observed that it simply couldn't be done wihthout silent run-time breakage. So that was not acceptable.
>
> The solution: Re-introduce "reduce" under a new name: "fold".
> Simple as that.
>
> --------
>
> I'm taking this naming-changing event as an opportunity to "cleanup" reduce too. One thing that gets on my nerves is that "range.reduce()" is not nothrow, because it throws an exception if the range is empty.
>
> I think this is wrong. popping an empty range is an *Error*, and should be validated by the programmer. Because of this, it is currently not possible to use "reduce(range)" in nothrow context.
>
> This however, even with a name change, it *is* change of behavior. Do you feel this is acceptable? Do you want this change at all? Or do you think an Exception is fine?
Sounds to me like the fact that it throws an Exception instead of an Error is a leftover from the earlier days of ranges, when it wasn't clear what one should do in the case of an empty range. I think it's well worth making fold nothrow, and it will simplify calling code in the case where the callee wrapped reduce in a try-catch block.
|
March 25, 2014 Re: "fold": a replacement for "reduce" | ||||
---|---|---|---|---|
| ||||
Posted in reply to monarch_dodra | monarch_dodra: > I'm taking this naming-changing event as an opportunity to "cleanup" reduce too. One thing that gets on my nerves is that "range.reduce()" is not nothrow, because it throws an exception if the range is empty. > > I think this is wrong. popping an empty range is an *Error*, and should be validated by the programmer. Because of this, it is currently not possible to use "reduce(range)" in nothrow context. This Haskell library shows one way Haskellers use to face similar problems: http://hackage.haskell.org/package/safe-0.3.4/docs/Safe.html Every function that could have similar problems is present in four forms, with the "Note", "Def", "May" and "Safe" suffixes. An example with the standard Haskell function "tail", that is similar to dropOne of std.range (skips the first and returns the rest. The problem is when the input list is empty): tailDef :: [a] -> [a] -> [a] tailDef [12] [] = [12] tailDef [12] [1,3,4] = [3,4] tailMay :: [a] -> Maybe [a] tailMay [] = Nothing tailMay [1,3,4] = Just [3,4] tailNote :: String -> [a] -> [a] tail "help me" [] = error "Pattern match failure, tail [], help me" tail "help me" [1,3,4] = [3,4] tailSafe :: [a] -> [a] tailSafe [] = [] tailSafe [1,3,4] = [3,4] Bye, bearophile |
March 25, 2014 Re: "fold": a replacement for "reduce" | ||||
---|---|---|---|---|
| ||||
Posted in reply to Graham Fawcett | On Tuesday, 25 March 2014 at 18:02:49 UTC, Graham Fawcett wrote:
> My knee-jerk observation is that the documentation for 'fold' should indicate that it's a left fold, i.e., the sequence of operations associates to the left (in other words, it's sequence-iterative, not sequence-recursive). It's a small thing, but it might help Haskellers and Schemers to orient themselves.
>
> http://srfi.schemers.org/srfi-1/srfi-1.html#fold
> http://srfi.schemers.org/srfi-1/srfi-1.html#fold-right
>
> Graham
Will be noted.
|
March 25, 2014 Re: "fold": a replacement for "reduce" | ||||
---|---|---|---|---|
| ||||
Posted in reply to Meta | On Tuesday, 25 March 2014 at 18:21:25 UTC, Meta wrote:
> Sounds to me like the fact that it throws an Exception instead of an Error is a leftover from the earlier days of ranges, when it wasn't clear what one should do in the case of an empty range. I think it's well worth making fold nothrow, and it will simplify calling code in the case where the callee wrapped reduce in a try-catch block.
Well, 1 thing I just thought of is that if you are operating on a "non-range iterable": Then it may not be possible to actually know if the iterable will loop more than once. So in this case, we'd have to keep the enforce for iterables...
|
March 25, 2014 Re: "fold": a replacement for "reduce" | ||||
---|---|---|---|---|
| ||||
Posted in reply to monarch_dodra | On Tuesday, 25 March 2014 at 17:22:45 UTC, monarch_dodra wrote:
> I'm working on something called "fold". It is designed as nothing more than a replacement for "reduce", but where the seed comes *second* in terms of arguments. It allows this:
>
> someLongUFCSChain().reduce(intoThis);
Good! I've been greatly disturbed by this too.
Especially by the fact that reduce throws upon empty input because it currently has no way of deducing return value. This can be a greatly annoying "surprise" to new users.
From algebra we learn the following relations:
Operator Unit
+,(-) 0
*,(/) 1
min T.max
max T.min
..
I guess typeof return value could be deduced according + and * at least.
Have you perhaps found a way to deduce it in the general case using the reduce function and ElementType of input range, something like
typeof(binaryFun(E, E))
where
alias E = ElementType!R,
I thought about adding an overload for reduce using template restriction that would solve the problem without adding a new function. But I guess that adding such an overload could cause just as much confusion to programmers who are used to the current syntax.
|
March 25, 2014 Re: "fold": a replacement for "reduce" | ||||
---|---|---|---|---|
| ||||
Posted in reply to Nordlöw | Correction:
> I guess typeof return value could be deduced according + and * at least.
Return type is of course obvious, value is not...
|
March 25, 2014 Re: "fold": a replacement for "reduce" | ||||
---|---|---|---|---|
| ||||
Posted in reply to Nordlöw | On Tuesday, 25 March 2014 at 20:37:31 UTC, Nordlöw wrote:
> Correction:
>
>> I guess typeof return value could be deduced according + and * at least.
>
> Return type is of course obvious, value is not...
The return type is not actually obvious. You'd think:
alias S = typeof(fun(r.front, r.front));
S s;
Which is a good start. But then
is (S == typeof(fun(s, r.front))) ?
You have no guarantees that you'll have "stability" of the returned type.
More often than not, it *is* stable, but there are cases where it is not.
This is not a huge problem, because the consequence is simply that it fails to compile. We can simply catch it, and tell the user to provide an explicit seed, so as to help out the function.
But as you said, for the value, the is simply no correct initial value, unless you know the "identity" value of your operator. But even then, that value makes no sense is the range is empty.
|
March 25, 2014 Re: "fold": a replacement for "reduce" | ||||
---|---|---|---|---|
| ||||
Posted in reply to monarch_dodra | > You have no guarantees that you'll have "stability" of the returned type. What do you mean with "stability" of the return type? > More often than not, it *is* stable, but there are cases where it is not. Could you gives examples of when it's stable and when it's not? |
Copyright © 1999-2021 by the D Language Foundation