feat(lang): 0.R5 - a reducer writes one state and may read others, declared between its state and the action
What only becomes known inside the drain - a value another reducer just set, the map in play - no longer has to be faked into the action, so a verb that reads several systems while it changes one is a reducer instead of an act handed its states. A second state to write is still refused, and the message says the way out. examples/actions/reads.ludic; reseeded. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
dfcc851d2a
commit
c64f8750e2
8 changed files with 48037 additions and 47917 deletions
14
LANGUAGE.md
14
LANGUAGE.md
|
|
@ -360,12 +360,14 @@ program Pack {
|
|||
|
||||
- **`action Name { fields }`** is a record, with defaults like any. `export action` for other
|
||||
modules to dispatch it.
|
||||
- **`reducer State on Action(s: mut State, a: Action) { ... }`** takes exactly its state and the
|
||||
action, in that order. A second state is refused (`reducer Pack on Buy: a reducer takes one state,
|
||||
and w is a Wallet - what it needs to know rides in the action`), and so is a call inside it to a
|
||||
function that needs another, since nothing supplies one there. What it needs to know rides in the
|
||||
action, filled by whoever dispatches it. Several reducers may handle one action, one per state;
|
||||
a reducer is not called by name.
|
||||
- **`reducer State on Action(s: mut State, a: Action) { ... }`** WRITES exactly its state, and takes
|
||||
the action last. Between the two it may declare states it only READS - `reducer Wallet on Buy(w:
|
||||
mut Wallet, s: Shop, a: Buy)` - supplied like its own, for what is only known inside the drain (a
|
||||
price another reducer just set, the map in play, where the save lives). A second state to write
|
||||
is refused (`reducer Pack on Buy: a reducer writes one state, and w is a mut Wallet - read it (w:
|
||||
Wallet), or dispatch an action Wallet's own reducer takes`), and so is a call inside it to a
|
||||
function that writes another. What the dispatcher knows rides in the action. Several reducers may
|
||||
handle one action, one per state; a reducer is not called by name.
|
||||
- **`dispatch Action { fields }`** queues the action, from anywhere: a handler, a function, a
|
||||
listener, a reducer. The queue is the runtime's, supplied like an entry point's state, so
|
||||
dispatching needs no state parameter.
|
||||
|
|
|
|||
8
changes/reducer-reads.md
Normal file
8
changes/reducer-reads.md
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
bump: minor
|
||||
type: feat
|
||||
**A reducer may read other states.** `reducer Wallet on Buy(w: mut Wallet, s: Shop, a: Buy)` writes
|
||||
its own state and reads the ones declared between it and the action, supplied by the drain like its
|
||||
own. What only becomes known inside the drain - a value another reducer just set, the map in play,
|
||||
where the save lives - no longer has to be faked into the action, and a verb that reads several
|
||||
systems while it changes one is a reducer instead of an act. A second state to WRITE is still
|
||||
refused, now with the way out in the message (`examples/actions/reads.ludic`).
|
||||
23
examples/actions/reads.ludic
Normal file
23
examples/actions/reads.ludic
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
# reads.ludic - 0.R5: a reducer writes one state and may READ others, declared between its state and
|
||||
# the action: what only becomes known inside the drain (a price another reducer set) need not ride in
|
||||
# the action, and still no reducer changes a state it does not own.
|
||||
program ReducerReads {
|
||||
state Shop { price: int = 3 }
|
||||
state Wallet { cash: int = 10 }
|
||||
state Pack { n: int = 0 }
|
||||
action Rise { by: int }
|
||||
action Buy { }
|
||||
|
||||
reducer Shop on Rise(s: mut Shop, a: Rise) { s.price += a.by }
|
||||
reducer Wallet on Buy(w: mut Wallet, s: Shop, a: Buy) {
|
||||
if w.cash >= s.price { w.cash -= s.price }
|
||||
}
|
||||
reducer Pack on Buy(p: mut Pack, w: Wallet, a: Buy) { p.n += 1 }
|
||||
|
||||
entry (s: Shop, w: Wallet, p: Pack) {
|
||||
dispatch Rise { by: 2 }
|
||||
dispatch Buy { }
|
||||
drain_actions()
|
||||
print(`{s.price} {w.cash} {p.n}`)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
# 0.R: a reducer takes its own state and the action; what else it needs rides in the action
|
||||
# 0.R: a reducer writes its own state and may read others; a second state to write is refused
|
||||
program ReducerTwoStates {
|
||||
state Pack { n: int = 0 }
|
||||
state Wallet { cash: int = 0 }
|
||||
action Buy { cost: int }
|
||||
reducer Pack on Buy(p: mut Pack, w: mut Wallet) { p.n += 1 }
|
||||
reducer Pack on Buy(p: mut Pack, w: mut Wallet, a: Buy) { p.n += 1 }
|
||||
entry { print(1) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,9 +8,9 @@
|
|||
#
|
||||
# `dispatch` queues the action; the queue is drained - every reducer of each action run in turn,
|
||||
# each supplied its own state - at the end of every phase of the frame loop, after each ludic.base
|
||||
# phase, or where the program calls drain_actions(). A reducer takes exactly its state and the
|
||||
# action. Actions a reducer dispatches go behind the queue, never re-entrant, and a queue still
|
||||
# growing after ACTION_PASSES rounds stops the program with the action that keeps coming.
|
||||
# phase, or where the program calls drain_actions(). A reducer writes exactly its own state and may
|
||||
# READ others, declared between it and the action. Actions a reducer dispatches go behind the queue,
|
||||
# never re-entrant, and a queue still growing after ACTION_PASSES rounds stops the program.
|
||||
var g_act_names: []pointer = new []pointer # the actions, in the order read
|
||||
var g_act_nodes: []Node = new []Node
|
||||
var g_red_nodes: []Node = new []Node # the reducers
|
||||
|
|
@ -126,16 +126,16 @@ function red_check(i: int) -> void {
|
|||
if f.kids[k].kind == N_PARAM { push(ps, f.kids[k]) }
|
||||
k += 1
|
||||
}
|
||||
k = 0
|
||||
while k < len(ps) {
|
||||
# between its state and the action: states it only READS, never another to write
|
||||
k = 1
|
||||
while k < len(ps) - 1 {
|
||||
let p = ps[k]
|
||||
if k > 0 and is_state_ty(p.ty) {
|
||||
act_err(f, `reducer {st} on {act}: a reducer takes one state, and {p.s} is a {p.ty} - what it needs to know rides in the action`)
|
||||
}
|
||||
if not is_state_ty(p.ty) { act_err(f, `reducer {st} on {act}: {p.s} is a {p.ty} - between its state and the action a reducer takes only states it reads; what it needs to know rides in the action`) }
|
||||
if p.uns == 1 { act_err(f, `reducer {st} on {act}: a reducer writes one state, and {p.s} is a mut {p.ty} - read it ({p.s}: {p.ty}), or dispatch an action {p.ty}'s own reducer takes`) }
|
||||
k += 1
|
||||
}
|
||||
if len(ps) != 2 or not (ps[0].ty == st) or not (ps[1].ty == act) {
|
||||
act_err(f, `reducer {st} on {act}: its parameters are its state and the action - ({reg_lower(st)}: mut {st}, a: {act})`)
|
||||
if len(ps) < 2 or not (ps[0].ty == st) or not (ps[len(ps) - 1].ty == act) {
|
||||
act_err(f, `reducer {st} on {act}: its parameters are its state, any states it reads, and the action - ({reg_lower(st)}: mut {st}, a: {act})`)
|
||||
}
|
||||
var j = 0
|
||||
while j < i {
|
||||
|
|
|
|||
47941
selfhost/ludicc.seed.ll
47941
selfhost/ludicc.seed.ll
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1013,6 +1013,7 @@ function cmd_dev_test() -> int {
|
|||
reject_case("rejected/layer_cycle", "go round in a circle: items -> layer app -> items", "a cycle through a layer and out of it is refused")
|
||||
feat_case("actions/pack", "", "0 1 9 13 picked 7 picked 9 too heavy", "pack.ludic (0.R: actions, reducers - one per state, in the order of the states' names - dispatch, drain_actions, an action a reducer dispatches goes behind)")
|
||||
controller_case("actions/ui_press", "", "5 10", "ui_press.ludic (0.R4: an action a UI press dispatches is reduced before ludic.ui hands control back)")
|
||||
feat_case("actions/reads", "", "5 5 1", "reads.ludic (0.R5: a reducer reads other states, declared between its own and the action)")
|
||||
feat_case("actions/modules", "", "2", "modules.ludic (0.R: an action in one module, its reducer in another)")
|
||||
actions_deps_case()
|
||||
deps_reach_case()
|
||||
|
|
@ -1020,7 +1021,7 @@ function cmd_dev_test() -> int {
|
|||
feat_case("actions/phases", "dd a q", "1 0 2 0 2 1 1 1 1 2 1 2", "phases.ludic (0.R: the frame loop drains the queue after every phase - Input's actions are reduced before Update)")
|
||||
feat_case("actions/runaway", "", "actions: Ping is still being dispatched after 64 rounds of reducers - a reducer dispatches what dispatches it", "runaway.ludic (0.R: a reducer that dispatches what dispatches it is stopped by name)")
|
||||
reject_case("rejected/registry_count_key", "def Tools count: its constant would be TL_COUNT", "a registry key named count is refused")
|
||||
reject_case("rejected/reducer_two_states", "a reducer takes one state, and w is a Wallet", "a reducer takes exactly one state")
|
||||
reject_case("rejected/reducer_two_states", "a reducer writes one state, and w is a mut Wallet", "a reducer writes exactly one state")
|
||||
reject_case("rejected/reducer_not_action", "Buy is not an action", "a reducer is on an action")
|
||||
reject_case("rejected/dispatch_unknown", "dispatch Sell: Sell is not an action", "only an action is dispatched")
|
||||
feat_case("state/counter", "", "11 3 heard 5", "counter.ludic (0.S: a state, mut and read-only parameters, entry, listener and fn-value injection)")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue