Result pipeline
Divide a quantity among teams, then among people, while requiring every share to be a nonnegative whole number. Invalid inputs become Res.Err values.
func divide_evenly(total: Int, groups: Int): Res[Int, Str] { if groups <= 0 { ret Res[Int, Str].Err("group count must be positive") } if total < 0 { ret Res[Int, Str].Err("total must be nonnegative") } if total % groups != 0 { ret Res[Int, Str].Err("cannot divide evenly") } ret Res[Int, Str].Val(total / groups)}func per_person(total: Int, teams: Int, people: Int): Res[Int, Str] { let per_team = divide_evenly(total, teams)? let share = divide_evenly(per_team, people)? ret Res[Int, Str].Val(share)}func report(result: Res[Int, Str]) { match result { Val(amount) -> print("each person gets {amount}"), Err(reason) -> print("cannot share: {reason}"), }}report(per_person(120, 3, 4))report(per_person(120, 0, 4))report(per_person(100, 3, 4))report(per_person(120, 3, 0))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.
each person gets 10 cannot share: group count must be positive cannot share: cannot divide evenly cannot share: group count must be positive
Original AST
[
{
"node": {
"FuncDef": {
"name": {
"node": "divide_evenly",
"span": [
5,
18
]
},
"params": [
{
"name": {
"node": "total",
"span": [
19,
24
]
},
"type_ann": {
"node": {
"Name": "Int"
},
"span": [
26,
29
]
}
},
{
"name": {
"node": "groups",
"span": [
31,
37
]
},
"type_ann": {
"node": {
"Name": "Int"
},
"span": [
39,
42
]
}
}
],
"ret_type": {
"node": {
"Item": {
"object": {
"node": {
"Name": "Res"
},
"span": [
45,
48
]
},
"args": [
{
"node": {
"Name": "Int"
},
"span": [
49,
52
]
},
{
"node": {
"Name": "Str"
},
"span": [
54,
57
]
}
]
}
},
"span": [
45,
58
]
},
"body": [
{
"node": {
"Expr": {
"node": {
"If": {
"cond": {
"node": {
"BinOp": {
"op": "Le",
"left": {
"node": {
"Name": "groups"
},
"span": [
68,
74
]
},
"right": {
"node": {
"Int": "0"
},
"span": [
78,
79
]
}
}
},
"span": [
68,
79
]
},
"then_body": [
{
"node": {
"Ret": {
"keyword": [
82,
85
],
"value": {
"node": {
"Call": {
"callee": {
"node": {
"Attr": {
"object": {
"node": {
"Item": {
"object": {
"node": {
"Name": "Res"
},
"span": [
86,
89
]
},
"args": [
{
"node": {
"Name": "Int"
},
"span": [
90,
93
]
},
{
"node": {
"Name": "Str"
},
"span": [
95,
98
]
}
]
}
},
"span": [
86,
99
]
},
"name": "Err",
"name_span": [
100,
103
]
}
},
"span": [
86,
103
]
},
"args": [
{
"node": {
"Str": "group count must be positive"
},
"span": [
104,
134
]
}
],
"args_span": [
103,
135
]
}
},
"span": [
86,
135
]
}
}
},
"span": [
82,
135
]
}
],
"else_body": null
}
},
"span": [
65,
137
]
}
},
"span": [
65,
137
]
},
{
"node": {
"Expr": {
"node": {
"If": {
"cond": {
"node": {
"BinOp": {
"op": "Lt",
"left": {
"node": {
"Name": "total"
},
"span": [
145,
150
]
},
"right": {
"node": {
"Int": "0"
},
"span": [
153,
154
]
}
}
},
"span": [
145,
154
]
},
"then_body": [
{
"node": {
"Ret": {
"keyword": [
157,
160
],
"value": {
"node": {
"Call": {
"callee": {
"node": {
"Attr": {
"object": {
"node": {
"Item": {
"object": {
"node": {
"Name": "Res"
},
"span": [
161,
164
]
},
"args": [
{
"node": {
"Name": "Int"
},
"span": [
165,
168
]
},
{
"node": {
"Name": "Str"
},
"span": [
170,
173
]
}
]
}
},
"span": [
161,
174
]
},
"name": "Err",
"name_span": [
175,
178
]
}
},
"span": [
161,
178
]
},
"args": [
{
"node": {
"Str": "total must be nonnegative"
},
"span": [
179,
206
]
}
],
"args_span": [
178,
207
]
}
},
"span": [
161,
207
]
}
}
},
"span": [
157,
207
]
}
],
"else_body": null
}
},
"span": [
142,
209
]
}
},
"span": [
142,
209
]
},
{
"node": {
"Expr": {
"node": {
"If": {
"cond": {
"node": {
"BinOp": {
"op": "Ne",
"left": {
"node": {
"BinOp": {
"op": "Mod",
"left": {
"node": {
"Name": "total"
},
"span": [
217,
222
]
},
"right": {
"node": {
"Name": "groups"
},
"span": [
225,
231
]
}
}
},
"span": [
217,
231
]
},
"right": {
"node": {
"Int": "0"
},
"span": [
235,
236
]
}
}
},
"span": [
217,
236
]
},
"then_body": [
{
"node": {
"Ret": {
"keyword": [
239,
242
],
"value": {
"node": {
"Call": {
"callee": {
"node": {
"Attr": {
"object": {
"node": {
"Item": {
"object": {
"node": {
"Name": "Res"
},
"span": [
243,
246
]
},
"args": [
{
"node": {
"Name": "Int"
},
"span": [
247,
250
]
},
{
"node": {
"Name": "Str"
},
"span": [
252,
255
]
}
]
}
},
"span": [
243,
256
]
},
"name": "Err",
"name_span": [
257,
260
]
}
},
"span": [
243,
260
]
},
"args": [
{
"node": {
"Str": "cannot divide evenly"
},
"span": [
261,
283
]
}
],
"args_span": [
260,
284
]
}
},
"span": [
243,
284
]
}
}
},
"span": [
239,
284
]
}
],
"else_body": null
}
},
"span": [
214,
286
]
}
},
"span": [
214,
286
]
},
{
"node": {
"Ret": {
"keyword": [
291,
294
],
"value": {
"node": {
"Call": {
"callee": {
"node": {
"Attr": {
"object": {
"node": {
"Item": {
"object": {
"node": {
"Name": "Res"
},
"span": [
295,
298
]
},
"args": [
{
"node": {
"Name": "Int"
},
"span": [
299,
302
]
},
{
"node": {
"Name": "Str"
},
"span": [
304,
307
]
}
]
}
},
"span": [
295,
308
]
},
"name": "Val",
"name_span": [
309,
312
]
}
},
"span": [
295,
312
]
},
"args": [
{
"node": {
"BinOp": {
"op": "Div",
"left": {
"node": {
"Name": "total"
},
"span": [
313,
318
]
},
"right": {
"node": {
"Name": "groups"
},
"span": [
321,
327
]
}
}
},
"span": [
313,
327
]
}
],
"args_span": [
312,
328
]
}
},
"span": [
295,
328
]
}
}
},
"span": [
291,
328
]
}
]
}
},
"span": [
0,
330
]
},
{
"node": {
"FuncDef": {
"name": {
"node": "per_person",
"span": [
336,
346
]
},
"params": [
{
"name": {
"node": "total",
"span": [
347,
352
]
},
"type_ann": {
"node": {
"Name": "Int"
},
"span": [
354,
357
]
}
},
{
"name": {
"node": "teams",
"span": [
359,
364
]
},
"type_ann": {
"node": {
"Name": "Int"
},
"span": [
366,
369
]
}
},
{
"name": {
"node": "people",
"span": [
371,
377
]
},
"type_ann": {
"node": {
"Name": "Int"
},
"span": [
379,
382
]
}
}
],
"ret_type": {
"node": {
"Item": {
"object": {
"node": {
"Name": "Res"
},
"span": [
385,
388
]
},
"args": [
{
"node": {
"Name": "Int"
},
"span": [
389,
392
]
},
{
"node": {
"Name": "Str"
},
"span": [
394,
397
]
}
]
}
},
"span": [
385,
398
]
},
"body": [
{
"node": {
"Let": {
"pattern": {
"node": {
"Binding": {
"node": "per_team",
"span": [
409,
417
]
}
},
"span": [
409,
417
]
},
"type_ann": null,
"value": {
"node": {
"Ques": {
"node": {
"Call": {
"callee": {
"node": {
"Name": "divide_evenly"
},
"span": [
420,
433
]
},
"args": [
{
"node": {
"Name": "total"
},
"span": [
434,
439
]
},
{
"node": {
"Name": "teams"
},
"span": [
441,
446
]
}
],
"args_span": [
433,
447
]
}
},
"span": [
420,
447
]
}
},
"span": [
420,
448
]
}
}
},
"span": [
405,
448
]
},
{
"node": {
"Let": {
"pattern": {
"node": {
"Binding": {
"node": "share",
"span": [
457,
462
]
}
},
"span": [
457,
462
]
},
"type_ann": null,
"value": {
"node": {
"Ques": {
"node": {
"Call": {
"callee": {
"node": {
"Name": "divide_evenly"
},
"span": [
465,
478
]
},
"args": [
{
"node": {
"Name": "per_team"
},
"span": [
479,
487
]
},
{
"node": {
"Name": "people"
},
"span": [
489,
495
]
}
],
"args_span": [
478,
496
]
}
},
"span": [
465,
496
]
}
},
"span": [
465,
497
]
}
}
},
"span": [
453,
497
]
},
{
"node": {
"Ret": {
"keyword": [
502,
505
],
"value": {
"node": {
"Call": {
"callee": {
"node": {
"Attr": {
"object": {
"node": {
"Item": {
"object": {
"node": {
"Name": "Res"
},
"span": [
506,
509
]
},
"args": [
{
"node": {
"Name": "Int"
},
"span": [
510,
513
]
},
{
"node": {
"Name": "Str"
},
"span": [
515,
518
]
}
]
}
},
"span": [
506,
519
]
},
"name": "Val",
"name_span": [
520,
523
]
}
},
"span": [
506,
523
]
},
"args": [
{
"node": {
"Name": "share"
},
"span": [
524,
529
]
}
],
"args_span": [
523,
530
]
}
},
"span": [
506,
530
]
}
}
},
"span": [
502,
530
]
}
]
}
},
"span": [
331,
532
]
},
{
"node": {
"FuncDef": {
"name": {
"node": "report",
"span": [
538,
544
]
},
"params": [
{
"name": {
"node": "result",
"span": [
545,
551
]
},
"type_ann": {
"node": {
"Item": {
"object": {
"node": {
"Name": "Res"
},
"span": [
553,
556
]
},
"args": [
{
"node": {
"Name": "Int"
},
"span": [
557,
560
]
},
{
"node": {
"Name": "Str"
},
"span": [
562,
565
]
}
]
}
},
"span": [
553,
566
]
}
}
],
"ret_type": null,
"body": [
{
"node": {
"Expr": {
"node": {
"Match": {
"keyword": [
574,
579
],
"subject": {
"node": {
"Name": "result"
},
"span": [
580,
586
]
},
"arms": [
{
"pattern": {
"node": {
"Variant": {
"name": {
"node": "Val",
"span": [
597,
600
]
},
"bindings": [
{
"node": {
"Binding": {
"node": "amount",
"span": [
601,
607
]
}
},
"span": [
601,
607
]
}
]
}
},
"span": [
597,
608
]
},
"body": [
{
"node": {
"Expr": {
"node": {
"Call": {
"callee": {
"node": {
"Name": "print"
},
"span": [
612,
617
]
},
"args": [
{
"node": {
"Interp": {
"parts": [
{
"Lit": "each person gets "
},
{
"Expr": {
"node": {
"Name": "amount"
},
"span": [
637,
643
]
}
}
]
}
},
"span": [
618,
645
]
}
],
"args_span": [
617,
646
]
}
},
"span": [
612,
646
]
}
},
"span": [
612,
646
]
}
]
},
{
"pattern": {
"node": {
"Variant": {
"name": {
"node": "Err",
"span": [
656,
659
]
},
"bindings": [
{
"node": {
"Binding": {
"node": "reason",
"span": [
660,
666
]
}
},
"span": [
660,
666
]
}
]
}
},
"span": [
656,
667
]
},
"body": [
{
"node": {
"Expr": {
"node": {
"Call": {
"callee": {
"node": {
"Name": "print"
},
"span": [
671,
676
]
},
"args": [
{
"node": {
"Interp": {
"parts": [
{
"Lit": "cannot share: "
},
{
"Expr": {
"node": {
"Name": "reason"
},
"span": [
693,
699
]
}
}
]
}
},
"span": [
677,
701
]
}
],
"args_span": [
676,
702
]
}
},
"span": [
671,
702
]
}
},
"span": [
671,
702
]
}
]
}
]
}
},
"span": [
574,
709
]
}
},
"span": [
574,
709
]
}
]
}
},
"span": [
533,
711
]
},
{
"node": {
"Expr": {
"node": {
"Call": {
"callee": {
"node": {
"Name": "report"
},
"span": [
712,
718
]
},
"args": [
{
"node": {
"Call": {
"callee": {
"node": {
"Name": "per_person"
},
"span": [
719,
729
]
},
"args": [
{
"node": {
"Int": "120"
},
"span": [
730,
733
]
},
{
"node": {
"Int": "3"
},
"span": [
735,
736
]
},
{
"node": {
"Int": "4"
},
"span": [
738,
739
]
}
],
"args_span": [
729,
740
]
}
},
"span": [
719,
740
]
}
],
"args_span": [
718,
741
]
}
},
"span": [
712,
741
]
}
},
"span": [
712,
741
]
},
{
"node": {
"Expr": {
"node": {
"Call": {
"callee": {
"node": {
"Name": "report"
},
"span": [
742,
748
]
},
"args": [
{
"node": {
"Call": {
"callee": {
"node": {
"Name": "per_person"
},
"span": [
749,
759
]
},
"args": [
{
"node": {
"Int": "120"
},
"span": [
760,
763
]
},
{
"node": {
"Int": "0"
},
"span": [
765,
766
]
},
{
"node": {
"Int": "4"
},
"span": [
768,
769
]
}
],
"args_span": [
759,
770
]
}
},
"span": [
749,
770
]
}
],
"args_span": [
748,
771
]
}
},
"span": [
742,
771
]
}
},
"span": [
742,
771
]
},
{
"node": {
"Expr": {
"node": {
"Call": {
"callee": {
"node": {
"Name": "report"
},
"span": [
772,
778
]
},
"args": [
{
"node": {
"Call": {
"callee": {
"node": {
"Name": "per_person"
},
"span": [
779,
789
]
},
"args": [
{
"node": {
"Int": "100"
},
"span": [
790,
793
]
},
{
"node": {
"Int": "3"
},
"span": [
795,
796
]
},
{
"node": {
"Int": "4"
},
"span": [
798,
799
]
}
],
"args_span": [
789,
800
]
}
},
"span": [
779,
800
]
}
],
"args_span": [
778,
801
]
}
},
"span": [
772,
801
]
}
},
"span": [
772,
801
]
},
{
"node": {
"Expr": {
"node": {
"Call": {
"callee": {
"node": {
"Name": "report"
},
"span": [
802,
808
]
},
"args": [
{
"node": {
"Call": {
"callee": {
"node": {
"Name": "per_person"
},
"span": [
809,
819
]
},
"args": [
{
"node": {
"Int": "120"
},
"span": [
820,
823
]
},
{
"node": {
"Int": "3"
},
"span": [
825,
826
]
},
{
"node": {
"Int": "0"
},
"span": [
828,
829
]
}
],
"args_span": [
819,
830
]
}
},
"span": [
809,
830
]
}
],
"args_span": [
808,
831
]
}
},
"span": [
802,
831
]
}
},
"span": [
802,
831
]
}
]Read the two boundaries
Section titled “Read the two boundaries”divide_evenly owns the arithmetic contract. It rejects invalid divisors and totals before using division, then rejects any nonzero remainder.
per_person owns composition. Each ? either extracts the successful share or returns the failure unchanged. Both stages use Res[Int, Str], so propagation fits the enclosing function’s return type.
report owns presentation. Its match prints success and failure without asking arithmetic code to decide how an error should be shown.
The result tutorial walks through the contract and suggests boundary cases you can test.