-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathchapter4.html
More file actions
41 lines (40 loc) · 62.9 KB
/
chapter4.html
File metadata and controls
41 lines (40 loc) · 62.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<!DOCTYPE html>
<html>
<head>
<title>L.B.Stanza</title>
<link type="text/css" rel="stylesheet" href="resources/mainstyle.css">
<link type="text/css" rel="stylesheet" href="resources/documentation.css">
</head>
<body>
<table class="wrap">
<tr><td colspan="3" class="banner">
<a href="index.html">Home</a><a href="stanzabyexample.html">Table of Contents</a><a href="chapter3.html">Previous Chapter</a><a href="chapter5.html">Next Chapter</a>
</td></tr>
<tr>
<td class="nav">
<h1>NAVIGATION</h1>
<h2><a href="#anchor223">Architecting Programs</a></h2><h3><a href="#anchor38">A Shape Library</a></h3><h3><a href="#anchor39">Creating a New Shape</a></h3><h3><a href="#anchor40">Subtyping</a></h3><h4><a href="#anchor224">Code Cleanup</a></h4><h3><a href="#anchor41">Multis and Methods</a></h3><h4><a href="#anchor225">The Need for Extensibility</a></h4><h4><a href="#anchor226">defmulti and defmethod</a></h4><h4><a href="#anchor227">Program Listing</a></h4><h3><a href="#anchor42">Default Methods</a></h3><h3><a href="#anchor43">Underneath the Hood</a></h3><h3><a href="#anchor44">Intersection Types</a></h3><h4><a href="#anchor228">Multiple Parent Types</a></h4><h4><a href="#anchor229">Intersection Types as Arguments</a></h4><h3><a href="#anchor45">The Flexibility of Functions</a></h3><h3><a href="#anchor46">Fundamental and Derived Operations</a></h3><h3><a href="#anchor47">Multiple Dispatch</a></h3><h3><a href="#anchor48">Ambiguous Methods</a></h3><h3><a href="#anchor49">Revisiting Print</a></h3><h3><a href="#anchor50">The New Expression</a></h3><h4><a href="#anchor230">Instance Methods</a></h4><h4><a href="#anchor231">The Push and Pop Methods</a></h4><h3><a href="#anchor51">Constructor Functions</a></h3><h3><a href="#anchor52">Revisiting Defstruct</a></h3>
</td>
<td class="main">
<h1 id="anchor223">Architecting Programs</h1><p>Stanza's object system differs significantly from most other programming languages. Most other languages (e.g. Java, C#, Python, Ruby, Objective-C, C++, Swift, Scala, OCaml, etc.) employ a <span style="font-style:italic;">class</span> based object system. In a class based object system, each <span style="font-style:italic;">thing</span> in the program is represented using a class. For each <span style="font-style:italic;">ability</span> that a thing has, the user adds another method to its class. Classes have all the power in a class-based object system. Methods live inside classes.</p><p>In contrast, Stanza employs a <span style="font-style:italic;">class-less</span> object system. In Stanza, each <span style="font-style:italic;">thing</span> is represented as a type. There is a minimal set of fundamental operations that defines the behaviour of a type. After that, everything that can be <span style="font-style:italic;">done</span> with each thing is implemented as a simple function. Both types and functions have equal standing. Types do not live inside functions, nor do functions live inside types. The careful balance between these constructs is what gives Stanza its flexibility and architectural power.</p><h2 id="anchor38">A Shape Library</h2><p>In <code>shapes.stanza</code>, let's create a package for creating and manipulating two-dimensional shapes.</p><pre><code>defpackage shapes :<br> import core<br> import math<br><br>public defstruct Point :<br> x: Double<br> y: Double<br><br>public defstruct Circle :<br> x: Double<br> y: Double<br> radius: Double<br><br>public defn area (s:Point|Circle) -> Double :<br> match(s) :<br> (s:Point) : 0.0<br> (s:Circle) : PI * radius(s) * radius(s)<br><br>defmethod print (o:OutputStream, p:Point) :<br> print(o, "Point(%_, %_)" % [x(p), y(p)])<br><br>defmethod print (o:OutputStream, c:Circle) :<br> print(o, "Circle(%_, %_, radius = %_)" % [x(c), y(c), radius(c)])</code></pre><p>The <code>shapes</code> package contains struct definitions for points and circles, methods for printing them, as well as an <code>area</code> function for computing the areas of these shapes. It imports the <code>math</code> package to access the definition of the mathematical constant <code>PI</code>. </p><p>In <code>shapes-main.stanza</code>, as our main program, let's compute the total area of a bunch of shapes. </p><pre><code>defpackage shapes/main :<br> import core<br> import collections<br> import shapes<br><br>defn total-area (ss:Vector<Point|Circle>) :<br> var total = 0.0<br> for s in ss do :<br> total = total + area(s)<br> total <br><br>defn main () :<br> val ss = Vector<Point|Circle>()<br> add(ss, Point(1.0, 1.0))<br> add(ss, Circle(2.0, 2.0, 3.0))<br> add(ss, Circle(3.0, 0.0, 1.0))<br> println("Shapes:")<br> println(ss)<br> println("Total area = %_" % [total-area(ss)])<br><br>main()</code></pre><p>Compile and run the program by typing the following in the terminal.</p><pre><code>stanza shapes.stanza shapes-main.stanza -o shapes<br>./shapes</code></pre><p>It should print out</p><pre><code>Shapes:<br>[Point(1.000000000000000, 1.000000000000000)<br> Circle(2.000000000000000, 2.000000000000000, radius = 3.000000000000000)<br> Circle(3.000000000000000, 0.000000000000000, radius = 1.000000000000000)]<br>Total area = 31.415926535897931</code></pre><h2 id="anchor39">Creating a New Shape</h2><p>Our <code>shapes</code> package supports points and circles, but let's now extend it to support rectangles. What do we need to change in order to support another shape?</p><ol><li>First we need to define a <code>Rectangle</code> struct for representing rectangles.
</li><li>Next we need to provide custom printing behavior for rectangles.
</li><li>We need to change <code>area</code>'s type signature to now accept a <code>Point|Circle|Rectangle</code>.
</li><li>We need to add another branch to the implementation of <code>area</code> to support rectangles.
</li><li>We need to change <code>total-area</code>'s type signature to now accept a <code>Vector<Point|Circle|Rectangle></code>.
</li><li>We need to change how <code>ss</code> is created to allow it to also hold rectangles.
</li></ol><p>The first two items are straightforward so let's do that immediately.</p><pre><code>public defstruct Rectangle :<br> x: Double<br> y: Double<br> width: Double<br> height: Double<br><br>defmethod print (o:OutputStream, r:Rectangle) :<br> print(o, "Rectangle(%_, %_, size = %_ x %_)" %<br> [x(r), y(r), width(r), height(r)])</code></pre><p>A rectangle is defined by the coordinates of its bottom-left corner and its width and height.</p><p>The other items on the list are not hard to implement at present but it is clear that it is not a sustainable strategy. Here's how it would look.</p><p><code>area</code> in the <code>shapes</code> package is updated to</p><pre><code>public defn area (s:Point|Circle|Rectangle) -> Double :<br> match(s) :<br> (s:Point) : 0.0<br> (s:Circle) : PI * radius(s) * radius(s)<br> (s:Rectangle) : width(s) * height(s)</code></pre><p><code>total-area</code> in the <code>shapes/main</code> package is updated to</p><pre><code>defn total-area (ss:Vector<Point|Circle|Rectangle>) :<br> var total = 0.0<br> for s in ss do :<br> total = total + area(s)<br> total </code></pre><p>And the creation of <code>ss</code> in the <code>main</code> function is updated to</p><pre><code>val ss = Vector<Point|Circle|Rectangle>()</code></pre><p>It is not a pretty solution. Imagine if we had ten more types shapes to define. <code>area</code>'s type signature would quickly become unwieldy as we tack on more and more types to the argument. Every new shape requires manually editing the internals of the <code>area</code> function. Currently <code>area</code> is the only function defined on shapes, but what if there were a dozen more? Would we have to manually edit the internals of each of them? </p><p>By far, the worst aspect of the solution is the need to update the definition of the user's <code>total-area</code> function and <code>ss</code> vector. The user simply wants <code>total-area</code> to accept a vector of shapes. Which shapes? Well, <span style="font-style:italic;">any</span> shape! There must be a better way to express that than an explicit union containing the names of every single type of shape currently defined.</p><h2 id="anchor40">Subtyping</h2><p>Here is the definition of a new <span style="font-style:italic;">type</span> called <code>Shape</code>.</p><pre><code>public deftype Shape</code></pre><p>A <code>Shape</code> is a general representation of a two-dimensional shape. If an object has type <code>Shape</code>, then we know that it is definitely a shape, though we may not know which particular shape it is. </p><p>Here is how to annotate our <code>Point</code> struct as being a <span style="font-style:italic;">subtype</span> of <code>Shape</code>.</p><pre><code>public defstruct Point <: Shape :<br> x: Double<br> y: Double</code></pre><p>This annotation tells Stanza that all points are shapes. Thus if we write a function that requires <code>Shape</code> objects,</p><pre><code>defn its-a-shape (s:Shape) :<br> println("%_ is a shape!" % [s])</code></pre><p>then we are allowed to pass it <code>Point</code> objects. The following</p><pre><code>its-a-shape(Point(1.0, 2.0))</code></pre><p>compiles correctly and prints out</p><pre><code>Point(1.000000000000000, 2.000000000000000) is a shape!</code></pre><p>Note, however, that though all points are shapes, <span style="font-style:italic;">not</span> all shapes are points. Thus if we write a function that requires <code>Point</code> objects,</p><pre><code>defn its-a-point (p:Point) :<br> println("%_ is a point!" % [p])</code></pre><p>and try it to call it with a <code>Shape</code> object,</p><pre><code>var s:Shape<br>its-a-point(s)</code></pre><p>Stanza will give the following error.</p><pre><code>Cannot call function its-a-point of type Point -> False with arguments<br>of type (Shape).</code></pre><p>Thus the relationship</p><pre><code>Point <: Shape</code></pre><p>says that <code>Point</code> is a subtype of <code>Shape</code>, meaning that all <code>Point</code> objects are also <code>Shape</code> objects (but not vice versa). </p><h3 id="anchor224">Code Cleanup</h3><p>Now that we have a definition for <code>Shape</code>, let's indicate this relationship for all of our shape structs.</p><pre><code>public defstruct Point <: Shape :<br> x: Double<br> y: Double<br><br>public defstruct Circle <: Shape :<br> x: Double<br> y: Double<br> radius: Double<br><br>public defstruct Rectangle <: Shape :<br> x: Double<br> y: Double<br> width: Double<br> height: Double</code></pre><p>Now all of our shape structs are also subtypes of <code>Shape</code>. This allows us to clean up many of the type signatures, both in the <code>shapes</code> package and in the <code>shapes/main</code> package.</p><p>The type signature for <code>area</code> is simplified.</p><pre><code>public defn area (s:Shape) -> Double :<br> match(s) :<br> (s:Point) : 0.0<br> (s:Circle) : PI * radius(s) * radius(s)<br> (s:Rectangle) : width(s) * height(s)</code></pre><p>The type signature for <code>total-area</code> is simplified.</p><pre><code>defn total-area (ss:Vector<Shape>) :<br> var total = 0.0<br> for s in ss do :<br> total = total + area(s)<br> total </code></pre><p>And the creation of <code>ss</code> is simplified.</p><pre><code>val ss = Vector<Shape>()</code></pre><p>Notice that with these simplifications, items 3, 5, and 6 on our checklist for creating new shapes are no longer necessary.</p><h2 id="anchor41">Multis and Methods</h2><p>Our <code>shape</code> package is architecturally fairly complete at this point. It currently supports points, circles and rectangles, and we can calculate the area of each of them. If we need another shape, e.g. lines, then all we have to do is define a <code>Line</code> struct and edit <code>area</code> to support <code>Line</code> objects. </p><h3 id="anchor225">The Need for Extensibility</h3><p>There remains one limitation to our shapes library however. Suppose that we are the authors and maintainers in charge of the shapes library, and that there are many users who use the <code>shapes</code> package for their daily work. What should a user do if our library does not support a shape that he needs? This is a likely scenario, because it is implausible for us to have fully considered the shape needs of every user. And often, some user's needs are so specific that we don't <span style="font-style:italic;">want</span> to support it in the standard shapes library. It will just end up cluttering the library and confusing the rest of the users. For example, it seems inappropriate to support the <code>Salinon</code> shape in the standard library. </p><p>What we can do however, is to allow users to define their <span style="font-style:italic;">own</span> shapes. Then typical users can stay content using the standard shapes in the library, and power users can define their own shapes for their own use. </p><p>Users can <span style="font-style:italic;">almost</span> do this. They can create their own shape struct, and provide custom printing behaviour for it. Let us portray here a user working on greek architecture, and who has started defining his own extensions to the shape library in the file <code>greek-shapes.stanza</code>.</p><pre><code>defpackage greek-shapes :<br> import core<br> import shapes<br><br>public defstruct Salinon <: Shape :<br> x: Double<br> y: Double<br> outer-radius: Double<br> inner-radius: Double<br><br>defmethod print (o:OutputStream, s:Salinon) :<br> print(o, "Salinon(%_, %_, outer-radius = %_, inner-radius = %_)" % [<br> x(s), y(s), outer-radius(s), inner-radius(s)])</code></pre><p>The problem though is that the user has no way of extending <code>area</code> to support <code>Salinon</code> shapes, because that would require editing the code in the <code>shapes</code> package, which he does not have access to.</p><h3 id="anchor226">defmulti and defmethod</h3><p>The solution is to declare <code>area</code> not as a function but as a <span style="font-style:italic;">multi</span>.</p><pre><code>public defmulti area (s:Shape) -> Double</code></pre><p>Note that the definition of a multi does not include a body. It simply says that <code>area</code> is a multi that when called with a <code>Shape</code> returns a <code>Double</code>. </p><p>Here is how to <span style="font-style:italic;">attach</span> a method to a multi.</p><pre><code>defmethod area (p:Point) -> Double :<br> 0.0</code></pre><p>This definition tells Stanza that when the <code>area</code> multi is called on a <code>Point</code> then simply return <code>0.0</code>. </p><p>Here are the methods for <code>area</code> for circles and rectangles.</p><pre><code>defmethod area (c:Circle) :<br> PI * radius(c) * radius(c)<br><br>defmethod area (r:Rectangle) :<br> width(r) * height(r)</code></pre><p>Notice that similar to functions, if the return type is not given, then it is inferred from the method body. </p><p>A multi can have any number of methods, and the methods can be distributed across any number of packages and source files. Thus our greek architect can now define a method for <code>area</code> for <code>Salinon</code> shapes in his own <code>greek-shapes</code> package.</p><pre><code>defpackage greek-shapes :<br> import core<br> import shapes<br> import math<br><br>...<br><br>defmethod area (s:Salinon) :<br> val r = outer-radius(s) + inner-radius(s)<br> PI * r * r / 4.0</code></pre><p>You'll just have to trust me on the area of a salinon.</p><p>Let's go back to our main program now and include a couple of salinons in our <code>ss</code> vector.</p><pre><code>defpackage shapes/main :<br> import core<br> import collections<br> import shapes<br> import greek-shapes<br><br>...<br><br>defn main () :<br> val ss = Vector<Shape>()<br> add(ss, Point(1.0, 1.0))<br> add(ss, Circle(2.0, 2.0, 3.0))<br> add(ss, Circle(3.0, 0.0, 1.0))<br> add(ss, Salinon(0.0, 0.0, 10.0, 5.0))<br> add(ss, Salinon(5.0, -1.0, 8.0, 7.0))<br> println("Shapes:")<br> println(ss)<br> println("Total area = %_" % [total-area(ss)])<br><br>...</code></pre><p>Compile and run the program by typing</p><pre><code>stanza shapes.stanza greek-shapes.stanza shapes-main.stanza -o shapes<br>./shapes</code></pre><p>The program should print out</p><pre><code>Shapes:<br>[Point(1.000000000000000, 1.000000000000000)<br> Circle(2.000000000000000, 2.000000000000000, radius = 3.000000000000000)<br> Circle(3.000000000000000, 0.000000000000000, radius = 1.000000000000000)<br> Salinon(0.000000000000000, 0.000000000000000,<br> outer-radius = 10.000000000000000, inner-radius = 5.000000000000000)<br> Salinon(5.000000000000000, -1.000000000000000,<br> outer-radius = 8.000000000000000, inner-radius = 7.000000000000000)]<br>Total area = 384.845100064749658</code></pre><p>The following listing contains the complete program.</p><h3 id="anchor227">Program Listing</h3><p>In <code>shapes.stanza</code></p><pre><code>defpackage shapes :<br> import core<br> import math<br><br>public deftype Shape<br><br>public defstruct Point <: Shape :<br> x: Double<br> y: Double<br><br>public defstruct Circle <: Shape :<br> x: Double<br> y: Double<br> radius: Double<br><br>public defstruct Rectangle <: Shape :<br> x: Double<br> y: Double<br> width: Double<br> height: Double<br><br>defmethod print (o:OutputStream, p:Point) :<br> print(o, "Point(%_, %_)" % [x(p), y(p)])<br><br>defmethod print (o:OutputStream, c:Circle) :<br> print(o, "Circle(%_, %_, radius = %_)" % [x(c), y(c), radius(c)])<br><br>defmethod print (o:OutputStream, r:Rectangle) :<br> print(o, "Rectangle(%_, %_, size = %_ x %_)" %<br> [x(r), y(r), width(r), height(r)])<br><br>public defmulti area (s:Shape) -> Double<br><br>defmethod area (p:Point) -> Double :<br> 0.0<br><br>defmethod area (c:Circle) :<br> PI * radius(c) * radius(c)<br><br>defmethod area (r:Rectangle) :<br> width(r) * height(r)</code></pre><p>In <code>greek-shapes.stanza</code></p><pre><code>defpackage greek-shapes :<br> import core<br> import shapes<br> import math<br><br>public defstruct Salinon <: Shape :<br> x: Double<br> y: Double<br> outer-radius: Double<br> inner-radius: Double<br><br>defmethod print (o:OutputStream, s:Salinon) :<br> print(o, "Salinon(%_, %_, outer-radius = %_, inner-radius = %_)" % [<br> x(s), y(s), outer-radius(s), inner-radius(s)])<br><br>defmethod area (s:Salinon) :<br> val r = outer-radius(s) + inner-radius(s)<br> PI * r * r / 4.0</code></pre><p>In <code>shapes-main.stanza</code></p><pre><code>defpackage shapes/main :<br> import core<br> import collections<br> import shapes<br> import greek-shapes<br><br>defn total-area (ss:Vector<Shape>) :<br> var total = 0.0<br> for s in ss do :<br> total = total + area(s)<br> total <br><br>defn main () :<br> val ss = Vector<Shape>()<br> add(ss, Point(1.0, 1.0))<br> add(ss, Circle(2.0, 2.0, 3.0))<br> add(ss, Circle(3.0, 0.0, 1.0))<br> add(ss, Salinon(0.0, 0.0, 10.0, 5.0))<br> add(ss, Salinon(5.0, -1.0, 8.0, 7.0))<br> println("Shapes:")<br> println(ss)<br> println("Total area = %_" % [total-area(ss)])<br><br>main()</code></pre><h2 id="anchor42">Default Methods</h2><p>Our current implementation of <code>area</code> does not have a <span style="font-style:italic;">default</span> method. This means that if we call <code>area</code> with a shape that has no appropriate method, then the program will crash. Let's try this by commenting out the method for computing the areas of salinons and try running our program again.</p><pre><code>;defmethod area (s:Salinon) :<br>; val r = outer-radius(s) + inner-radius(s)<br>; PI * r * r / 4.0</code></pre><p>It should print out</p><pre><code>Shapes:<br>[Point(1.000000000000000, 1.000000000000000)<br> Circle(2.000000000000000, 2.000000000000000, radius = 3.000000000000000)<br> Circle(3.000000000000000, 0.000000000000000, radius = 1.000000000000000)<br> Salinon(0.000000000000000, 0.000000000000000,<br> outer-radius = 10.000000000000000, inner-radius = 5.000000000000000)<br> Salinon(5.000000000000000, -1.000000000000000,<br> outer-radius = 8.000000000000000, inner-radius = 7.000000000000000)]<br>FATAL ERROR: No matching branch.<br> at shapes.stanza:31.16<br> at shapes-main.stanza:10.22<br> at core/collections.stanza:182.15<br> at core/core.stanza:4042.16<br> at shapes-main.stanza:9.15<br> at shapes-main.stanza:22.32<br> at shapes-main.stanza:24.0</code></pre><p>Let's instead provide a <span style="font-style:italic;">default</span> method that is called when no other method matches. Add the following method to the <code>shapes</code> package</p><pre><code>defmethod area (s:Shape) :<br> println("No appropriate area method for %_." % [s])<br> println("Returning 0.0.")<br> 0.0</code></pre><p>and run the program again. It should now print out</p><pre><code>Shapes:<br>[Point(1.000000000000000, 1.000000000000000)<br> Circle(2.000000000000000, 2.000000000000000, radius = 3.000000000000000)<br> Circle(3.000000000000000, 0.000000000000000, radius = 1.000000000000000)<br> Salinon(0.000000000000000, 0.000000000000000,<br> outer-radius = 10.000000000000000, inner-radius = 5.000000000000000)<br> Salinon(5.000000000000000, -1.000000000000000,<br> outer-radius = 8.000000000000000, inner-radius = 7.000000000000000)]<br>No appropriate area method for<br> Salinon(0.000000000000000, 0.000000000000000,<br> outer-radius = 10.000000000000000, inner-radius = 5.000000000000000).<br>Returning 0.0.<br>No appropriate area method for<br> Salinon(5.000000000000000, -1.000000000000000,<br> outer-radius = 8.000000000000000, inner-radius = 7.000000000000000).<br>Returning 0.0.<br>Total area = 31.415926535897931</code></pre><p>Default methods are often used to return a default value when no other method is appropriate. Another common use case for default methods is to provide a slow but general implementation of a certain function that works on any type in its domain, and then use methods to provide efficient implementations for specialized types. </p><h2 id="anchor43">Underneath the Hood</h2><p>To help you better understand how the multi and method system works, here is what is happening underneath the hood. When a Stanza program is compiled it searches through all the packages and gathers up all the methods defined for each multi. In our <code>shapes</code> example, that gives us</p><pre><code>public defmulti area (s:Shape) -> Double<br>defmethod area (s:Shape) :<br> println("No appropriate area method for %_." % [s])<br> println("Returning 0.0.")<br> 0.0<br>defmethod area (p:Point) -> Double :<br> 0.0<br>defmethod area (c:Circle) :<br> PI * radius(c) * radius(c)<br>defmethod area (r:Rectangle) :<br> width(r) * height(r)<br>defmethod area (s:Salinon) :<br> val r = outer-radius(s) + inner-radius(s)<br> PI * r * r / 4.0</code></pre><p>These methods are then <span style="font-style:italic;">sorted</span> from most specific to least specific, and the multi is transformed into a single function with a match expression for selecting which method to call.</p><pre><code>public defn area (s:Shape) -> Double :<br> match(s) :<br> (p:Point) :<br> 0.0<br> (c:Circle) :<br> PI * radius(c) * radius(c)<br> (r:Rectangle) :<br> width(r) * height(r)<br> (s:Salinon) :<br> val r = outer-radius(s) + inner-radius(s)<br> PI * r * r / 4.0<br> (s:Shape) :<br> println("No appropriate area method for %_." % [s])<br> println("Returning 0.0.")<br> 0.0</code></pre><p>Notice how the default method for <code>Shape</code> is positioned as the last branch in the match expression as it is the least specific method. </p><p>Thus this engine demonstrates that Stanza's multi and method system can simply be thought of as a way of writing match expressions but with its branches distributed across multiple packages.</p><h2 id="anchor44">Intersection Types</h2><p></p><h3 id="anchor228">Multiple Parent Types</h3><p>Suppose we have a type called <code>Rollable</code> with the following multi declared.</p><pre><code>deftype Rollable<br>defmulti roll-distance (r:Rollable) -> Double</code></pre><p><code>roll-distance</code> computes the distance traveled by a <code>Rollable</code> object in one revolution.</p><p>We now wish to make <code>Circle</code> a subtype of <code>Rollable</code> and provide it an appropriate method for <code>roll-distance</code>, but <code>Circle</code> is already declared to be a subtype of <code>Shape</code>! The solution is to declare <code>Circle</code> as both a subtype of <code>Shape</code> <span style="font-style:italic;">and</span> <code>Rollable</code>. </p><pre><code>public defstruct Circle <: Shape & Rollable :<br> x: Double<br> y: Double<br> radius: Double</code></pre><p>This is an example of using an <span style="font-style:italic;">intersection type</span>. Now we can provide a method for <code>roll-distance</code> on <code>Circle</code> objects.</p><pre><code>defmethod roll-distance (c:Circle) :<br> 2.0 * PI * radius(c)</code></pre><p>A circle rolls exactly the length of its diameter in one revolution.</p><p>Let's try it out!</p><pre><code>defn try-rolling () :<br> val c = Circle(0.0, 0.0, 10.0)<br> println("The circle %_ rolls %_ units in one revolution." % [c, roll-distance(c)])<br><br>try-rolling()</code></pre><p>Compiling and running the above prints out</p><pre><code>The circle Circle(0.000000000000000, 0.000000000000000,<br> radius = 10.000000000000000) <br>rolls 62.831853071795862 units in one revolution.</code></pre><h3 id="anchor229">Intersection Types as Arguments</h3><p>Let's suppose that we're about to make a pizza, and we need to choose the shape of its base. Additionally, we're experimenting with new pizza delivery methods, and would also like the pizza to be able to roll.</p><p>Here's the function that makes our pizza.</p><pre><code>defn make-pizza (base:Shape & Rollable) :<br> println("Let's make a pizza!")<br> println("The base will have shape %_." % [base])<br> println("And it will have area %_." % [area(base)])<br> println("Plus when we roll it, it travels %_ units!" % [roll-distance(base)])</code></pre><p>Because we call <code>area</code> on <code>base</code> we know that the <code>base</code> needs to be a type of <code>Shape</code> object. But we also call <code>roll-distance</code> on <code>base</code>, and so <code>base</code> will <span style="font-style:italic;">also</span> have to be a type of <code>Rollable</code> object. Thus <code>base</code> is declared to be both <code>Shape</code> and <code>Rollable</code>.</p><p>Let's try it out!</p><pre><code>make-pizza(Circle(0.0, 0.0, 10.0))</code></pre><p>Compiling and running the above prints out</p><pre><code>Let's make a pizza!<br>The base will have shape Circle(0.000000000000000, 0.000000000000000, <br> radius = 10.000000000000000).<br>And it will have area 314.159265358979326.<br>Plus when we roll it, it travels 62.831853071795862 units!</code></pre><p>So circular pizzas will be our first foray into rolling self-delivering pizzas!</p><p>The argument that <code>make-pizza</code> requires needs to be both a <code>Shape</code> and a <code>Rollable</code>. We do have other shapes available that are not <code>Rollable</code>. Here is what happens if we try to make a rectangular pizza for example.</p><pre><code>make-pizza(Rectangle(0.0, 0.0, 5.0, 3.0))</code></pre><p>Attempting to compile the above gives us the following error.</p><pre><code>Cannot call function make-pizza of type Rollable&Shape -> False <br>with arguments of type (Rectangle).</code></pre><p>Thus Stanza correctly prevents us from attempting to make pizzas out of shapes that don't roll.</p><h2 id="anchor45">The Flexibility of Functions</h2><p>In the beginning of the chapter we said that Stanza's class-less object system gives you flexibility you wouldn't have in a typical class based language. Here is a demonstration of that. </p><p>The definition of the <code>Circle</code> struct in the <code>shapes</code> package defines circles using their x and y coordinates, and their radius. But what if, as a user, we don't like this convention and instead want to define circles given a <code>Point</code> to represent their center, and their diameter? Stanza allows us to easily do that.</p><p>Here is <code>shape-utils.stanza</code>, which contains a user defined package with his own utilities for managing shapes. </p><pre><code>defpackage shape-utils :<br> import core<br> import shapes<br><br>public defn Circle (center:Point, diameter:Double) :<br> Circle(x(center), y(center), diameter / 2.0)<br><br>public defn diameter (c:Circle) :<br> radius(c) * 2.0<br><br>public defn center (c:Circle) :<br> Point(x(c), y(c))</code></pre><p>With this package, users can now use circles as if they were defined with a center point and a diameter instead of a pair of x, and y coordinates and a radius. Let's update our <code>main</code> function to use this new convention.</p><pre><code>defpackage shapes/main :<br> import core<br> import collections<br> import shapes<br> import greek-shapes<br> import shape-utils<br><br>...<br> <br>defn main () :<br> val ss = Vector<Shape>()<br> add(ss, Point(1.0, 1.0))<br> add(ss, Circle(Point(2.0, 2.0), 6.0))<br> add(ss, Circle(Point(3.0, 0.0), 2.0))<br> add(ss, Salinon(0.0, 0.0, 10.0, 5.0))<br> add(ss, Salinon(5.0, -1.0, 8.0, 7.0))<br> println("Shapes:")<br> println(ss)<br> println("Total area = %_" % [total-area(ss)])</code></pre><p>Run the program to see that it continues to print out the same message as before.</p><pre><code>Shapes:<br>[Point(1.000000000000000, 1.000000000000000)<br> Circle(2.000000000000000, 2.000000000000000, radius = 3.000000000000000)<br> Circle(3.000000000000000, 0.000000000000000, radius = 1.000000000000000)<br> Salinon(0.000000000000000, 0.000000000000000,<br> outer-radius = 10.000000000000000, inner-radius = 5.000000000000000)<br> Salinon(5.000000000000000, -1.000000000000000,<br> outer-radius = 8.000000000000000, inner-radius = 7.000000000000000)]<br>Total area = 384.845100064749658</code></pre><p>Note that the <code>center</code> and <code>diameter</code> functions in the <code>shape-utils</code> package are no less "special" or "fundamental" than the <code>x</code>, <code>y</code>, and <code>radius</code> functions in the <code>shapes</code> package. Users can use whichever representation they prefer. Most importantly, adding this functionality did not require the user to communicate with the authors of the <code>shapes</code> package at all. </p><p>We can similarly add support for a new representation of rectangles. Currently, a rectangle is represented using the x, and y coordinates of its bottom-left corner and its width and height. Let's add support for representing rectangles given its bottom-left point and its top-right point.</p><pre><code>public defn Rectangle (bottom-left:Point, top-right:Point) :<br> val w = x(top-right) - x(bottom-left)<br> val h = y(top-right) - y(bottom-left)<br> if (w < 0.0) or (h < 0.0) : fatal("Illegal Rectangle!")<br> Rectangle(x(bottom-left), y(bottom-left), w, h)<br> <br>public defn bottom-left (r:Rectangle) :<br> Point(x(r), y(r))<br><br>public defn top-right (r:Rectangle) :<br> Point(x(r) + width(r), y(r) + height(r))</code></pre><p>Again, the <code>bottom-left</code> and <code>top-right</code> functions in the <code>shape-utils</code> package are no less "fundamental" than the <code>x</code>, <code>y</code>, <code>width</code>, and <code>height</code> functions in the <code>shapes</code> package.</p><h2 id="anchor46">Fundamental and Derived Operations</h2><p><span style="font-style:italic;">Things</span> in your program are modeled using <span style="font-style:italic;">types</span> in Stanza. Anything than can be <span style="font-style:italic;">done</span> using a type is implemented as a function (or multi). These operations are further categorized into <span style="font-style:italic;">fundamental</span> and <span style="font-style:italic;">derived</span> operations. </p><p>The <code>area</code> function for <code>Shape</code> objects is an example of a fundamental operation. At least in our <code>shapes</code> package, a shape is <span style="font-style:italic;">defined</span> by its property of having an area. In fact, the <span style="font-style:italic;">only</span> thing that all shapes have in common is that you can compute their area. And when defining a new type of shape, users <span style="font-style:italic;">must</span> also define an appropriate method for <code>area</code>. </p><p>Here is an example of a <span style="font-style:italic;">derived</span> operation on shapes.</p><pre><code>public defn sort-by-area (xs:Array<Shape>) :<br> var sorted? = false<br> while not sorted? :<br> sorted? = true<br> for i in 0 to (length(xs) - 1) do :<br> if area(xs[i + 1]) < area(xs[i]) :<br> val a = xs[i]<br> val b = xs[i + 1]<br> xs[i] = b<br> xs[i + 1] = a<br> sorted? = false </code></pre><p>This operation allows you to sort an array of shapes by increasing area. By reading its definition, you can see that it will work on <span style="font-style:italic;">all</span> shapes, because the only operation it requires is the ability to call <code>area</code>. A derived operation is a function implemented in terms of a type's fundamental operations. </p><p>When defining a new subtype of an existing type, users must implement a small set of fundamental operations to ensure correct operation of their subtype. In the core library documentation, this set is called the <span style="font-style:italic;">mandatory minimal implementation</span>. </p><p>The typical architecture of a Stanza program is to define a <span style="font-style:italic;">small</span> number of fundamental operations for each type, coupled with a <span style="font-style:italic;">large</span> library of derived operations. Structuring your program in this way gives you the most flexibility and extensibility. Adding new derived operations is as simple as defining a new function and is very easy. Defining new types is also easy as their mandatory minimal implementations are small.</p><h2 id="anchor47">Multiple Dispatch</h2><p>The <code>area</code> multi in the <code>shapes</code> package accepts only a single argument, and at runtime it <span style="font-style:italic;">dispatches</span> to the appropriate method depending on the type of the argument. Stanza places no restriction on the number of arguments that a multi can take, so users can write multis that dispatches to the appropriate method depending on the types of <span style="font-style:italic;">multiple</span> arguments. This feature is called <span style="font-style:italic;">multiple dispatch</span>. </p><p>We will demonstrate the power of multiple dispatch by writing an <span style="font-style:italic;">overlaps?</span> function that decides whether two shapes are overlapping. Here is the definition of the multi.</p><pre><code>public defmulti overlaps? (a:Shape, b:Shape) -> True|False</code></pre><p>It returns <code>true</code> if the shape <code>a</code> overlaps with shape <code>b</code>, or <code>false</code> otherwise.</p><p>Points have zero area, so two points overlap only if they are exactly equal to each other. </p><pre><code>defmethod overlaps? (a:Point, b:Point) :<br> (x(a) == x(b)) and (y(a) == y(b))</code></pre><p>A circle overlaps with a point if the distance between the point and the center of the circle is less than or equal to the radius of the circle.</p><pre><code>defmethod overlaps? (a:Point, b:Circle) :<br> val dx = x(b) - x(a)<br> val dy = y(b) - y(a)<br> val r = radius(b)<br> dx * dx + dy * dy <= r * r</code></pre><p>Stanza makes no assumption that <code>overlaps?</code> is commutative. So we explicitly tell Stanza that a circle overlaps with a point if the point overlaps with the circle.</p><pre><code>defmethod overlaps? (a:Circle, b:Point) :<br> overlaps?(b, a)</code></pre><p>Finally, two circles overlap if the center of circle <code>a</code> overlaps with a circle with the same center as circle <code>b</code> but with radius equal to the sum of both circles.</p><pre><code>defmethod overlaps? (a:Circle, b:Circle) :<br> overlaps?(Point(x(a), y(a)), Circle(x(b), y(b), radius(b) + radius(a)))</code></pre><p>With these definitions, <code>overlaps?</code> completely handles points and circles. Let's try it out.</p><pre><code>defn test-overlap (a:Shape, b:Shape) :<br> println(a)<br> if overlaps?(a, b) : println("overlaps with")<br> else : println("does not overlap with")<br> println(b)<br> print("\n")<br> <br>defn try-overlaps () :<br> val pt-a = Point(0.0, 0.0)<br> val pt-b = Point(0.0, 3.0)<br> val circ-a = Circle(0.0, 3.0, 1.0)<br> val circ-b = Circle(3.0, 0.0, 1.0)<br> val circ-c = Circle(0.0, 0.0, 3.0)<br> test-overlap(pt-a, pt-b)<br> test-overlap(circ-a, circ-b)<br> test-overlap(pt-b, circ-b)<br> test-overlap(circ-a, pt-b)<br> test-overlap(circ-c, circ-a)<br><br>try-overlaps()</code></pre><p>The above prints out</p><pre><code>Point(0.000000000000000, 0.000000000000000)<br>does not overlap with<br>Point(0.000000000000000, 3.000000000000000)<br><br>Circle(0.000000000000000, 3.000000000000000, radius = 1.000000000000000)<br>does not overlap with<br>Circle(3.000000000000000, 0.000000000000000, radius = 1.000000000000000)<br><br>Point(0.000000000000000, 3.000000000000000)<br>does not overlap with<br>Circle(3.000000000000000, 0.000000000000000, radius = 1.000000000000000)<br><br>Circle(0.000000000000000, 3.000000000000000, radius = 1.000000000000000)<br>overlaps with<br>Point(0.000000000000000, 3.000000000000000)<br><br>Circle(0.000000000000000, 0.000000000000000, radius = 3.000000000000000)<br>overlaps with<br>Circle(0.000000000000000, 3.000000000000000, radius = 1.000000000000000)</code></pre><p>As an exercise, you may try to implement the rest of the methods required for <code>overlaps?</code> to also work on rectangles. The brave and adventurous amongst you can try supporting salinons as well.</p><h2 id="anchor48">Ambiguous Methods</h2><p>A multi dispatches to the most <span style="font-style:italic;">specific</span> method appropriate for the types of the arguments. However, there are sometimes multiple methods that are equally specific, and it is ambiguous which method should be called. </p><p>As an example, consider the very strange shape, the <code>Blob</code>. The blob has the very strange property that <span style="font-style:italic;">it</span> overlaps with no shape, but <span style="font-style:italic;">every</span> shape overlaps with <span style="font-style:italic;">it</span>. </p><pre><code>defstruct Blob <: Shape<br>defmethod print (o:OutputStream, b:Blob) : print(o, "Amorphous Blob")<br>defmethod overlaps? (a:Blob, b:Shape) : false<br>defmethod overlaps? (a:Shape, b:Blob) : true</code></pre><p>Let's try it out.</p><pre><code>defn try-overlaps () :<br> val pt-a = Point(0.0, 0.0)<br> val pt-b = Point(0.0, 3.0)<br> val circ-a = Circle(0.0, 3.0, 1.0)<br> val circ-b = Circle(3.0, 0.0, 1.0)<br> val circ-c = Circle(0.0, 0.0, 3.0)<br> val blob = Blob()<br> test-overlap(pt-a, blob)<br> test-overlap(circ-a, blob)<br> test-overlap(blob, pt-b)<br> test-overlap(blob, circ-b)</code></pre><p>The program prints out</p><pre><code>Point(0.000000000000000, 0.000000000000000)<br>overlaps with<br>Amorphous Blob<br><br>Circle(0.000000000000000, 3.000000000000000, radius = 1.000000000000000)<br>overlaps with<br>Amorphous Blob<br><br>Amorphous Blob<br>does not overlap with<br>Point(0.000000000000000, 3.000000000000000)<br><br>Amorphous Blob<br>does not overlap with<br>Circle(3.000000000000000, 0.000000000000000, radius = 1.000000000000000)</code></pre><p>But the real question is: does a blob overlap with a blob? Let's see.</p><pre><code>defn try-overlaps () :<br> val pt-a = Point(0.0, 0.0)<br> val pt-b = Point(0.0, 3.0)<br> val circ-a = Circle(0.0, 3.0, 1.0)<br> val circ-b = Circle(3.0, 0.0, 1.0)<br> val circ-c = Circle(0.0, 0.0, 3.0)<br> val blob = Blob()<br> test-overlap(pt-a, blob)<br> test-overlap(circ-a, blob)<br> test-overlap(blob, pt-b)<br> test-overlap(blob, circ-b)<br> test-overlap(blob, blob)</code></pre><p>prints out</p><pre><code>Point(0.000000000000000, 0.000000000000000)<br>overlaps with<br>Amorphous Blob<br><br>Circle(0.000000000000000, 3.000000000000000, radius = 1.000000000000000)<br>overlaps with<br>Amorphous Blob<br><br>Amorphous Blob<br>does not overlap with<br>Point(0.000000000000000, 3.000000000000000)<br><br>Amorphous Blob<br>does not overlap with<br>Circle(3.000000000000000, 0.000000000000000, radius = 1.000000000000000)<br><br>Amorphous Blob<br>does not overlap with<br>Amorphous Blob</code></pre><p>While there are two equally specific <code>overlaps?</code> methods, Stanza resolves the ambiguity by prioritizing function arguments from left-to-right. Since <code>Blob</code> is a subtype of <code>Shape</code>, and therefore stronger, Stanza uses the <code>overlaps?</code> instance where the first argument is a <code>Blob</code>. To verify this, try swapping the method bodies.</p><h2 id="anchor49">Revisiting Print</h2><p>Now that you've been introduced to multis and methods, we can remove some of the mysteries surrounding the <code>print</code> function. So far, you've been told to follow a specific pattern to provide custom printing behaviour for your custom structs. For example, here is the print method defined for circles.</p><pre><code>defmethod print (o:OutputStream, c:Circle) :<br> print(o, "Circle(%_, %_, radius = %_)" % [x(c), y(c), radius(c)])</code></pre><p>But now you can see that it is simply attaching a new method to a multi called <code>print</code>. The <code>print</code> multi is defined in the <code>core</code> package</p><pre><code>defmulti print (o:OutputStream, x) -> False</code></pre><p>and takes two arguments. The first is an <code>OutputStream</code> object that represents the <span style="font-style:italic;">target</span> that you're printing to. The most common target is the standard output stream, i.e. the user's terminal. The second argument is the object that you're printing. </p><p>Thus far, you've only provided <code>print</code> methods for more specific types of <code>x</code> in order to print different types of objects. But later, you'll see how you can provide <code>print</code> methods for more specific types of <code>o</code> in order to print to different targets. And all of this works seamlessly due to the power of multiple dispatch.</p><h2 id="anchor50">The New Expression</h2><p>The new expression is Stanza's fundamental construct for creating objects. All objects in Stanza are either literals (e.g. 1, 'x', "Timon"), or are created (directly or indirectly) by the new expression. </p><p>Let's define a type called <code>Stack</code> that represents a stack into which we can push and pop strings. Start a new file called <code>stack.stanza</code>. Here's the type definition for <code>Stack</code> and also two multis for the push and pop operations to which we will later attach methods, and a third multi for checking whether the stack is empty.</p><pre><code>deftype Stack<br>defmulti push (s:Stack, x:String) -> False<br>defmulti pop (s:Stack) -> String<br>defmulti empty? (s:Stack) -> True|False</code></pre><p>Let's provide it with custom printing behaviour.</p><pre><code>defmethod print (o:OutputStream, s:Stack) :<br> print(o, "Stack")</code></pre><p>Now in our main function we will create a single <code>Stack</code> object and print it out.</p><pre><code>defn main () :<br> val s = new Stack<br> println("s is a %_." % [s])<br><br>main()</code></pre><p>Compile the program and run it. It should print out</p><pre><code>s is a Stack.</code></pre><p>Thus the expression</p><pre><code>new Stack</code></pre><p>creates a new <code>Stack</code> object. We say that it creates a new <span style="font-style:italic;">instance</span> of type <code>Stack</code>. </p><p>But this stack object thus far isn't terribly useful. The only thing it can do is print itself. Stanza does allow us to call <code>push</code> and <code>pop</code> on the stack, but it will just crash because we haven't attached any methods yet. </p><h3 id="anchor230">Instance Methods</h3><p>The new expression allows us to define <span style="font-style:italic;">instance</span> methods for the object being created. Here is an instance method for the <code>empty?</code> multi for the stack being created.</p><pre><code>defn main () :<br> val s = new Stack :<br> defmethod empty? (this) :<br> true<br> println("s is a %_." % [s])<br> println("stack is empty? %_." % [empty?(s)])<br><br>main()</code></pre><p>We haven't defined any methods for pushing strings to the stack yet, so the <code>empty?</code> method simply returns <code>true</code> for now. Compile the program and run it. It should print out</p><pre><code>s is a Stack.<br>stack is empty? true.</code></pre><p>The instance method declaration looks similar to the standard method declarations that you've already learned except for one major difference. The <span style="font-style:italic;">this</span> argument is very special. In an instance method declaration, <code>this</code> refers specifically to the object currently being created by new. In this case, the object being created is <code>s</code>. So the instance method is saying: if <code>empty?</code> is called on <code>s</code> then return <code>true</code>. Exactly one of the arguments of every instance method must be named <code>this</code>.</p><p>In fact, now that we've learned about instance methods, let's redefine the <code>print</code> method as an instance method for <code>s</code>. Delete the top-level print method, and add the following.</p><pre><code>defn main () :<br> val s = new Stack :<br> defmethod empty? (this) :<br> true<br> defmethod print (o:OutputStream, this) :<br> print(o, "Stack")<br> println("s is a %_." % [s])<br> println("stack is empty? %_." % [empty?(s)])<br><br>main()</code></pre><p>Compile and run the program and verify that it prints the same message as before.</p><h3 id="anchor231">The Push and Pop Methods</h3><p>We will now define the methods for <code>push</code> and <code>pop</code>. The stack contents will be held in an array, and we'll keep track of how many items are currently in the stack using a <code>size</code> variable. The array will be of length 10, so the maximum number of strings that the stack can hold is 10. Declare the following <span style="font-style:italic;">within</span> the <code>main</code> function.</p><pre><code>val items = Array<String>(10)<br>var size = 0</code></pre><p>Next we'll declare the <code>push</code> method. Declare the following within the new expression.</p><pre><code>defmethod push (this, x:String) :<br> if size == 10 : fatal("Stack is full!")<br> items[size] = x<br> size = size + 1</code></pre><p>The <code>push</code> method first ensures that the stack is not full. Then it stores <code>x</code> in the next slot in the array and increments the stack's size by one.</p><p>Here's the corresponding <code>pop</code> method.</p><pre><code>defmethod pop (this) :<br> if size == 0 : fatal("Stack is empty!")<br> size = size - 1<br> items[size]</code></pre><p>The <code>pop</code> method first ensures that the stack is not empty. Then it decrements the stack's size by one, and returns the top item in the stack.</p><p>Here's the revised <code>empty?</code> method.</p><pre><code>defmethod empty? (this) :<br> size == 0</code></pre><p>The stack is empty if its size is zero.</p><p>And finally, here's the revised <code>print</code> method.</p><pre><code>defmethod print (o:OutputStream, this) :<br> print(o, "Stack containing [")<br> for i in 0 to size do :<br> print(o, items[i])<br> if i < size - 1 :<br> print(o, " ")<br> print(o, "]") </code></pre><p>It iterates through and prints all the strings currently in the stack.</p><p>Putting all the pieces together gives us the following <code>main</code> function. To test the stack, we try pushing and popping a few strings.</p><pre><code>defn main () :<br> val items = Array<String>(10)<br> var size = 0<br> val s = new Stack :<br> defmethod push (this, x:String) :<br> if size == 10 : fatal("Stack is full!")<br> items[size] = x<br> size = size + 1<br> defmethod pop (this) :<br> if size == 0 : fatal("Stack is empty!")<br> size = size - 1<br> items[size]<br> defmethod empty? (this) :<br> size == 0<br> defmethod print (o:OutputStream, this) :<br> print(o, "Stack containing [")<br> for i in 0 to size do :<br> print(o, items[i])<br> if i < size - 1 :<br> print(o, " ")<br> print(o, "]") <br><br> println("1.")<br> println(s)<br><br> println("2.") <br> push(s, "Pumbaa")<br> println(s)<br> <br> println("3.") <br> push(s, "and")<br> push(s, "Timon")<br> println(s)<br> <br> println("4.") <br> val x = pop(s)<br> println("Popped %_ from stack." % [x])<br> println(s)<br><br> println("5.")<br> val y = pop(s)<br> println("Popped %_ from stack." % [y])<br> println(s)<br><br>main() </code></pre><p>Compile and run the program. It should print out</p><pre><code>1.<br>Stack containing []<br>2.<br>Stack containing [Pumbaa]<br>3.<br>Stack containing [Pumbaa and Timon]<br>4.<br>Popped Timon from stack.<br>Stack containing [Pumbaa and]<br>5.<br>Popped and from stack.<br>Stack containing [Pumbaa]</code></pre><h2 id="anchor51">Constructor Functions</h2><p>In the above example, we created a stack called <code>s</code> directly in the <code>main</code> function. You may be thinking that this seems like a lot of work to create a single stack! What if we need to create multiple stacks? </p><p>The solution is to simply move the stack construction code into a new function and call it once for each stack we want to create. Here is a function called <code>make-stack</code> that accepts a <code>capacity</code> argument for specifying the maximum size supported by the stack.</p><pre><code>defn make-stack (capacity:Int) -> Stack :<br> val items = Array<String>(capacity)<br> var size = 0<br> new Stack :<br> defmethod push (this, x:String) :<br> if size == capacity : fatal("Stack is full!")<br> items[size] = x<br> size = size + 1<br> defmethod pop (this) :<br> if size == 0 : fatal("Stack is empty!")<br> size = size - 1<br> items[size]<br> defmethod empty? (this) :<br> size == 0<br> defmethod print (o:OutputStream, this) :<br> print(o, "Stack containing [")<br> for i in 0 to size do :<br> print(o, items[i])<br> if i < size - 1 :<br> print(o, " ")<br> print(o, "]") </code></pre><p>Let's change our main function to create two stacks and push different strings into them.</p><pre><code>defn main () :<br> val s1 = make-stack(10)<br> val s2 = make-stack(10)<br><br> println("1.")<br> push(s1, "Timon")<br> push(s1, "Pumbaa")<br> push(s1, "Nala")<br> push(s2, "Ryu")<br> push(s2, "Ken")<br> push(s2, "Makoto")<br> println(s1)<br> println(s2)<br><br> println("2.")<br> println("Popped %_ from s1." % [pop(s1)])<br> println("Popped %_ from s2." % [pop(s2)])<br> println(s1)<br> println(s2)<br><br> println("3.")<br> println("Popped %_ from s1." % [pop(s1)])<br> println("Popped %_ from s2." % [pop(s2)])<br> println(s1)<br> println(s2)<br><br>main() </code></pre><p>Compile and run the program. It should print out</p><pre><code>1.<br>Stack containing [Timon Pumbaa Nala]<br>Stack containing [Ryu Ken Makoto]<br>2.<br>Popped Nala from s1.<br>Popped Makoto from s2.<br>Stack containing [Timon Pumbaa]<br>Stack containing [Ryu Ken]<br>3.<br>Popped Pumbaa from s1.<br>Popped Ken from s2.<br>Stack containing [Timon]<br>Stack containing [Ryu]</code></pre><p>Notice especially that the two stacks created by the separate calls to <code>make-stack</code> contain different strings and operate independently of each other. </p><p>We call <code>make-stack</code> a <span style="font-style:italic;">constructor</span> function for <code>Stack</code> objects because it returns newly created <code>Stack</code> objects. If you are familiar with the object systems in other languages it might surprise you to see that there is nothing particularly special about constructor functions in Stanza. They're just regular functions. This lack of distinction between constructors and functions is another contributing factor to Stanza's flexibility. Constructors in class based languages are typically more "special" than regular functions, and while any user can define functions for a class, only the library's author can define more constructors for a class.</p><p>As a note on style, we named the constructor function for <code>Stack</code> objects <code>make-stack</code> in order to avoid confusing you. But the idiomatic Stanza style is to give the same name to the constructor function as the type of object it is constructing. So <code>make-stack</code> would simply be named <code>Stack</code>, and you will distinguish based on context whether a reference to <code>Stack</code> refers to the type or the function.</p><p>As a reminder, even with the <code>new</code> expression, you are still encouraged to keep the number of fundamental operations for a type small, and then implement as much functionality as derived operations as possible.</p><p>As an exercise, try implementing a function called <code>UnboundedStack</code> that constructs a <code>Stack</code> object with no maximum capacity. Then try it in place of <code>Stack</code>, and observe that there is no behavioural difference (save for capacity limitations) between stacks created with <code>UnboundedStack</code> and stacks created with <code>Stack</code>. </p><h2 id="anchor52">Revisiting Defstruct</h2><p><code>defmulti</code>, <code>defmethod</code>, <code>deftype</code> and <code>new</code> forms the fundamental constructs of Stanza's class-less object system. The <code>defstruct</code> construct that you have been using thus far is merely a syntactic shorthand for a specific usage pattern of <code>new</code>. Let's take a peek at its internals.</p><p>Here is a struct definition for a <code>Dog</code> object with a name field and a mutable breed field. </p><pre><code>defstruct Dog <: Animal :<br> name: String<br> breed: String with: (setter => set-breed)</code></pre><p>The above can be equivalently written as</p><pre><code>deftype Dog <: Animal<br>defmulti name (d:Dog) -> String<br>defmulti breed (d:Dog) -> String<br>defmulti set-breed (d:Dog, breed:String) -> False<br><br>defn Dog (name:String, initial-breed:String) -> Dog :<br> var breed = initial-breed<br> new Dog :<br> defmethod name (this) : name<br> defmethod breed (this) : breed<br> defmethod set-breed (this, b:String) : breed = b</code></pre><p>Thus, the <code>defstruct</code> construct expands to</p><ol><li>a type definition,
</li><li>getter functions for each of its fields,
</li><li>setter functions for each of its mutable fields, and
</li><li>a constructor function for creating instances of the type.
</li></ol>
</td>
<td class="rest">
<img url="resources/spacer.gif"></img>
</td>
</tr>
<tr><td colspan="3" class="footer">
Site design by Luca Li. Copyright 2015.
</td></tr>
</table>
</body>
</html>