Jump to page: 1 26  
Page
Thread overview
"fold": a replacement for "reduce"
Mar 25, 2014
monarch_dodra
Mar 25, 2014
Graham Fawcett
Mar 25, 2014
monarch_dodra
Mar 25, 2014
Meta
Mar 25, 2014
monarch_dodra
Mar 25, 2014
bearophile
Mar 25, 2014
Nordlöw
Mar 25, 2014
Nordlöw
Mar 25, 2014
monarch_dodra
Mar 25, 2014
Nordlöw
Mar 25, 2014
monarch_dodra
Mar 25, 2014
Nordlöw
Mar 25, 2014
monarch_dodra
Mar 25, 2014
Nordlöw
Mar 26, 2014
Simen Kjærås
Mar 26, 2014
monarch_dodra
Mar 26, 2014
Nordlöw
Mar 27, 2014
Jakob Ovrum
Mar 27, 2014
Marc Schütz
Mar 27, 2014
monarch_dodra
Mar 27, 2014
Simen Kjærås
Mar 27, 2014
monarch_dodra
Mar 27, 2014
Brian Rogoff
Mar 27, 2014
monarch_dodra
Mar 27, 2014
Brian Rogoff
Mar 27, 2014
Simen Kjærås
Mar 27, 2014
Timon Gehr
Mar 28, 2014
monarch_dodra
Mar 27, 2014
Meta
Mar 27, 2014
monarch_dodra
Mar 27, 2014
Simen Kjærås
Mar 27, 2014
monarch_dodra
Mar 27, 2014
Meta
Mar 27, 2014
Simen Kjærås
Mar 27, 2014
Meta
Mar 27, 2014
Meta
Mar 27, 2014
Simen Kjærås
Mar 27, 2014
Meta
Mar 27, 2014
Timon Gehr
Mar 28, 2014
Meta
Mar 28, 2014
Timon Gehr
Mar 29, 2014
Meta
Mar 29, 2014
Meta
Mar 29, 2014
Timon Gehr
Mar 27, 2014
Simen Kjærås
Mar 30, 2014
bearophile
Mar 31, 2014
monarch_dodra
Mar 31, 2014
bearophile
Mar 31, 2014
monarch_dodra
Mar 31, 2014
bearophile
Mar 31, 2014
monarch_dodra
Mar 31, 2014
bearophile
March 25, 2014
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
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
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
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
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
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
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
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
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
> 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?
« First   ‹ Prev
1 2 3 4 5 6