design: nested when silently drops outer condition — should error or document as unsupported #23

Closed
opened 2026-06-10 14:47:40 +02:00 by yorunikakeru · 0 comments
Owner

Problem

when condA $ when condB $ ... silently discards condA. The inner setCondition overwrites ssCondition with condB and returns without any indication that the outer condition was lost:

setCondition condition body = Builder $ \state -> do
    validCondition <- condition
    let withCondition = state{ssCondition = Just validCondition}
    runBuilder body withCondition  -- inner setCondition can overwrite ssCondition again

The doc comment in DSL.hs says "applying when again replaces it" — but that refers to sequential calls in a do-block, not nesting. Nested calls look semantically like "gate by condA AND condB", which is not what happens.

Example

profile "gaming" $
    when (cpuLoad 0.8) $          -- ← condA: silently lost
        when (processRunning "steam") $   -- ← condB: actually stored
            enable "performance-mode"

The resulting profile has condition processRunning "steam" only. cpuLoad 0.8 is gone.

Options

  1. Make nesting a compile errorsetCondition receives a ProfileBuilder (); it could return a restricted type (e.g. BodyBuilder) that does not expose setCondition, so nesting is rejected at compile time.
  2. Make nesting a runtime error — detect ssCondition /= Nothing before overwriting and return Left with a new NestedCondition error.
  3. Compose conditions with And — replace instead of erroring, but combine: ssCondition = Just (And existing new).
  4. Document and test the current behavior explicitly — add a regression test for nested when so future refactors do not accidentally change the semantics.
## Problem `when condA $ when condB $ ...` silently discards `condA`. The inner `setCondition` overwrites `ssCondition` with `condB` and returns without any indication that the outer condition was lost: ```haskell setCondition condition body = Builder $ \state -> do validCondition <- condition let withCondition = state{ssCondition = Just validCondition} runBuilder body withCondition -- inner setCondition can overwrite ssCondition again ``` The doc comment in `DSL.hs` says *"applying `when` again replaces it"* — but that refers to sequential calls in a `do`-block, not nesting. Nested calls look semantically like "gate by condA AND condB", which is not what happens. ## Example ```haskell profile "gaming" $ when (cpuLoad 0.8) $ -- ← condA: silently lost when (processRunning "steam") $ -- ← condB: actually stored enable "performance-mode" ``` The resulting profile has condition `processRunning "steam"` only. `cpuLoad 0.8` is gone. ## Options 1. **Make nesting a compile error** — `setCondition` receives a `ProfileBuilder ()`; it could return a restricted type (e.g. `BodyBuilder`) that does not expose `setCondition`, so nesting is rejected at compile time. 2. **Make nesting a runtime error** — detect `ssCondition /= Nothing` before overwriting and return `Left` with a new `NestedCondition` error. 3. **Compose conditions with `And`** — replace instead of erroring, but combine: `ssCondition = Just (And existing new)`. 4. **Document and test the current behavior explicitly** — add a regression test for nested `when` so future refactors do not accidentally change the semantics.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
FrogOS/DSL#23
No description provided.