-
-
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 + 1the 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 wantstepto take care for that, or having somedefaultmechanism 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.