092_interfaces.zig 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. //
  2. // Remember our ant and bee simulator constructed with unions
  3. // back in exercises 55 and 56? There, we demonstrated that
  4. // unions allow us to treat different data types in a uniform
  5. // manner.
  6. //
  7. // One neat feature was using tagged unions to create a single
  8. // function to print a status for ants *or* bees by switching:
  9. //
  10. // switch (insect) {
  11. // .still_alive => ... // (print ant stuff)
  12. // .flowers_visited => ... // (print bee stuff)
  13. // }
  14. //
  15. // Well, that simulation was running just fine until a new insect
  16. // arrived in the virtual garden, a grasshopper!
  17. //
  18. // Doctor Zoraptera started to add grasshopper code to the
  19. // program, but then she backed away from her keyboard with an
  20. // angry hissing sound. She had realized that having code for
  21. // each insect in one place and code to print each insect in
  22. // another place was going to become unpleasant to maintain when
  23. // the simulation expanded to hundreds of different insects.
  24. //
  25. // Thankfully, Zig has another comptime feature we can use
  26. // to get out of this dilema called the 'inline else'.
  27. //
  28. // We can replace this redundant code:
  29. //
  30. // switch (thing) {
  31. // .a => |a| special(a),
  32. // .b => |b| normal(b),
  33. // .c => |c| normal(c),
  34. // .d => |d| normal(d),
  35. // .e => |e| normal(e),
  36. // ...
  37. // }
  38. //
  39. // With:
  40. //
  41. // switch (thing) {
  42. // .a => |a| special(a),
  43. // inline else |t| => normal(t),
  44. // }
  45. //
  46. // We can have special handling of some cases and then Zig
  47. // handles the rest of the matches for us.
  48. //
  49. // With this feature, you decide to make an Insect union with a
  50. // single uniform 'print()' function. All of the insects can
  51. // then be responsible for printing themselves. And Doctor
  52. // Zoraptera can calm down and stop gnawing on the furniture.
  53. //
  54. const std = @import("std");
  55. const Ant = struct {
  56. still_alive: bool,
  57. pub fn print(self: Ant) void {
  58. std.debug.print("Ant is {s}.\n", .{if (self.still_alive) "alive" else "dead"});
  59. }
  60. };
  61. const Bee = struct {
  62. flowers_visited: u16,
  63. pub fn print(self: Bee) void {
  64. std.debug.print("Bee visited {} flowers.\n", .{self.flowers_visited});
  65. }
  66. };
  67. // Here's the new grasshopper. Notice how we've also added print
  68. // methods to each insect.
  69. const Grasshopper = struct {
  70. distance_hopped: u16,
  71. pub fn print(self: Grasshopper) void {
  72. std.debug.print("Grasshopper hopped {} meters.\n", .{self.distance_hopped});
  73. }
  74. };
  75. const Insect = union(enum) {
  76. ant: Ant,
  77. bee: Bee,
  78. grasshopper: Grasshopper,
  79. // Thanks to 'inline else', we can think of this print() as
  80. // being an interface method. Any member of this union with
  81. // with a print() method can be treated uniformly by outside
  82. // code without needing to know any other details. Cool!
  83. pub fn print(self: Insect) void {
  84. switch (self) {
  85. inline else => |case| return case.print(),
  86. }
  87. }
  88. };
  89. pub fn main() !void {
  90. var my_insects = [_]Insect{
  91. Insect{ .ant = Ant{ .still_alive = true } },
  92. Insect{ .bee = Bee{ .flowers_visited = 17 } },
  93. Insect{ .grasshopper = Grasshopper{ .distance_hopped = 32 } },
  94. };
  95. std.debug.print("Daily Insect Report:\n", .{});
  96. for (my_insects) |insect| {
  97. // Almost done! We want to print() each insect with a
  98. // single method call here.
  99. ???
  100. }
  101. }
  102. // Our print() method in the Insect union above demonstrates
  103. // something very similar to the object-oriented concept of an
  104. // abstract data type. That is, the Insect type doesn't contain
  105. // the underlying data, and the print() function doesn't
  106. // actually do the printing.
  107. //
  108. // The point of an interface is to support generic programming:
  109. // the ability to treat different things as if they were the
  110. // same to cut down on clutter and conceptual complexity.
  111. //
  112. // The Daily Insect Report doesn't need to worry about *which*
  113. // insects are in the report - they all print the same way via
  114. // the interface!
  115. //
  116. // Doctor Zoraptera loves it.