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

.toggle is hard to understand #2089

Open
zoffixznet opened this Issue Jul 17, 2018 · 14 comments

Comments

Projects
None yet
6 participants
@zoffixznet
Contributor

zoffixznet commented Jul 17, 2018

Today, for more than an hour people tried to understand .toggle. I thought twice I finally got it, but failed miserably, because what the toggles do highly depends on the data being processed.

The docs I wrote awhile back, are like a page and a half long.

This makes me think we should simplify this method while we can (it's a 6.d proposal). I think it should be split into two separate methods that function like .while and .until. Those names were already rejected for good reasons but we can come up with some other names.

<raschipi> I see, so it's not a toggle?
[...]
<perlpilot> See? that's kind of my only objection here: .toggle is too inscrutable.
[...]
<Zoffix> you can understand ff without knowing what the data is. 2 ff 5 will give you the all the items between the first 2 and the first 5. With .toggle: * == 2, * == 5 you can't say that. You must know the positions of 2 and 5 in the list. you might get all the data after the first 5, you might get the 2, and then all the data after the first 5, or you might just get the data after the first 5

@perlpilot

This comment has been minimized.

Show comment
Hide comment
@perlpilot

perlpilot Jul 17, 2018

Contributor

Random observation (if .toggle is going to stay around). ff and fff are "one time toggles" but they both start in the "off" state. Maybe .toggle should also start in the "off" state?

As far as documentation is concerned, equivalences between .toggle and ff could be shown in order to leverage existing documentation to aid in understanding. Though it breaks down when you have multiple conditions in the .toggle.

Contributor

perlpilot commented Jul 17, 2018

Random observation (if .toggle is going to stay around). ff and fff are "one time toggles" but they both start in the "off" state. Maybe .toggle should also start in the "off" state?

As far as documentation is concerned, equivalences between .toggle and ff could be shown in order to leverage existing documentation to aid in understanding. Though it breaks down when you have multiple conditions in the .toggle.

@lizmat

This comment has been minimized.

Show comment
Hide comment
@lizmat

lizmat Jul 17, 2018

Contributor

FWIW, coming back to it later, I find myself always having to look at the documentation :-(

So maybe we need to re-think .toggle :-(

Contributor

lizmat commented Jul 17, 2018

FWIW, coming back to it later, I find myself always having to look at the documentation :-(

So maybe we need to re-think .toggle :-(

@FCO

This comment has been minimized.

Show comment
Hide comment
@FCO

FCO Jul 17, 2018

Member

what about something like.take-while, .ignore-while, .take-until and ignore-until?

Member

FCO commented Jul 17, 2018

what about something like.take-while, .ignore-while, .take-until and ignore-until?

@0racle

This comment has been minimized.

Show comment
Hide comment
@0racle

0racle Jul 17, 2018

Contributor

I like toggle. Though, I suppose I have only ever used it as a take-while or drop-while... and never really used it to toggle on and off multiple times.

My mnemonic is: "The 't' in toggle stands for 'take-while'". That is, if toggle took one Callable, you might call it take-while. However once it's in the :off position, it becomes a drop-while.

I like that it takes the 2 separate functions of take-while and drop-while and generalises them into one function. Of course, the extra 2 functions of take-until and drop-until are in there too if you negate your condition.

Maybe the name is confusing to people? Maybe it should be documented more clearly that it's just a generalisation of (drop|take)-while? Maybe people find the multiple on/off support confusing. However, it's the chaining of conditionals that lets you do things that a chain of calls to take-while and drop-while methods couldn't, ie.

> say (flat (0 ... 10, 9 ... 1) xx 4).toggle((* < 5, * < 2) xx *) 
(0 1 2 3 4 1 0 1 2 3 4 1 0 1 2 3 4 1 0 1 2 3 4 1)

But, apart from that silly example, I don't have a good showcase of when that chaining would be useful.

Contributor

0racle commented Jul 17, 2018

I like toggle. Though, I suppose I have only ever used it as a take-while or drop-while... and never really used it to toggle on and off multiple times.

My mnemonic is: "The 't' in toggle stands for 'take-while'". That is, if toggle took one Callable, you might call it take-while. However once it's in the :off position, it becomes a drop-while.

I like that it takes the 2 separate functions of take-while and drop-while and generalises them into one function. Of course, the extra 2 functions of take-until and drop-until are in there too if you negate your condition.

Maybe the name is confusing to people? Maybe it should be documented more clearly that it's just a generalisation of (drop|take)-while? Maybe people find the multiple on/off support confusing. However, it's the chaining of conditionals that lets you do things that a chain of calls to take-while and drop-while methods couldn't, ie.

> say (flat (0 ... 10, 9 ... 1) xx 4).toggle((* < 5, * < 2) xx *) 
(0 1 2 3 4 1 0 1 2 3 4 1 0 1 2 3 4 1 0 1 2 3 4 1)

But, apart from that silly example, I don't have a good showcase of when that chaining would be useful.

@zoffixznet

This comment has been minimized.

Show comment
Hide comment
@zoffixznet

zoffixznet Jul 17, 2018

Contributor

2 separate functions of take-while and drop-while and generalises them into one function [...] when that chaining would be useful.

That chaining is affected by the data though, which is what I think makes the routine problematic. Following your description, I assume the * == 2 below is a .drop-while? Yet depending on the data, it either causes the trailing elements to be dropped or kept:

<Zoffix_> m: .say for (2, 2, 4, 5, 6).toggle: * == 1, * == 2
<camelia> rakudo-moar b30800c8e: OUTPUT: «2␤4␤5␤6␤»
<Zoffix_> m: .say for (1, 2, 4, 5, 6).toggle: * == 1, * == 2
<camelia> rakudo-moar b30800c8e: OUTPUT: «1␤»
Contributor

zoffixznet commented Jul 17, 2018

2 separate functions of take-while and drop-while and generalises them into one function [...] when that chaining would be useful.

That chaining is affected by the data though, which is what I think makes the routine problematic. Following your description, I assume the * == 2 below is a .drop-while? Yet depending on the data, it either causes the trailing elements to be dropped or kept:

<Zoffix_> m: .say for (2, 2, 4, 5, 6).toggle: * == 1, * == 2
<camelia> rakudo-moar b30800c8e: OUTPUT: «2␤4␤5␤6␤»
<Zoffix_> m: .say for (1, 2, 4, 5, 6).toggle: * == 1, * == 2
<camelia> rakudo-moar b30800c8e: OUTPUT: «1␤»
@perlpilot

This comment has been minimized.

Show comment
Hide comment
@perlpilot

perlpilot Jul 18, 2018

Contributor

I keep wanting .toggle to work more like ff and fff for some reason. Something like:

 say ^50 .toggle: * > 3, * > 9, * > 16, * == 25 , * > 30, * > 35;

would return (4,5,6,7,8,9,10,17,18,19,20,21,22,23,24,25,31,32,33,34,35,36)

That is, it would start in an "off" state and remain that way until * > 3 is true, then it will be in the "on" state and remain that way until * > 9 is true, then it will enter the "off" state again and stay that way until * > 16 is true, then it will flip to the "on" state again and stay that way until * == 25 is true ... etc. (with appropriate adverbs to include/exclude end points ... maybe)

Contributor

perlpilot commented Jul 18, 2018

I keep wanting .toggle to work more like ff and fff for some reason. Something like:

 say ^50 .toggle: * > 3, * > 9, * > 16, * == 25 , * > 30, * > 35;

would return (4,5,6,7,8,9,10,17,18,19,20,21,22,23,24,25,31,32,33,34,35,36)

That is, it would start in an "off" state and remain that way until * > 3 is true, then it will be in the "on" state and remain that way until * > 9 is true, then it will enter the "off" state again and stay that way until * > 16 is true, then it will flip to the "on" state again and stay that way until * == 25 is true ... etc. (with appropriate adverbs to include/exclude end points ... maybe)

@0racle

This comment has been minimized.

Show comment
Hide comment
@0racle

0racle Jul 18, 2018

Contributor

I assume the * == 2 below is a .drop-while? Yet depending on the data, it either causes the trailing elements to be dropped or kept

Admittedly, a chain of keep-while and drop-while is semantically different to toggle. The key is the name: The conditional's trip the toggle on or off, and they only trip once.

say (2, 2, 4, 5, 6).toggle: * == 1, * == 2;
# OUTPUT: (2 4 5 6)

The toggle is on. 2 == 1 is False, so the value is dropped, and the toggle is tripped off.
The next conditional 2 == 2 is True. The value is kept, the toggle trips back on on.
The rest of the values flow through.

say (1, 2, 4, 5, 6).toggle: * == 1, * == 2;
# OUTPUT: (1)

The toggle is on, the first conditional is True, value is taken.
The next conditional 2 == 1 is False, the value is dropped, the toggle trips off.
The next conditional is False for the remaining values, so the toggle is never tripped on again.

I keep wanting .toggle to work more like ff and fff for some reason

I can see the desire, but maybe toggle is the wrong description of what you want. Perhaps a flipflop method on Iterables is required (hah! more methods is just what we need!)

What you want is possible with toggle, but you need to conform to how the toggle works

say (^50).toggle(:off, * > 3, * < 11, * > 16, * < 26, * > 30, * < 37);
# OUTPUT: (4 5 6 7 8 9 10 17 18 19 20 21 22 23 24 25 31 32 33 34 35 36)

All that not withstanding, I will admit that the semantics of toggle can be confusing. You have to essentially keep a state in your head of which position the toggle is in, so you know whether you need a True or False condition to trip it.

EDIT: Just to clarify that statement: 'on' toggles require a False condition to trip them off, and 'off' toggles required a True condition to trip them on. To further reiterate that...

say (^50).toggle(:off, * > 3, not * > 10, * > 16, not * > 25, * > 30, not * > 36)
# OUTPUT: (4 5 6 7 8 9 10 17 18 19 20 21 22 23 24 25 31 32 33 34 35 36)
Contributor

0racle commented Jul 18, 2018

I assume the * == 2 below is a .drop-while? Yet depending on the data, it either causes the trailing elements to be dropped or kept

Admittedly, a chain of keep-while and drop-while is semantically different to toggle. The key is the name: The conditional's trip the toggle on or off, and they only trip once.

say (2, 2, 4, 5, 6).toggle: * == 1, * == 2;
# OUTPUT: (2 4 5 6)

The toggle is on. 2 == 1 is False, so the value is dropped, and the toggle is tripped off.
The next conditional 2 == 2 is True. The value is kept, the toggle trips back on on.
The rest of the values flow through.

say (1, 2, 4, 5, 6).toggle: * == 1, * == 2;
# OUTPUT: (1)

The toggle is on, the first conditional is True, value is taken.
The next conditional 2 == 1 is False, the value is dropped, the toggle trips off.
The next conditional is False for the remaining values, so the toggle is never tripped on again.

I keep wanting .toggle to work more like ff and fff for some reason

I can see the desire, but maybe toggle is the wrong description of what you want. Perhaps a flipflop method on Iterables is required (hah! more methods is just what we need!)

What you want is possible with toggle, but you need to conform to how the toggle works

say (^50).toggle(:off, * > 3, * < 11, * > 16, * < 26, * > 30, * < 37);
# OUTPUT: (4 5 6 7 8 9 10 17 18 19 20 21 22 23 24 25 31 32 33 34 35 36)

All that not withstanding, I will admit that the semantics of toggle can be confusing. You have to essentially keep a state in your head of which position the toggle is in, so you know whether you need a True or False condition to trip it.

EDIT: Just to clarify that statement: 'on' toggles require a False condition to trip them off, and 'off' toggles required a True condition to trip them on. To further reiterate that...

say (^50).toggle(:off, * > 3, not * > 10, * > 16, not * > 25, * > 30, not * > 36)
# OUTPUT: (4 5 6 7 8 9 10 17 18 19 20 21 22 23 24 25 31 32 33 34 35 36)
@zoffixznet

This comment has been minimized.

Show comment
Hide comment
@zoffixznet

zoffixznet Jul 19, 2018

Contributor

.oO( .pull-until / .skip-until / .pull-while / .skip-while )

Contributor

zoffixznet commented Jul 19, 2018

.oO( .pull-until / .skip-until / .pull-while / .skip-while )

@zoffixznet

This comment has been minimized.

Show comment
Hide comment
@zoffixznet

zoffixznet Jul 19, 2018

Contributor

OK, seeing on IRC there are now a group of 6 (!) replacement methods being proposed without a solid any usecase in mind, I think an easy way forward is to just KISS:

  • Remove the slurpy candidate of .toggle
  • Reject R#2088 for the time being, to leave room open for re-adding slurpy candidate in the future, should we come up with some good semantics
  • That's it

So .toggle is always to be called with one Callable. If you want more than one toggle chain more than one .toggle call.

Contributor

zoffixznet commented Jul 19, 2018

OK, seeing on IRC there are now a group of 6 (!) replacement methods being proposed without a solid any usecase in mind, I think an easy way forward is to just KISS:

  • Remove the slurpy candidate of .toggle
  • Reject R#2088 for the time being, to leave room open for re-adding slurpy candidate in the future, should we come up with some good semantics
  • That's it

So .toggle is always to be called with one Callable. If you want more than one toggle chain more than one .toggle call.

@0racle

This comment has been minimized.

Show comment
Hide comment
@0racle

0racle Jul 19, 2018

Contributor

If you want more than one toggle chain more than one .toggle call

Well not exactly. Once the first toggle is off, the second toggle won't see anything... so you can't do stuff like (1..100).toggle(* < 10, * > 20, * < 30)... but I guess the question is... is that a legitimate use-case? I'll come back to that.

I agree that adding more methods is probably not necessary. I haven't been tracking the IRC conversations, but I'd hazard a guess most of those 6 replacement methods are <take drop> X~ <while until>. I think the main strength of toggle is it generalises all those into one method.

As for gutting .toggle current functionality down to one Callable, hrm... I don't like it. I know there are not many use-cases for it, but I did find one example in my code where I'm using it.

I needed a prime-range function (similar to Python's range but would returns prime numbers in the range). I already had a sub for generating primes, so I implemented prime-range like this.

sub prime-range($a, $b) {
    primes().toggle(:off, * ≥ $a, * < $b) 
}

This, however, kind of supports Zoffix's suggestion, because I could do this instead

sub prime-range($a, $b) {
    primes().toggle(:off, * ≥ $a).toggle(* < $b)
}

In summary, while not everything .toggle can currently do could be implemented with a chain of .toggle's - and despite the fact that I don't like the idea of removing functionality - I think we'd need to see legitimate use-cases for multiple Callables to justify keeping the existing functionality in 6.d.

Contributor

0racle commented Jul 19, 2018

If you want more than one toggle chain more than one .toggle call

Well not exactly. Once the first toggle is off, the second toggle won't see anything... so you can't do stuff like (1..100).toggle(* < 10, * > 20, * < 30)... but I guess the question is... is that a legitimate use-case? I'll come back to that.

I agree that adding more methods is probably not necessary. I haven't been tracking the IRC conversations, but I'd hazard a guess most of those 6 replacement methods are <take drop> X~ <while until>. I think the main strength of toggle is it generalises all those into one method.

As for gutting .toggle current functionality down to one Callable, hrm... I don't like it. I know there are not many use-cases for it, but I did find one example in my code where I'm using it.

I needed a prime-range function (similar to Python's range but would returns prime numbers in the range). I already had a sub for generating primes, so I implemented prime-range like this.

sub prime-range($a, $b) {
    primes().toggle(:off, * ≥ $a, * < $b) 
}

This, however, kind of supports Zoffix's suggestion, because I could do this instead

sub prime-range($a, $b) {
    primes().toggle(:off, * ≥ $a).toggle(* < $b)
}

In summary, while not everything .toggle can currently do could be implemented with a chain of .toggle's - and despite the fact that I don't like the idea of removing functionality - I think we'd need to see legitimate use-cases for multiple Callables to justify keeping the existing functionality in 6.d.

@zoffixznet

This comment has been minimized.

Show comment
Hide comment
@zoffixznet

zoffixznet Jul 19, 2018

Contributor

not everything .toggle can currently do could be implemented with a chain of .toggle's

Yeah, that's the point. We're not keeping all of the existing functionality. We're just keeping our options open for the future should this functionality ever have a ligitimate need (basically, avoiding what the Policy for Implementation of New Features calls Featuritis)

I don't like the idea of removing functionality

We're not really removing anything, since this method is not yet part of the language 😛

Contributor

zoffixznet commented Jul 19, 2018

not everything .toggle can currently do could be implemented with a chain of .toggle's

Yeah, that's the point. We're not keeping all of the existing functionality. We're just keeping our options open for the future should this functionality ever have a ligitimate need (basically, avoiding what the Policy for Implementation of New Features calls Featuritis)

I don't like the idea of removing functionality

We're not really removing anything, since this method is not yet part of the language 😛

@0racle

This comment has been minimized.

Show comment
Hide comment
@0racle

0racle Jul 19, 2018

Contributor

Featuritis

Indeed. In any event, I think in my last comments I managed to convince myself that reducing the arguments to one Callable (and an adverb) is a good idea (at least for now).

I'm giving this one my own personal thumbs up.

👍

Contributor

0racle commented Jul 19, 2018

Featuritis

Indeed. In any event, I think in my last comments I managed to convince myself that reducing the arguments to one Callable (and an adverb) is a good idea (at least for now).

I'm giving this one my own personal thumbs up.

👍

@FCO

This comment has been minimized.

Show comment
Hide comment
@FCO

FCO Jul 21, 2018

Member

I don’t know if it will help with anything, but I wrote a module implementing the 6 methods cited above: https://github.com/FCO/SeqSplitter

Member

FCO commented Jul 21, 2018

I don’t know if it will help with anything, but I wrote a module implementing the 6 methods cited above: https://github.com/FCO/SeqSplitter

@b2gills

This comment has been minimized.

Show comment
Hide comment
@b2gills

b2gills Aug 12, 2018

Contributor

I think toggle in its current form has the possibility to be something we regret enshrining in the Perl 6 spec.
As it will prevent major changes.

So it may be best to not include it in v6.d. Keep it Rakudo specific for now.

If at some point it gets whipped into shape then add it.

Contributor

b2gills commented Aug 12, 2018

I think toggle in its current form has the possibility to be something we regret enshrining in the Perl 6 spec.
As it will prevent major changes.

So it may be best to not include it in v6.d. Keep it Rakudo specific for now.

If at some point it gets whipped into shape then add it.

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