Added story/explanation to new ex. 092

This commit is contained in:
Dave Gauer 2023-02-15 17:45:10 -05:00
parent beaa89fdf5
commit 662086cb89
3 changed files with 87 additions and 28 deletions

View file

@ -462,7 +462,7 @@ const exercises = [_]Exercise{
// }, // },
.{ .{
.main_file = "092_interfaces.zig", .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", .main_file = "999_the_end.zig",

View file

@ -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"); const std = @import("std");
@ -9,7 +57,7 @@ const Ant = struct {
still_alive: bool, still_alive: bool,
pub fn print(self: Ant) void { 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 { const Grasshopper = struct {
distance_hopped: u16, distance_hopped: u16,
pub fn print(self: Grasshopper) void { 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, bee: Bee,
grasshopper: Grasshopper, 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 { pub fn print(self: Insect) void {
switch (self) { switch (self) {
inline else => |case| return case.print(), inline else => |case| return case.print(),
@ -42,27 +96,32 @@ const Insect = union(enum) {
}; };
pub fn main() !void { pub fn main() !void {
var my_insects = [_]Insect{ Insect{ var my_insects = [_]Insect{
.ant = Ant{ .still_alive = true }, Insect{ .ant = Ant{ .still_alive = true } },
}, Insect{ Insect{ .bee = Bee{ .flowers_visited = 17 } },
.bee = Bee{ .flowers_visited = 17 }, Insect{ .grasshopper = Grasshopper{ .distance_hopped = 32 }, },
}, Insect{ };
.grasshopper = Grasshopper{ .distance_hopped = 32 },
} };
// The daily situation report, what's going on in the garden std.debug.print("Daily Insect Report:\n", .{});
try dailyReport("what is here the right parameter?"); for (my_insects) |insect| {
} // Almost done! We want to print() each insect with a
// single method call here.
// 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();
} }
} }
// 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.

View file

@ -1,4 +1,4 @@
54c54 109c109
< try dailyReport("what is here the right parameter?"); < ???
--- ---
> try dailyReport(&my_insects); > insect.print();