Skip to content

Instantly share code, notes, and snippets.

@greggirwin
Created June 4, 2019 19:52
Show Gist options
  • Save greggirwin/4dd6deb56dcba704bf6d418aff244373 to your computer and use it in GitHub Desktop.
Save greggirwin/4dd6deb56dcba704bf6d418aff244373 to your computer and use it in GitHub Desktop.
Red Incr/Decr
Re: WRT: func ['name content][]
WRT incr {
The most common case will be to change state, but incr does add meaning, and has
benefits, in my mind, when used with scalars.
- By default, there is no step value, so you can't get the + 1 part wrong.
- Incr is often used when looping, making loop variables stand out from other add
ops that might be in expressions.
- Incr generally means the step is small. Another mental hint.
- If we use a lit-word! param how do you use a computed incr arg?
- It's not a terrible thing to use lit-word! params. They are a tool. But they can
make you think a bit more, especially in mixed expressions. For incr that may
not be as much of an issue, but it may be used a lot, and that means that many
more cases where a non-evaluated arg isn't directly visible when reading code.
}
incr: function [
"Increments a value or series index"
value [scalar! series! any-word! any-path!] "If value is a word, it will refer to the incremented value"
/by "Change by this amount"
amount [scalar!]
][
amount: any [amount 1]
if integer? value [return add value amount] ;-- This speeds up our most common case by 4.5x
; though we are still 5x slower than just adding
; 1 to an int directly and doing nothing else.
; All this just to be smart about incrementing percents.
; The question is whether we want to do this, so the default 'incr
; call seems arguably nicer. But if we don't do this it is at
; least easy to explain.
if all [
integer? amount
1 = absolute amount
any [percent? value percent? attempt [get value]]
][amount: to percent! (1% * sign? amount)] ;-- % * int == float, so we cast.
case [
scalar? value [add value amount]
any [
any-word? value
any-path? value ;!! Check any-path before series.
][
op: either series? get value [:skip][:add]
set value op get value amount
:value ;-- Return the word for chaining calls.
]
series? value [skip value amount] ;!! Check series after any-path.
]
]
decr: function [
"Decrements a value or series index"
value [scalar! series! any-word! any-path!] "If value is a word, it will refer to the decremented value"
/by "Change by this amount"
amount [scalar!]
][
incr/by value negate any [amount 1]
]
w: 1
incr w
w
incr 'w
w
b: [1 2 3 x 4 y 5 z [6 7 8]]
incr b
b
incr 'b
b
incr 'b/1
b
incr 'b/x
b
incr/by 'b/y 2
b
incr 'b/z
b
incr 'b/z/1
b
o: object [i: 0 b: [a b c]]
incr 'o/i
incr 'o/b
o
v: 1.2.3
incr v
v
incr 'v
v
incr/by v 2.3.4
incr #"A"
incr 1:2:3
incr 1x2
incr 1%
decr 10%
i: 0
; i will now refer to 'i, just as if you'd done `i: 'i`
i: incr 'i
get i
@hiiamboris
Copy link

hiiamboris commented Jun 5, 2019

Bear with me, as I'll be very critical ☺

  1. incr series - useless. Same as next series/skip series. The same pattern can be used for a better operation: incr [s1 s2 s3] to advance the heads of a few series at once (modifying them in place preferably), but then advance is a better word for that IMO.
  2. incr pair - useless. Increases both X and Y of the pair. What's the point? Diagonal traversal? I suggest a special case for pairs: amount: amount * 1x0 when it's an integer.
  3. % * int == float - if this holds true for some set of operands we should raise an issue about it.
  4. incr 'i - the most common case arguably reads no better than i: i + 1; incr/by 'i n IMO is less readable than i: i + n

I'm generally not a fan of index operations, and this includes increments. Instead of glossing the headache, we should get rid of it by replacing the patterns like:

x: first s  incr 's
set [y z] b  incr/by 'b 2

with something like (just an idea right now, the point is - in our time the computer is smart enough to figure out the increment amount on it's own):

set/skip [x] s
set/skip [y z] b

Even better, we should strive to make loops smart enough to free us of the burden of the aforementioned set/skip:

> foreach [ [w] [x y z] ] [ [a b c] [1 0 0  0 1 0  0 0 1] ] [print [w ":" x y z]]
a : 1 0 0
b : 0 1 0
c : 0 0 1

@hiiamboris
Copy link

The only real problem I foresee in incr/decr is this scenario:

i: 0				;) there was some user variable
f: function [] [		;) and some function with a local variable
	i: 0
	loop 3 [i: i + 1  print i]
]
				;) and it was working.. 
f: function [] [		;) then the user changed it to `incr`:
	i: 0
	loop 3 [incr 'i  print i]
]
				;) and then after some refactoring he accidentally destroyed `i: 0`
f: function [] [
	loop 3 [incr 'i  print i]
]
;) initial `i: i + 1` version would give him an error from summing `none` and `1`
;) but the new version silently modifies the global `i`

I cannot judge how common this mistake can be. Likely not enough to consider it.

@greggirwin
Copy link
Author

Thanks for the feedback. Good thoughts.

@giesse (I think) did the first n-foreach, which works like your example. Logo does that implicitly in its map func, which I'm taking into consideration on HOF design.

Easy to remove series! support, as it is redundant with next/skip. Advance is a great word.

Agree that it's not very useful for pairs as well, but the alternative is to use distinct types in the spec, rather than the scalar! typeset.

If you could check on the % float! issue, that would be great.

i: i + 1 is not offensive, so then it's the question of whether the word incr will help convey intent, as noted in the opening comments.

That leaves the numeric value in series case:

customers: [
	gregg [
		stats [visits 0]
	]
]
name: 'gregg
customers/:name/stats/visits: customers/:name/stats/visits + 1
incr 'customers/:name/stats/visits

The reason not to use a lit-arg, is computed targets. But that doesn't seem like a big problem for this function, but it is still an issue. Using a lit-arg means the update-in-place behavior is simpler, at the expense of other features. If those features don't add value anyway, it's a win, as you now know that incr is there for a reason.

@hiiamboris
Copy link

hiiamboris commented Jun 5, 2019

If we use a lit-word! param how do you use a computed incr arg?

You mean like this?

>> incr: func ['x] [print set x 1 + get x]
== func ['x][print set x 1 + get x]
>> y: 'x  x: 0  incr x
1
>> incr :y
2
>> incr (y)
3
>> incr (to word! "x")
4

That forbids incr incr x but not incr (incr x) - which can be better than incr/by with complex scalars like time.

If you could check on the % float! issue, that would be great.

Do you have an example of this behavior?

@greggirwin
Copy link
Author

Lit-args, yes. The workarounds just aren't very nice.

% * int Seems to be fixed now. This code is quite old.

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