In a nested policy notation built from blocks, what does it mean that each block runs against an implicit receiver?
answer
- the block is code, not data
- someone hands the block a target
- unqualified names are member accesses
- the enclosing call creates and attaches
- nesting is containment, not indentation
basics
~20 sEach block is a piece of code the enclosing call evaluates against an object it supplies, so unqualified calls inside the block configure that object. Nesting blocks builds a tree: an inner block targets the node its enclosing call just created.
solid answer
~40 sA scoped-receiver notation passes a block of code to a call. That call creates a node, runs the block with the node as the block's implicit target, and then attaches the node where it belongs. So a bare `effect = ALLOW` written inside a rule block is not a free-floating statement: it is a member access on the rule node, written without saying `rule.`. Because every enclosing call does the same thing, the physical nesting of the blocks is what expresses containment — a rule block written inside a role block produces a rule node held by that role node. Nothing scans the text afterwards to work out the shape; the shape is the order in which the calls ran.
code
pseudocode · 8 linespolicy {
role("auditor") {
rule("read-ledger") {
effect = ALLOW
condition { actorInGroup("finance") }
}
}
}go deeper
Be able to read a nested block and say, line by line, which node each line configures and who created it. That reading skill is the whole of the junior expectation here.
Explain the four steps of a scope-opening call: create the node, run the block against it, validate, attach. Say why validation belongs at the close of the block.
Point out that enclosing receivers stay in scope inside inner blocks, and describe the misplacement and ordering failures that follow from a mutable node being configured in place.
Weigh a nested block notation against a plain nested data structure for a config surface many teams author, judging the error messages, tooling support and review cost each produces.
## What the construct is A **block** here is ordinary code passed as an argument to a call, not a data literal. A **scoped receiver** (an *implicit receiver*) is an object the enclosing call supplies as the default target for the names written inside that block. Inside the block you write `effect = ALLOW` or `condition { ... }`; both are members of the supplied object, reached without naming it. Two things follow immediately, and they are what an interviewer is checking: - The notation is **executing**, not being parsed. Every line inside a block is a call or an assignment that runs in order. - The **nesting is the structure**. Nothing walks the file afterwards to decide which rule belongs to which role; the rule node was created and attached while the role's block was running. ## What the enclosing call does The shape of every scope-opening function is the same four steps: 1. Create the node the block is going to describe. 2. Evaluate the block with that node as its implicit receiver. 3. Validate the node, now that the block has finished adding to it. 4. Attach it to the node the *enclosing* block is describing, and return. Step 2 is the whole trick. The block is a value the caller can decide what to do with — run it once, run it against a node of its choosing, or (rarely) not run it at all. Step 3 matters because the node is only complete after the block returns, which is why validation is normally written at the close of the scope rather than in the middle of it. ## Reading the notation | What the author writes | What actually happens | |---|---| | `policy { ... }` | create the root node, run the block against it | | `role("auditor") { ... }` | create a role node, run the block against it, add it to the root | | `effect = ALLOW` inside a rule block | set a member on the rule node that is the current receiver | | `condition { ... }` inside a rule block | call a member of the rule node, which opens a further scope | The indentation a reader sees and the parent-child relation the program builds are the same fact, which is the readability argument for the style. ## Why a notation is built this way - **The shape is declared once.** The author never repeats which node they are configuring, and never introduces a variable per level. - **The scope is a type.** Because the receiver has a type, the set of things that may legally be written inside a block is fixed by that type, and an editor can list them. - **Containment is structural.** A misplaced closing brace is a visible structural error rather than a value assigned to the wrong variable. - **The tree can be assembled and checked in one pass**, since each scope closes before its parent continues. This is a different mechanism from chaining calls that each return the same object to continue a sentence: chaining produces a flat sequence of steps, while nested receiver blocks produce depth. Some notations use both, chaining inside a block that a scope opened. ## Where the convenience has a cost - Enclosing receivers do not disappear when an inner block opens. Inside the rule block, the role node is usually still in scope, so a call that the rule node cannot satisfy may be answered by the role node instead — quietly, and with no error. - Two scopes may offer a member of the same name, and only one of them will be hit. - Because the node is mutable while its block runs, a statement that *reads* the node sees only what the statements above it have done. Those are the failures the rest of this subject is about. What a candidate must be able to do first is read a nested block and say, for any line in it, **which node** that line is configuring and **who created** that node. That single reading skill is what separates "I have used a configuration notation like this" from "I know what it is doing". ## How languages differ Whether a block can take an implicit receiver at all is a language feature, and languages differ: some let a function declare that the block it takes is evaluated against a given type, some require the node to be passed to the block as an ordinary parameter and written explicitly, and some only have the explicit form. The mechanism is the same in every case — a call supplies the target and runs the block against it — and only the amount of ceremony at the call site changes.
- Who creates the node that an inner block configures, and when is it attached to its parent?The call that takes the block creates it before running the block, and attaches it after the block returns. That ordering is why validation naturally sits at the close of a scope: only then has the block finished adding members, so the node can be checked as a whole before it joins the tree.
- If the language cannot evaluate a block against an implicit receiver, how is the same tree expressed?The node is handed to the block as an explicit parameter and every line names it, so the same calls run in the same order and the same tree is built. The structure is identical; the notation is noisier, and the gain of the implicit form is only that the target is not repeated on every line.
- Does a scope block run more than once, or at a later time?By default it runs exactly once, immediately, while the enclosing call is on the stack. A scope-opening function can choose otherwise — storing the block to run later or per item — but then the notation no longer reads as a declaration, and authors are usually surprised, so the convention is to run it once and eagerly.
It is like filling in a form section where every box is already understood to belong to that section: you write the value, not the section's name, and starting a sub-section is what says where the next boxes belong.
saying these in an interview costs you the question
- Thinks the nested block is data the runtime parses, not code that runs
- Says the nesting is only indentation and the nodes end up flat
- Assumes one global builder is the receiver for every block in the file
- Believes blocks are collected and evaluated after the whole file is read
- Cannot say which node a given line inside a nested block configures