Skip to content

Instantly share code, notes, and snippets.

@greggirwin
Last active August 19, 2023 06:51
Show Gist options
  • Save greggirwin/1f8c1a7f59b4d47cefd9267ae0ccb0af to your computer and use it in GitHub Desktop.
Save greggirwin/1f8c1a7f59b4d47cefd9267ae0ccb0af to your computer and use it in GitHub Desktop.
Step mezz func (new name for incr/decr) and demo script.
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.
; ]
;]
@greggirwin
Copy link
Author

I'm also looking at this with range and loop/repeat in mind.

@pekr
Copy link

pekr commented Aug 19, 2023

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 want step to take care for that, or having some default mechanism in the language.

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