Custom iterator
A for loop needs an iterable that creates an iterator, and an iterator that yields optional values. This countdown implements both pieces in KataScript.
kind Countdown { start: Int }kind CountdownIter { current: Int }impl Countdown as ToIter[Int] { func to_iter(self): CountdownIter { ret CountdownIter { current: self.start } }}impl CountdownIter as Iter[Int] { func next(self): Opt[Int] { if self.current <= 0 { ret Opt[Int].Non } let value = self.current self.current = self.current - 1 ret Opt[Int].Val(value) }}let launch = Countdown { start: 3 }for n in launch { print(n) }print("go")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.
3 2 1 go
Original AST
[
{
"node": {
"KindDef": {
"name": {
"node": "Countdown",
"span": [
5,
14
]
},
"type_params": [],
"fields": [
{
"name": {
"node": "start",
"span": [
17,
22
]
},
"type_ann": {
"node": {
"Name": "Int"
},
"span": [
24,
27
]
}
}
]
}
},
"span": [
0,
29
]
},
{
"node": {
"KindDef": {
"name": {
"node": "CountdownIter",
"span": [
35,
48
]
},
"type_params": [],
"fields": [
{
"name": {
"node": "current",
"span": [
51,
58
]
},
"type_ann": {
"node": {
"Name": "Int"
},
"span": [
60,
63
]
}
}
]
}
},
"span": [
30,
65
]
},
{
"node": {
"Impl": {
"target": {
"node": {
"Concrete": "Countdown"
},
"span": [
71,
80
]
},
"as_type": {
"node": {
"Item": {
"object": {
"node": {
"Name": "ToIter"
},
"span": [
84,
90
]
},
"args": [
{
"node": {
"Name": "Int"
},
"span": [
91,
94
]
}
]
}
},
"span": [
84,
95
]
},
"methods": [
{
"node": {
"name": {
"node": "to_iter",
"span": [
107,
114
]
},
"params": [
{
"name": {
"node": "self",
"span": [
115,
119
]
},
"type_ann": null
}
],
"ret_type": {
"node": {
"Name": "CountdownIter"
},
"span": [
122,
135
]
},
"body": [
{
"node": {
"Ret": {
"keyword": [
146,
149
],
"value": {
"node": {
"Construct": {
"type_expr": {
"node": {
"Name": "CountdownIter"
},
"span": [
150,
163
]
},
"fields": [
[
"current",
{
"node": {
"Attr": {
"object": {
"node": {
"Name": "self"
},
"span": [
175,
179
]
},
"name": "start",
"name_span": [
180,
185
]
}
},
"span": [
175,
185
]
}
]
],
"open_brace": [
164,
165
]
}
},
"span": [
150,
187
]
}
}
},
"span": [
146,
187
]
}
]
},
"span": [
102,
193
]
}
]
}
},
"span": [
66,
195
]
},
{
"node": {
"Impl": {
"target": {
"node": {
"Concrete": "CountdownIter"
},
"span": [
201,
214
]
},
"as_type": {
"node": {
"Item": {
"object": {
"node": {
"Name": "Iter"
},
"span": [
218,
222
]
},
"args": [
{
"node": {
"Name": "Int"
},
"span": [
223,
226
]
}
]
}
},
"span": [
218,
227
]
},
"methods": [
{
"node": {
"name": {
"node": "next",
"span": [
239,
243
]
},
"params": [
{
"name": {
"node": "self",
"span": [
244,
248
]
},
"type_ann": null
}
],
"ret_type": {
"node": {
"Item": {
"object": {
"node": {
"Name": "Opt"
},
"span": [
251,
254
]
},
"args": [
{
"node": {
"Name": "Int"
},
"span": [
255,
258
]
}
]
}
},
"span": [
251,
259
]
},
"body": [
{
"node": {
"Expr": {
"node": {
"If": {
"cond": {
"node": {
"BinOp": {
"op": "Le",
"left": {
"node": {
"Attr": {
"object": {
"node": {
"Name": "self"
},
"span": [
273,
277
]
},
"name": "current",
"name_span": [
278,
285
]
}
},
"span": [
273,
285
]
},
"right": {
"node": {
"Int": "0"
},
"span": [
289,
290
]
}
}
},
"span": [
273,
290
]
},
"then_body": [
{
"node": {
"Ret": {
"keyword": [
293,
296
],
"value": {
"node": {
"Attr": {
"object": {
"node": {
"Item": {
"object": {
"node": {
"Name": "Opt"
},
"span": [
297,
300
]
},
"args": [
{
"node": {
"Name": "Int"
},
"span": [
301,
304
]
}
]
}
},
"span": [
297,
305
]
},
"name": "Non",
"name_span": [
306,
309
]
}
},
"span": [
297,
309
]
}
}
},
"span": [
293,
309
]
}
],
"else_body": null
}
},
"span": [
270,
311
]
}
},
"span": [
270,
311
]
},
{
"node": {
"Let": {
"pattern": {
"node": {
"Binding": {
"node": "value",
"span": [
324,
329
]
}
},
"span": [
324,
329
]
},
"type_ann": null,
"value": {
"node": {
"Attr": {
"object": {
"node": {
"Name": "self"
},
"span": [
332,
336
]
},
"name": "current",
"name_span": [
337,
344
]
}
},
"span": [
332,
344
]
}
}
},
"span": [
320,
344
]
},
{
"node": {
"Assign": {
"target": {
"Attr": {
"object": {
"node": {
"Name": "self"
},
"span": [
353,
357
]
},
"attr": "current"
}
},
"value": {
"node": {
"BinOp": {
"op": "Sub",
"left": {
"node": {
"Attr": {
"object": {
"node": {
"Name": "self"
},
"span": [
368,
372
]
},
"name": "current",
"name_span": [
373,
380
]
}
},
"span": [
368,
380
]
},
"right": {
"node": {
"Int": "1"
},
"span": [
383,
384
]
}
}
},
"span": [
368,
384
]
}
}
},
"span": [
353,
384
]
},
{
"node": {
"Ret": {
"keyword": [
393,
396
],
"value": {
"node": {
"Call": {
"callee": {
"node": {
"Attr": {
"object": {
"node": {
"Item": {
"object": {
"node": {
"Name": "Opt"
},
"span": [
397,
400
]
},
"args": [
{
"node": {
"Name": "Int"
},
"span": [
401,
404
]
}
]
}
},
"span": [
397,
405
]
},
"name": "Val",
"name_span": [
406,
409
]
}
},
"span": [
397,
409
]
},
"args": [
{
"node": {
"Name": "value"
},
"span": [
410,
415
]
}
],
"args_span": [
409,
416
]
}
},
"span": [
397,
416
]
}
}
},
"span": [
393,
416
]
}
]
},
"span": [
234,
422
]
}
]
}
},
"span": [
196,
424
]
},
{
"node": {
"Let": {
"pattern": {
"node": {
"Binding": {
"node": "launch",
"span": [
429,
435
]
}
},
"span": [
429,
435
]
},
"type_ann": null,
"value": {
"node": {
"Construct": {
"type_expr": {
"node": {
"Name": "Countdown"
},
"span": [
438,
447
]
},
"fields": [
[
"start",
{
"node": {
"Int": "3"
},
"span": [
457,
458
]
}
]
],
"open_brace": [
448,
449
]
}
},
"span": [
438,
460
]
}
}
},
"span": [
425,
460
]
},
{
"node": {
"Expr": {
"node": {
"For": {
"pattern": {
"node": {
"Binding": {
"node": "n",
"span": [
465,
466
]
}
},
"span": [
465,
466
]
},
"iter_expr": {
"node": {
"Name": "launch"
},
"span": [
470,
476
]
},
"body": [
{
"node": {
"Expr": {
"node": {
"Call": {
"callee": {
"node": {
"Name": "print"
},
"span": [
479,
484
]
},
"args": [
{
"node": {
"Name": "n"
},
"span": [
485,
486
]
}
],
"args_span": [
484,
487
]
}
},
"span": [
479,
487
]
}
},
"span": [
479,
487
]
}
]
}
},
"span": [
461,
489
]
}
},
"span": [
461,
489
]
},
{
"node": {
"Expr": {
"node": {
"Call": {
"callee": {
"node": {
"Name": "print"
},
"span": [
490,
495
]
},
"args": [
{
"node": {
"Str": "go"
},
"span": [
496,
500
]
}
],
"args_span": [
495,
501
]
}
},
"span": [
490,
501
]
}
},
"span": [
490,
501
]
}
]Two types, two responsibilities
Section titled “Two types, two responsibilities”Countdown stores the starting number. Its to_iter() method creates fresh traversal state, so another loop can start the countdown again.
CountdownIter stores the current number. Each next() call returns Opt[Int].Val(value) and moves the current number downward. Once it reaches zero, it returns Opt[Int].Non.
The value is saved before mutation, so starting at three yields 3, 2, 1. Starting at zero or a negative number yields no values.
Protocols connect the loop
Section titled “Protocols connect the loop”The impl ... as ToIter[Int] and impl ... as Iter[Int] declarations use interfaces from the prelude. The loop calls to_iter() once and keeps the updated iterator between calls to next().
There is no range literal or built-in range() required here. The iteration mechanism is ordinary methods over a small state value.
Read methods and interfaces for implementation syntax and control flow for loop patterns.