From 9b38f3584b85e9396ebd19d6ca8966b980022a93 Mon Sep 17 00:00:00 2001 From: Chris Boesch Date: Sat, 11 Feb 2023 11:12:47 +0100 Subject: [PATCH 1/5] first draft for interfaces --- exercises/092_interfaces.zig | 88 ++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 exercises/092_interfaces.zig diff --git a/exercises/092_interfaces.zig b/exercises/092_interfaces.zig new file mode 100644 index 0000000..f9f9dc3 --- /dev/null +++ b/exercises/092_interfaces.zig @@ -0,0 +1,88 @@ +// +// Remeber excerice xx with tagged unions. That was a lot more better +// but it's can bee perfect. +// +// With tagged unions, it gets EVEN BETTER! If you don't have a +// need for a separate enum, you can define an inferred enum with +// your union all in one place. Just use the 'enum' keyword in +// place of the tag type: +// +// const Foo = union(enum) { +// small: u8, +// medium: u32, +// large: u64, +// }; +// +// Let's convert Insect. Doctor Zoraptera has already deleted the +// explicit InsectStat enum for you! +// +const std = @import("std"); + +const Ant = struct { + still_alive: bool, + + pub fn print(self: Ant) void { + std.debug.print("Ant is {s}.\n", .{if (self.still_alive) "alive" else "death"}); + } +}; + +const Bee = struct { + flowers_visited: u16, + + pub fn print(self: Bee) void { + std.debug.print("Bee visited {} flowers.\n", .{self.flowers_visited}); + } +}; + +const Grasshopper = struct { + distance_hopped: u16, + + pub fn print(self: Grasshopper) void { + std.debug.print("Grasshopper hopped {} m.\n", .{self.distance_hopped}); + } +}; + +const Insect = union(enum) { + ant: Ant, + bee: Bee, + grasshopper: Grasshopper, + + pub fn print(self: Insect) void { + switch (self) { + inline else => |case| return case.print(), + } + } +}; + +pub fn main() !void { + var my_insects = [_]Insect{ Insect{ + .ant = Ant{ .still_alive = true }, + }, Insect{ + .bee = Bee{ .flowers_visited = 17 }, + }, Insect{ + .grasshopper = Grasshopper{ .distance_hopped = 32 }, + } }; + + try dailyReport(&my_insects); +} + +fn dailyReport(insectReport: []Insect) !void { + std.debug.print("Daily insect report:\n", .{}); + for (insectReport) |insect| { + insect.print(); + } +} + +// Inferred enums are neat, representing the tip of the iceberg +// in the relationship between enums and unions. You can actually +// coerce a union TO an enum (which gives you the active field +// from the union as an enum). What's even wilder is that you can +// coerce an enum to a union! But don't get too excited, that +// only works when the union type is one of those weird zero-bit +// types like void! +// +// Tagged unions, as with most ideas in computer science, have a +// long history going back to the 1960s. However, they're only +// recently becoming mainstream, particularly in system-level +// programming languages. You might have also seen them called +// "variants", "sum types", or even "enums"! From 35c5d6b976edb16c484e278aabc3d750e2880a77 Mon Sep 17 00:00:00 2001 From: Chris Boesch Date: Sat, 11 Feb 2023 11:43:09 +0100 Subject: [PATCH 2/5] added 092_interfaces to build --- build.zig | 9 +++++---- exercises/092_interfaces.zig | 36 ++++++++---------------------------- 2 files changed, 13 insertions(+), 32 deletions(-) diff --git a/build.zig b/build.zig index 4312fb7..03e83e9 100644 --- a/build.zig +++ b/build.zig @@ -460,6 +460,10 @@ const exercises = [_]Exercise{ // .output = "ABCDEF", // .@"async" = true, // }, + .{ + .main_file = "092_interfaces.zig", + .output = "Daily insect report:\nAnt is alive.\nBee visited 17 flowers.\nGrasshopper hopped 32 m.", + }, .{ .main_file = "999_the_end.zig", .output = "\nThis is the end for now!\nWe hope you had fun and were able to learn a lot, so visit us again when the next exercises are available.", @@ -564,10 +568,7 @@ pub fn build(b: *Builder) void { const file_path = std.fs.path.join(b.allocator, &[_][]const u8{ if (use_healed) "patches/healed" else "exercises", ex.main_file, }) catch unreachable; - const build_step = b.addExecutable(.{ - .name = base_name, - .root_source_file = .{ .path = file_path } - }); + const build_step = b.addExecutable(.{ .name = base_name, .root_source_file = .{ .path = file_path } }); build_step.install(); diff --git a/exercises/092_interfaces.zig b/exercises/092_interfaces.zig index f9f9dc3..45f9d8e 100644 --- a/exercises/092_interfaces.zig +++ b/exercises/092_interfaces.zig @@ -1,20 +1,7 @@ // -// Remeber excerice xx with tagged unions. That was a lot more better -// but it's can bee perfect. +// Remeber excerices 55-57 with tagged unions. // -// With tagged unions, it gets EVEN BETTER! If you don't have a -// need for a separate enum, you can define an inferred enum with -// your union all in one place. Just use the 'enum' keyword in -// place of the tag type: -// -// const Foo = union(enum) { -// small: u8, -// medium: u32, -// large: u64, -// }; -// -// Let's convert Insect. Doctor Zoraptera has already deleted the -// explicit InsectStat enum for you! +// (story/explanation from Dave) // const std = @import("std"); @@ -63,9 +50,14 @@ pub fn main() !void { .grasshopper = Grasshopper{ .distance_hopped = 32 }, } }; + // The daily situation report, what's going on in the garden try dailyReport(&my_insects); } +// Through the interface we can keep a list of various objects +// (in this case the insects of our garden) and even pass them +// to a function without having to know the specific properties +// of each or the object itself. This is really cool! fn dailyReport(insectReport: []Insect) !void { std.debug.print("Daily insect report:\n", .{}); for (insectReport) |insect| { @@ -73,16 +65,4 @@ fn dailyReport(insectReport: []Insect) !void { } } -// Inferred enums are neat, representing the tip of the iceberg -// in the relationship between enums and unions. You can actually -// coerce a union TO an enum (which gives you the active field -// from the union as an enum). What's even wilder is that you can -// coerce an enum to a union! But don't get too excited, that -// only works when the union type is one of those weird zero-bit -// types like void! -// -// Tagged unions, as with most ideas in computer science, have a -// long history going back to the 1960s. However, they're only -// recently becoming mainstream, particularly in system-level -// programming languages. You might have also seen them called -// "variants", "sum types", or even "enums"! +// Interfaces... (explanation from Dave) From bb4b321b0c848a5262d7bd75d70912528be74b0a Mon Sep 17 00:00:00 2001 From: Chris Boesch Date: Tue, 14 Feb 2023 09:11:46 +0100 Subject: [PATCH 3/5] created an empty patch for testing until the exercise is finished --- patches/patches/092_interfaces.patch | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 patches/patches/092_interfaces.patch diff --git a/patches/patches/092_interfaces.patch b/patches/patches/092_interfaces.patch new file mode 100644 index 0000000..ce3141f --- /dev/null +++ b/patches/patches/092_interfaces.patch @@ -0,0 +1,3 @@ +68a69,70 +> // +> // just an empty patch From beaa89fdf570b58c25f33f72715f6fa8ca2cd8a5 Mon Sep 17 00:00:00 2001 From: Chris Boesch Date: Tue, 14 Feb 2023 12:58:12 +0100 Subject: [PATCH 4/5] inserted a failure and created a patch --- exercises/092_interfaces.zig | 2 +- patches/patches/092_interfaces.patch | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/exercises/092_interfaces.zig b/exercises/092_interfaces.zig index 45f9d8e..5adc9c0 100644 --- a/exercises/092_interfaces.zig +++ b/exercises/092_interfaces.zig @@ -51,7 +51,7 @@ pub fn main() !void { } }; // The daily situation report, what's going on in the garden - try dailyReport(&my_insects); + try dailyReport("what is here the right parameter?"); } // Through the interface we can keep a list of various objects diff --git a/patches/patches/092_interfaces.patch b/patches/patches/092_interfaces.patch index ce3141f..67fc443 100644 --- a/patches/patches/092_interfaces.patch +++ b/patches/patches/092_interfaces.patch @@ -1,3 +1,4 @@ -68a69,70 -> // -> // just an empty patch +54c54 +< try dailyReport("what is here the right parameter?"); +--- +> try dailyReport(&my_insects); From 662086cb898ab96431edc38b969664e60e0d9d96 Mon Sep 17 00:00:00 2001 From: Dave Gauer Date: Wed, 15 Feb 2023 17:45:10 -0500 Subject: [PATCH 5/5] Added story/explanation to new ex. 092 --- build.zig | 2 +- exercises/092_interfaces.zig | 107 +++++++++++++++++++++------ patches/patches/092_interfaces.patch | 6 +- 3 files changed, 87 insertions(+), 28 deletions(-) diff --git a/build.zig b/build.zig index 03e83e9..50faf23 100644 --- a/build.zig +++ b/build.zig @@ -462,7 +462,7 @@ const exercises = [_]Exercise{ // }, .{ .main_file = "092_interfaces.zig", - .output = "Daily insect report:\nAnt is alive.\nBee visited 17 flowers.\nGrasshopper hopped 32 m.", + .output = "Daily Insect Report:\nAnt is alive.\nBee visited 17 flowers.\nGrasshopper hopped 32 meters.", }, .{ .main_file = "999_the_end.zig", diff --git a/exercises/092_interfaces.zig b/exercises/092_interfaces.zig index 5adc9c0..43f1119 100644 --- a/exercises/092_interfaces.zig +++ b/exercises/092_interfaces.zig @@ -1,7 +1,55 @@ // -// Remeber excerices 55-57 with tagged unions. +// Remember our ant and bee simulator constructed with unions +// back in exercises 55 and 56? There, we demonstrated that +// unions allow us to treat different data types in a uniform +// manner. // -// (story/explanation from Dave) +// One neat feature was using tagged unions to create a single +// function to print a status for ants *or* bees by switching: +// +// switch (insect) { +// .still_alive => ... // (print ant stuff) +// .flowers_visited => ... // (print bee stuff) +// } +// +// Well, that simulation was running just fine until a new insect +// arrived in the virtual garden, a grasshopper! +// +// Doctor Zoraptera started to add grasshopper code to the +// program, but then she backed away from her keyboard with an +// angry hissing sound. She had realized that having code for +// each insect in one place and code to print each insect in +// another place was going to become unpleasant to maintain when +// the simulation expanded to hundreds of different insects. +// +// Thankfully, Zig has another comptime feature we can use +// to get out of this dilema called the 'inline else'. +// +// We can replace this redundant code: +// +// switch (thing) { +// .a => |a| special(a), +// .b => |b| normal(b), +// .c => |c| normal(c), +// .d => |d| normal(d), +// .e => |e| normal(e), +// ... +// } +// +// With: +// +// switch (thing) { +// .a => |a| special(a), +// inline else |t| => normal(t), +// } +// +// We can have special handling of some cases and then Zig +// handles the rest of the matches for us. +// +// With this feature, you decide to make an Insect union with a +// single uniform 'print()' function. All of the insects can +// then be responsible for printing themselves. And Doctor +// Zoraptera can calm down and stop gnawing on the furniture. // const std = @import("std"); @@ -9,7 +57,7 @@ const Ant = struct { still_alive: bool, pub fn print(self: Ant) void { - std.debug.print("Ant is {s}.\n", .{if (self.still_alive) "alive" else "death"}); + std.debug.print("Ant is {s}.\n", .{if (self.still_alive) "alive" else "dead"}); } }; @@ -21,11 +69,13 @@ const Bee = struct { } }; +// Here's the new grasshopper. Notice how we've also added print +// methods to each insect. const Grasshopper = struct { distance_hopped: u16, pub fn print(self: Grasshopper) void { - std.debug.print("Grasshopper hopped {} m.\n", .{self.distance_hopped}); + std.debug.print("Grasshopper hopped {} meters.\n", .{self.distance_hopped}); } }; @@ -34,6 +84,10 @@ const Insect = union(enum) { bee: Bee, grasshopper: Grasshopper, + // Thanks to 'inline else', we can think of this print() as + // being an interface method. Any member of this union with + // with a print() method can be treated uniformly by outside + // code without needing to know any other details. Cool! pub fn print(self: Insect) void { switch (self) { inline else => |case| return case.print(), @@ -42,27 +96,32 @@ const Insect = union(enum) { }; pub fn main() !void { - var my_insects = [_]Insect{ Insect{ - .ant = Ant{ .still_alive = true }, - }, Insect{ - .bee = Bee{ .flowers_visited = 17 }, - }, Insect{ - .grasshopper = Grasshopper{ .distance_hopped = 32 }, - } }; + var my_insects = [_]Insect{ + Insect{ .ant = Ant{ .still_alive = true } }, + Insect{ .bee = Bee{ .flowers_visited = 17 } }, + Insect{ .grasshopper = Grasshopper{ .distance_hopped = 32 }, }, + }; - // The daily situation report, what's going on in the garden - try dailyReport("what is here the right parameter?"); -} - -// Through the interface we can keep a list of various objects -// (in this case the insects of our garden) and even pass them -// to a function without having to know the specific properties -// of each or the object itself. This is really cool! -fn dailyReport(insectReport: []Insect) !void { - std.debug.print("Daily insect report:\n", .{}); - for (insectReport) |insect| { - insect.print(); + std.debug.print("Daily Insect Report:\n", .{}); + for (my_insects) |insect| { + // Almost done! We want to print() each insect with a + // single method call here. + ??? } } -// Interfaces... (explanation from Dave) +// Our print() method in the Insect union above demonstrates +// something very similar to the object-oriented concept of an +// abstract data type. That is, the Insect type doesn't contain +// the underlying data, and the print() function doesn't +// actually do the printing. +// +// The point of an interface is to support generic programming: +// the ability to treat different things as if they were the +// same to cut down on clutter and conceptual complexity. +// +// The Daily Insect Report doesn't need to worry about *which* +// insects are in the report - they all print the same way via +// the interface! +// +// Doctor Zoraptera loves it. diff --git a/patches/patches/092_interfaces.patch b/patches/patches/092_interfaces.patch index 67fc443..1287e79 100644 --- a/patches/patches/092_interfaces.patch +++ b/patches/patches/092_interfaces.patch @@ -1,4 +1,4 @@ -54c54 -< try dailyReport("what is here the right parameter?"); +109c109 +< ??? --- -> try dailyReport(&my_insects); +> insect.print();