Skip to content
kata / a language workbench

Build a stateful counter

A counter needs two things: a number that survives between calls and an operation that changes it. A closure lets those two pieces stay together without placing the number in a global binding.

We will make a factory with a starting value and a step size. A counter created with make_counter(0, 1) should return 1, then 2, then 3. A separate counter with step 10 should return 10, then 20.

The operation increments before returning. That choice is part of the contract.

First, run this smaller program:

let count = 0
func next(): Int {
count = count + 1
ret count
}
print(next())
print(next())
print(count)

It prints 1, 2, and 2. The function captures the binding named count, and assignment updates that same binding. The final top-level read sees the update.

If the function instead used let count = 1, it would introduce a new local binding each time. That would be a different program: it would shadow the outer name instead of updating it.

Give make_counter two typed parameters, start: Int and step: Int. Inside it, create let value = start.

The factory returns an anonymous function with ret func(): Int { ... }. That function captures value and step; each call assigns value + step back to value, then returns it.

The factory itself has return type Func. This says that it returns a function value. KataScript does not currently encode the returned function’s full signature as type arguments to Func.

func make_counter(start: Int, step: Int): Func {
let value = start
ret func(): Int {
value = value + step
ret value
}
}
let next = make_counter(0, 1)
let tens = make_counter(0, 10)
print("next: {next()}")
print("next: {next()}")
print("tens: {tens()}")
print("next: {next()}")
print("tens: {tens()}")
Original example: checked output and syntax tree

These records belong to the original downloadable program. Run the editor above to see results for your changes.

next: 1
next: 2
tens: 10
next: 3
tens: 20
Original AST
[
  {
    "node": {
      "FuncDef": {
        "name": {
          "node": "make_counter",
          "span": [
            5,
            17
          ]
        },
        "params": [
          {
            "name": {
              "node": "start",
              "span": [
                18,
                23
              ]
            },
            "type_ann": {
              "node": {
                "Name": "Int"
              },
              "span": [
                25,
                28
              ]
            }
          },
          {
            "name": {
              "node": "step",
              "span": [
                30,
                34
              ]
            },
            "type_ann": {
              "node": {
                "Name": "Int"
              },
              "span": [
                36,
                39
              ]
            }
          }
        ],
        "ret_type": {
          "node": {
            "Name": "Func"
          },
          "span": [
            42,
            46
          ]
        },
        "body": [
          {
            "node": {
              "Let": {
                "pattern": {
                  "node": {
                    "Binding": {
                      "node": "value",
                      "span": [
                        57,
                        62
                      ]
                    }
                  },
                  "span": [
                    57,
                    62
                  ]
                },
                "type_ann": null,
                "value": {
                  "node": {
                    "Name": "start"
                  },
                  "span": [
                    65,
                    70
                  ]
                }
              }
            },
            "span": [
              53,
              70
            ]
          },
          {
            "node": {
              "Ret": {
                "keyword": [
                  75,
                  78
                ],
                "value": {
                  "node": {
                    "FuncExpr": {
                      "params": [],
                      "ret_type": {
                        "node": {
                          "Name": "Int"
                        },
                        "span": [
                          87,
                          90
                        ]
                      },
                      "body": [
                        {
                          "node": {
                            "Assign": {
                              "target": {
                                "Name": "value"
                              },
                              "value": {
                                "node": {
                                  "BinOp": {
                                    "op": "Add",
                                    "left": {
                                      "node": {
                                        "Name": "value"
                                      },
                                      "span": [
                                        109,
                                        114
                                      ]
                                    },
                                    "right": {
                                      "node": {
                                        "Name": "step"
                                      },
                                      "span": [
                                        117,
                                        121
                                      ]
                                    }
                                  }
                                },
                                "span": [
                                  109,
                                  121
                                ]
                              }
                            }
                          },
                          "span": [
                            101,
                            121
                          ]
                        },
                        {
                          "node": {
                            "Ret": {
                              "keyword": [
                                130,
                                133
                              ],
                              "value": {
                                "node": {
                                  "Name": "value"
                                },
                                "span": [
                                  134,
                                  139
                                ]
                              }
                            }
                          },
                          "span": [
                            130,
                            139
                          ]
                        }
                      ]
                    }
                  },
                  "span": [
                    79,
                    145
                  ]
                }
              }
            },
            "span": [
              75,
              145
            ]
          }
        ]
      }
    },
    "span": [
      0,
      147
    ]
  },
  {
    "node": {
      "Let": {
        "pattern": {
          "node": {
            "Binding": {
              "node": "next",
              "span": [
                152,
                156
              ]
            }
          },
          "span": [
            152,
            156
          ]
        },
        "type_ann": null,
        "value": {
          "node": {
            "Call": {
              "callee": {
                "node": {
                  "Name": "make_counter"
                },
                "span": [
                  159,
                  171
                ]
              },
              "args": [
                {
                  "node": {
                    "Int": "0"
                  },
                  "span": [
                    172,
                    173
                  ]
                },
                {
                  "node": {
                    "Int": "1"
                  },
                  "span": [
                    175,
                    176
                  ]
                }
              ],
              "args_span": [
                171,
                177
              ]
            }
          },
          "span": [
            159,
            177
          ]
        }
      }
    },
    "span": [
      148,
      177
    ]
  },
  {
    "node": {
      "Let": {
        "pattern": {
          "node": {
            "Binding": {
              "node": "tens",
              "span": [
                182,
                186
              ]
            }
          },
          "span": [
            182,
            186
          ]
        },
        "type_ann": null,
        "value": {
          "node": {
            "Call": {
              "callee": {
                "node": {
                  "Name": "make_counter"
                },
                "span": [
                  189,
                  201
                ]
              },
              "args": [
                {
                  "node": {
                    "Int": "0"
                  },
                  "span": [
                    202,
                    203
                  ]
                },
                {
                  "node": {
                    "Int": "10"
                  },
                  "span": [
                    205,
                    207
                  ]
                }
              ],
              "args_span": [
                201,
                208
              ]
            }
          },
          "span": [
            189,
            208
          ]
        }
      }
    },
    "span": [
      178,
      208
    ]
  },
  {
    "node": {
      "Expr": {
        "node": {
          "Call": {
            "callee": {
              "node": {
                "Name": "print"
              },
              "span": [
                209,
                214
              ]
            },
            "args": [
              {
                "node": {
                  "Interp": {
                    "parts": [
                      {
                        "Lit": "next: "
                      },
                      {
                        "Expr": {
                          "node": {
                            "Call": {
                              "callee": {
                                "node": {
                                  "Name": "next"
                                },
                                "span": [
                                  223,
                                  227
                                ]
                              },
                              "args": [],
                              "args_span": [
                                227,
                                229
                              ]
                            }
                          },
                          "span": [
                            223,
                            229
                          ]
                        }
                      }
                    ]
                  }
                },
                "span": [
                  215,
                  231
                ]
              }
            ],
            "args_span": [
              214,
              232
            ]
          }
        },
        "span": [
          209,
          232
        ]
      }
    },
    "span": [
      209,
      232
    ]
  },
  {
    "node": {
      "Expr": {
        "node": {
          "Call": {
            "callee": {
              "node": {
                "Name": "print"
              },
              "span": [
                233,
                238
              ]
            },
            "args": [
              {
                "node": {
                  "Interp": {
                    "parts": [
                      {
                        "Lit": "next: "
                      },
                      {
                        "Expr": {
                          "node": {
                            "Call": {
                              "callee": {
                                "node": {
                                  "Name": "next"
                                },
                                "span": [
                                  247,
                                  251
                                ]
                              },
                              "args": [],
                              "args_span": [
                                251,
                                253
                              ]
                            }
                          },
                          "span": [
                            247,
                            253
                          ]
                        }
                      }
                    ]
                  }
                },
                "span": [
                  239,
                  255
                ]
              }
            ],
            "args_span": [
              238,
              256
            ]
          }
        },
        "span": [
          233,
          256
        ]
      }
    },
    "span": [
      233,
      256
    ]
  },
  {
    "node": {
      "Expr": {
        "node": {
          "Call": {
            "callee": {
              "node": {
                "Name": "print"
              },
              "span": [
                257,
                262
              ]
            },
            "args": [
              {
                "node": {
                  "Interp": {
                    "parts": [
                      {
                        "Lit": "tens: "
                      },
                      {
                        "Expr": {
                          "node": {
                            "Call": {
                              "callee": {
                                "node": {
                                  "Name": "tens"
                                },
                                "span": [
                                  271,
                                  275
                                ]
                              },
                              "args": [],
                              "args_span": [
                                275,
                                277
                              ]
                            }
                          },
                          "span": [
                            271,
                            277
                          ]
                        }
                      }
                    ]
                  }
                },
                "span": [
                  263,
                  279
                ]
              }
            ],
            "args_span": [
              262,
              280
            ]
          }
        },
        "span": [
          257,
          280
        ]
      }
    },
    "span": [
      257,
      280
    ]
  },
  {
    "node": {
      "Expr": {
        "node": {
          "Call": {
            "callee": {
              "node": {
                "Name": "print"
              },
              "span": [
                281,
                286
              ]
            },
            "args": [
              {
                "node": {
                  "Interp": {
                    "parts": [
                      {
                        "Lit": "next: "
                      },
                      {
                        "Expr": {
                          "node": {
                            "Call": {
                              "callee": {
                                "node": {
                                  "Name": "next"
                                },
                                "span": [
                                  295,
                                  299
                                ]
                              },
                              "args": [],
                              "args_span": [
                                299,
                                301
                              ]
                            }
                          },
                          "span": [
                            295,
                            301
                          ]
                        }
                      }
                    ]
                  }
                },
                "span": [
                  287,
                  303
                ]
              }
            ],
            "args_span": [
              286,
              304
            ]
          }
        },
        "span": [
          281,
          304
        ]
      }
    },
    "span": [
      281,
      304
    ]
  },
  {
    "node": {
      "Expr": {
        "node": {
          "Call": {
            "callee": {
              "node": {
                "Name": "print"
              },
              "span": [
                305,
                310
              ]
            },
            "args": [
              {
                "node": {
                  "Interp": {
                    "parts": [
                      {
                        "Lit": "tens: "
                      },
                      {
                        "Expr": {
                          "node": {
                            "Call": {
                              "callee": {
                                "node": {
                                  "Name": "tens"
                                },
                                "span": [
                                  319,
                                  323
                                ]
                              },
                              "args": [],
                              "args_span": [
                                323,
                                325
                              ]
                            }
                          },
                          "span": [
                            319,
                            325
                          ]
                        }
                      }
                    ]
                  }
                },
                "span": [
                  311,
                  327
                ]
              }
            ],
            "args_span": [
              310,
              328
            ]
          }
        },
        "span": [
          305,
          328
        ]
      }
    },
    "span": [
      305,
      328
    ]
  }
]
Download original counter.ks ↓

Read the output in call order. The next counter advances three times, while tens advances twice. Calling one does not move the other.

Each factory call creates its own value binding. The functions share source code, but they capture different state. Returning a closure keeps its scalar binding accessible after the factory call has ended.

Change the first construction to make_counter(100, 5). Its three printed values should become 105, 110, and 115. The tens output should stay the same.

Then set the step to zero. The counter should keep returning its starting value. Nothing in the function requires a positive step; a negative step also makes sense.

These small changes test the contract directly: initial state, update rule, and independence.

A closure can carry a running sum, a numbering sequence, or a configured transformation. A factory that captures factor and returns func(n) { ret n * factor } has the same shape with immutable captured configuration.

This tutorial captures scalar values. Collection aliases and resource-owning closures have unresolved lifecycle behavior; do not generalize the example into an ownership guarantee. The function guide explains capture and shadowing, and language status records the current limits.