-
-
Save greggirwin/1f8c1a7f59b4d47cefd9267ae0ccb0af to your computer and use it in GitHub Desktop.
Red [] | |
do %step.red | |
; Wrapper that hides the detail of whether a value is indirectly referenced. | |
arg-val: func [arg][ | |
either any [any-word? arg any-path? arg] [get arg][arg] | |
] | |
test-step: func [arg /by amt][ | |
print ["Arg: " mold arg] | |
if by [print ["By: " amt]] | |
if any-path? arg [print ["Root: " mold get first arg]] ; root of path | |
print [ | |
"Before:" mold arg-val arg newline | |
"Result:" mold either by [step/by :arg amt][step :arg] newline | |
"After: " mold arg-val arg | |
] | |
if any-path? arg [print ["Root: " mold get first arg]] ; root of path | |
print "" | |
] | |
; Use different values, so show how STEP works. | |
; Scalars | |
n: 1 test-step 'n | |
; step/down 'n | |
test-step/by 'n 5 | |
n: 1.2 test-step 'n | |
test-step/by 'n 3.4 | |
p: 1x2 test-step 'p | |
test-step/by 'p 3x4 | |
ch: #"A" test-step 'ch | |
test-step/by 'ch 3 | |
t: 1.2.3 test-step 't | |
test-step/by 't 0.0.1 | |
t: 1:2:3 test-step 't | |
test-step/by 't 0:0:15 | |
pct: 1% test-step 'pct | |
test-step/by 'pct 0.5% | |
m: $1 test-step 'm | |
test-step/by 'm $0.50 | |
d: 01-jan-2022 | |
test-step 'd | |
test-step/by 'd 7 | |
d: 01-jan-2022/12:00:00 | |
test-step/by 'd 0:0:15 | |
; Series | |
b: [a b c d e f] | |
test-step 'b | |
; step/down 'b | |
test-step/by 'b 3 | |
b: [1 2 3 x 4 y 5 z [6 7 8]] | |
test-step 'b/1 | |
test-step 'b/x | |
test-step 'b/z | |
test-step 'b/z/1 | |
; step/down 'b/z/1 | |
;print mold b | |
customers: [ | |
gregg [ | |
stats [visits 0] | |
] | |
] | |
name: 'gregg | |
test-step 'customers/:name/stats/visits | |
test-step/by 'customers/:name/stats/visits 4 | |
;------------------------------------------------------------------------------- | |
;advance: function [ | |
; series [word!] "Word referring to a series" | |
; /by "Advance by count, instead of 1" | |
; count [integer!] | |
;][ | |
; op: either series? get value [:skip][:add] | |
; set value op get value amount | |
; :value ;-- Return the word for chaining calls. | |
;] | |
;tests: [ | |
; scalars: [#"A" 2 3.4 5x6 7% 8.9.10 11:12:13 14-Feb-2022 $16.17] | |
; scalar-words: [char int float pair pct tuple time date money] | |
; set scalar-words scalars | |
; | |
; paths: [a/b/c 'a/b/c :a/b/c a/b/c:] | |
; path-words: [path lit-path get-path set-path] | |
; set path-words paths | |
;] | |
halt |
Red [] | |
Re: WRT: func ['name content][ | |
; add content to system catalog, tagged with name. | |
] | |
WRT step { | |
The most common case will be to change state, but step does add meaning, | |
and has benefits, in my mind, when used with scalars. That can always | |
be added later, as it's a point of design contention on the value. | |
Benefits over manual `n: n + 1` approach: | |
- By default there is no amount value, so you can't get the + 1 part wrong. | |
- STEP is often used when looping, making loop variables stand out from | |
other `+/-` ops that might be in expressions. | |
- STEP generally means the amount is small. Another mental hint. | |
- If stepping sub-values, e.g. a pair's X or Y, it removes another thing | |
you could get wrong. `pos/x: pos/y + 1` | |
Other thoughts: | |
- If step updates a reference "in place", using a lit-word as the arg, | |
rather than a lit-arg, makes it look more like `set` calls, which is | |
nice. | |
- If step does not support scalar args, and only refs to update, the | |
lit-arg approach may be read differently, like a control func. | |
- If we use a lit-word! param how do you use a computed step arg? It's | |
just a big ugly to do, not impossible. | |
- 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 STEP 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. | |
- If the arg is not a lit-arg, and lit-words are used as args, that won't | |
break if the param is later changed to a lit-arg in STEP's spec. | |
- Should STEP return the word or the value? Returning the word means you | |
can chain with other word calls, but not directly with value calls, | |
like WHILE tests. The latter seems much more useful, because you already | |
have the word, and what else would you do with it? | |
} | |
step: function [ | |
"Steps (increments) a value or series index by 1" | |
name [any-word! any-path!] "Value referenced must be a series or scalar" | |
;'name [any-word! any-path!] "Value referenced must be a series or scalar" | |
/down "Step in a negative direction (decrement)" | |
/by "Change by this amount, instead of 1; can't be used with both /down and tuples" | |
; Not all scalar step types make sense. | |
amount [integer! float! pair! percent! time! tuple! money!] ; = exclude [scalar!] [char! date!] | |
][ | |
amount: any [amount 1] | |
if down [amount: negate amount] | |
; We use this more than once. No need to `get` it each time. | |
value: get name | |
; Type check | |
unless any [series? :value scalar? :value][ | |
cause-error 'Script 'invalid-arg "Step name must refer to a series or scalar value" | |
;cause-error 'Script 'invalid-type [type? :value] | |
] | |
; This is to be smart about stepping percents by numbers. We do | |
; this, so the default STEP call is nicer. If we don't do this | |
; we can explain why `1% + 1 == 101%` but that's not a nice for | |
; the user, because every time percent is used, they have to use | |
; `/by`, which largely defeats the purpose of STEP. This is about | |
; "stepping" in the context of the value given. | |
if all [ | |
percent? :value | |
any [integer? amount float? amount] | |
][ | |
amount: 1% * amount ;-- Scale and cast amount to percent. | |
] | |
;op: either series? :value [:skip][:add] ;!! This doesn't work compiled. | |
;set name op :value amount | |
set name either series? :value [ | |
skip :value amount | |
][ | |
add :value amount | |
] | |
;name ;?? Return word for chaining calls? | |
] | |
;step: function [ | |
; "Steps (increments) a value or series index by 1" | |
; value [scalar! any-word! any-path!] "If value is a word, it will refer to the new value" | |
; /down "Step in a negative direction (decrement)" | |
; /by "Change by this amount, instead of 1" | |
; amount [scalar!] | |
;][ | |
; amount: any [amount 1] | |
; if down [amount: negate amount] | |
; | |
; ;?? Is this worth it? | |
; 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 stepping percents by numbers. | |
; ; The question is whether we want to do this, so the default 'step | |
; ; call is inarguably nicer. If we don't do this it is easy to explain | |
; ; but not very nice. This is about "stepping" in the context of the | |
; ; value given. | |
; if all [ | |
; any [integer? amount float? amount] | |
; ;1 = absolute amount ;!! This makes it hard to reason about. | |
; any [percent? value percent? attempt [get value]] | |
; ][amount: 1% * amount] ;-- Scale and cast amount to percent. | |
; | |
; case [ | |
; scalar? value [add value amount] ;-- Same as n: n + i | |
; 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. | |
; ] | |
; ; Does this add enough value over `next/skip`? | |
; ;series? value [skip value amount] ;!! Check series after any-path. | |
; ] | |
;] | |
perhaps for everything other than integers we may require /by
view [progress 0% rate 0:0:0.1 on-time [step 'face/data]]
Now you will argue that it should still use /by
because that scenario is always calculated based on something. Which just points out that /by
is useful, here or somewhere else. Step
is the domain of incrementing/increasing and the opposite, so it's a good place for it. But let's extrapolate further. We've talked about quantity!
as a possible datatype. Could it possibly be useful there? e.g., in the U.S. if you have a measurement in feet, but you want to step it inch by inch. Should it be dialected, like my idea for split
, or is it constrained enough that a couple refinements will do better? I can't say. But I do feel comfortable saying that it's another tool for thinking, if we want it to be.
I'm also looking at this with range
and loop/repeat
in mind.
I have replied to the Element channel, so for the record:
- I don't need nor like
/down
, for me, negative value usage is more useful, transparent - For code like
n: n + 1
the most boring part is to initialise the value most often to zero. And you have to do it outside of the loop, e.g.n: 0 loop 10 [n: n + 1 print ["Something" n]]
. I wantstep
to take care for that, or having somedefault
mechanism in the language.
That's why I don't like special handling of percents.
Wasn't my point. My point is, perhaps for everything other than integers we may require /by, and that would also free us from imagined problems like how should percents be summed.