Jekyll2018-09-08T23:18:26+00:00/Coding Against ChaosMatthew ParkerTesting the Unthinkable2018-07-21T16:00:00+00:002018-07-21T16:00:00+00:00/2018/07/21/testing-the-unthinkable<p>Here’s a quick exercise. Below are 4 different kinds of bugs. Rank them from most difficult to least difficult to debug, analyze and fix.</p>
<p>Bugs:</p>
<ul>
<li>String typo (e.g. typing a URL incorrectly)</li>
<li>Race condition</li>
<li>Logic error (e.g. calculating the wrong threshold amount, using > instead of <)</li>
<li>Bad state after 10 step execution</li>
</ul>
<p>Here’s my ranking:</p>
<p><img src="/assets/img/error_ranking.jpg" alt="Ranking" width="400px" /></p>
<p>I ranked the string typo as the least difficult because you can probably detect that by taking a second look at the code. The logic error is a little harder in that you might have to do some debugging and step through to find the exact place where the program’s next state is calculated incorrectly. However, one the place is found, it’s often easy to analyze and fix. The 10 step execution jumps up significantly in difficulty because it might be difficult to find the exact 10 steps. Once found, the bug can be replicated and fixed. The most difficult is the race condition because it might not be possible to consistently replicate which makes analyzing and fixing close to impossible.</p>
<h2 id="thinking-vs-implementation">Thinking vs. implementation</h2>
<p>These bugs can be split in two categories: thinking and implementation errors.</p>
<p><img src="/assets/img/error_categories.jpg" alt="Categories" width="400px" /></p>
<p>Implementation errors are a mismatch between what we wanted to code and what we actually did. With an implementation error, the problem and solution space are completely understood but there was a copy error when transcribing from your brain to your text editor.</p>
<p>Thinking errors occur when the behavior of a program is underspecified. They aren’t the result of a mistake but rather the result of an unknown or unforeseen scenario. These are all bugs that you couldn’t even try to guard against because you didn’t imagine them in the first place.</p>
<p>This distinction matters because each category has a different impact on the software lifecycle. Implementation errors are typically easier to debug, analyze and fix as we saw above and because of this, estimates for addressing the bugs tend to be more accurate. Thinking errors, on the other hand, introduce a lot of risk into the development process. Estimates can be highly inaccurate because they are “unknown unknowns”. Additionally, fixes may require significant changes to the design which increases the probability of regressions in other areas of the code.</p>
<h2 id="testing-against-errors">Testing against errors</h2>
<p>The most common method for reducing the risk of errors is software testing.</p>
<p>Testing these bugs also falls into different categories:</p>
<p><img src="/assets/img/error_testing.jpg" alt="Testing" width="400px" /></p>
<p>High-level tests = end to end, integration, UI, etc.</p>
<p>Here we see a relationship: high-level tests are to thinking errors as unit tests are to implementation errors.</p>
<p>This creates a problem for us because as we move higher from unit tests, the cost of writing and maintaining the tests increases. To deal with this, it’s common to use the strategy of a testing pyramid where most of the testing suite consists of unit tests. This means that the testing suite is focused on catching implementation errors. We end up with a mass of cheap tests that cover the less risky parts of our programs.</p>
<p><img src="/assets/img/error_testing_pyramid.jpg" alt="Testing" width="400px" /></p>
<h2 id="the-economics-of-lightweight-formal-methods">The economics of lightweight formal methods</h2>
<p>Lightweight formal methods completely change the ROI on verifying the thinking parts of building software. They lower the cost and increase the return.</p>
<p>The term lightweight means that we do not specify our entire program, rather we focus on the level of abstraction we care about. Once the abstraction is specified, we exhaustively test the state space with a model checker.</p>
<h3 id="costs">Costs</h3>
<p>Tools like TLA+ do not have any of the costs that high-level tests have. <a href="https://martinfowler.com/bliki/TestPyramid.html">Martin Fowler writes</a> that end to end tests are <strong>brittle, expensive to write and time consuming to run</strong>. Let’s analyze each of these.</p>
<h4 id="brittle">Brittle</h4>
<p>Brittle means that the tests break easily when they shouldn’t. High-level tests are brittle because they consist of several moving parts that have to be setup correctly and all working as expected. Also, a lot of the tooling for these tests depend on implementation details that can easily change such as a specific element in the UI having a specific name.</p>
<p>Formal specifications do not suffer from moving parts because they are just descriptions. All of the pieces needed for checking the model are contained within the specification. There is no dependency on outside systems and there is no execution of other programs. In addition, these tools do not rely on any implementation details so are not at risk when those details change.</p>
<h4 id="expensive-to-write">Expensive to write</h4>
<p>The main expense when writing integration tests is the setup. Because there are many moving parts, each part needs to be configured, initialized and potentially mocked. This is all additional code that needs to be maintained and increases coupling to the implementation. Often checking a few properties of a system requires a order of magnitude more setup code.</p>
<p>Formal methods do not require any setup code because they are not tied to the actual components under test. The specifications only need to change when the abstractions change. There is the cost of having to completely describe your system in order to check it properly. Upfront this seems like duplication. However, this should still result in less lines of specification than code because in formal methods the programmer doesn’t have to define all of the permutations possible to test them – the model checker does that for you.</p>
<h4 id="time-consuming-to-run">Time consuming to run</h4>
<p>High-level tests can take a long time to run because they rely on the computational complexity of each component and the latency between those components. This introduces accidental complexity into the test suite. Rarely is the computational complexity and latency relevant to the test.</p>
<p>Model checking can also take a long time to run. What makes model checking slow is the state space of the program. Because model checkers exhaustively iterate through the state space, a lot of state means a lot of checking. However, this is essential complexity. While this certainly is a cost, it is directly proportional to the return.</p>
<h3 id="returns">Returns</h3>
<p>All tests are dependent on the inputs they use. A test can only show that an error doesn’t occur for that specific example. While this is often used a knock against tests, in practice it’s a cost trade off. We pick a range of inputs which reduces the probability that an error will occur. This moves us from proof to statistics. Unit tests are often a good trade off where they are low cost but also provide significant risk reduction. Integration tests on the other hand are high cost and quickly reach a number of permutations that are impossible to test. Thus the probability of errors exisiting remains high.</p>
<p>In all formal methods, the returns are easier to measure – no thinking errors. Of course, this is limited by the quality of the checks and constraints embedded in the spec. This amounts to the absence of thinking errors being limited by the ability to think clearly. However, this isn’t a one-way street. While formal methods are able to check the thinking, they do also help clarify your thinking. Note that there is a distinction here between being wrong in your thinking and thinking the wrong thing. <a href="https://en.wikipedia.org/wiki/Software_verification_and_validation#Definitions">Software verification covers the first where as validation covers the latter</a>. This post only addresses verification.</p>
<h2 id="working-together">Working together</h2>
<p>The majority of this post contrasts lightweight formal methods with high-level tests (end to end and integration). However, the point is not to suggest formal methods replace testing. Overall, our goal is to provide our customers with quality software and reduce the cost of doing so. Rather than treating all bugs as defects in the code, we can gain a lot by focusing on implementation vs. thinking errors and using the appropriate tools to tackle each. Formal methods are typically thought of as being prohibitively expensive except for in the most safety-critical software. Lightweight formal methods have much more favorable economics, not just when compared to traditional formal methods but even when compared to traditional testing techniques. All but the most trivial programs can benefit from adding these tools to their verification strategy.</p>Matthew ParkerHere’s a quick exercise. Below are 4 different kinds of bugs. Rank them from most difficult to least difficult to debug, analyze and fix.Starting Out with TLA+2018-06-04T00:00:00+00:002018-06-04T00:00:00+00:00/2018/06/04/starting-out-with-tla<p>I recently attended Never Graduate Week at <a href="https://www.recurse.com/">Recurse Center</a>. For my project, I chose to tackle learning TLA+. Prior to the week at RC, I worked through <a href="https://learntla.com">Learn TLA</a> and the <a href="http://lamport.azurewebsites.net/video/videos.html">TLA+ video course</a>. With some familiarity of the mechanics, my goal was to create my own specs. I shared my experience of going through this exercise with a few people and some similar questions/concerns came up. Here are my thoughts on those concerns.</p>
<h2 id="what-is-tla">What is TLA?</h2>
<p>TLA is a suite of tools for formal verification. The main tools we will focus on are the TLA+ specification language and the TLC model checker. While these names might sound completely foreign, they boil down to a languages for describing programs and a tool for checking those descriptions. This is similar to the relationship between a programming language which implements a program and a unit testing framework which checks that implementation.</p>
<p>However, there is a big difference between a programming language and a specification language. A programming language tells a computer how to do something. A specification language focuses on what the programmer wants to do. TLA+ is not executable and is not meant for translation.</p>
<p>To give you a feel for TLA+, here’s a simple spec:</p>
<figure class="highlight"><pre><code class="language-tla-" data-lang="tla+">VARIABLES toggle
On == /\ toggle = FALSE
/\ toggle' = TRUE
Off == /\ toggle = TRUE
/\ toggle' = FALSE
Init == toggle = FALSE
Next == Init \/ [Next]_toggle</code></pre></figure>
<p>This is a spec for toggle functionality. Here <code class="highlighter-rouge">On</code> is defined as the step where the toggle is false in the current state and true in the next. <code class="highlighter-rouge">Off</code> is defined as the step where toggle is true in the current state and false in the next. The programs starts with the toggle as false and the next step can be <code class="highlighter-rouge">On</code> or <code class="highlighter-rouge">Off</code>. In TLA+ <code class="highlighter-rouge">/\</code> means <code class="highlighter-rouge">AND</code> and <code class="highlighter-rouge">\/</code> means <code class="highlighter-rouge">OR</code>. The prime (shown with <code class="highlighter-rouge">your_var'</code>) declares the variable in the next state.</p>
<h2 id="what-about-real-programs">What about <em>real</em> programs?</h2>
<p>While the above example is a toy spec, this small, simple language is incredibly expressive. In fact, <strong>you can model any program or system in TLA+</strong>. This is possible because TLA+ focuses on one core mental model – state machines.</p>
<p>Leslie Lamport, the creator of TLA+, explains this in <em>Computer Science and State Machines</em> <sup id="fnref:1"><a href="#fn:1" class="footnote">1</a></sup>. The gist is that while programming languages may differ from each other, they all describe computations and computations can be described as state machines. One major benefit of this is that state machines are a powerful tool for reasoning about programs. With TLA+, we can reason about any program in any language, even if they are complex.</p>
<p>TLA+ makes this apparent when modeling concurrency. Concurrency bugs are notoriously difficult to reason about but TLA+ handles them as easily as a single-process program. First, we break down our processes into sequences of state transformations. For example, assume we have two processes, <code class="highlighter-rouge">A</code> and <code class="highlighter-rouge">B</code>. We can break <code class="highlighter-rouge">A</code> into <code class="highlighter-rouge">A1 -> A2 -> A3</code>. We can break <code class="highlighter-rouge">B</code> into <code class="highlighter-rouge">B1 -> B2 -> B3</code>. Then modeling concurrency is as simple as the permutation of these six steps, i.e.,</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>A1 -> B1 -> A2 -> B2 -> A3 -> B3
A1 -> A2 -> B1 -> A3 -> B2 -> B3
B1 -> A1 -> B2 -> B3 -> A2 -> A3
... etc ...
</code></pre></div></div>
<p>Even in this small example, there are a lot of sequences. This is where the model checker comes in. The model checker will try all of the different permutations and check that our state is always valid. The checks come in the form of invariants which are validations at each step and properties which are validations across steps.</p>
<h2 id="its-not-just-for-serious-problems">It’s not just for <em>serious</em> problems</h2>
<p>If you read around about TLA+, what you’ll probably find is that it is useful for hard problems. These hard problems typically involve concurrency. These kinds of problems make a great use case because they are the types of issues that can’t be found by many other tools.</p>
<p>However, verifying concurrency is not TLA+’s only focus. It is a consequence of how powerful the tool is. That power really comes from forcing the programmer to think precisely. Nothing in TLA+ can be ambiguous and everything must be defined in an atomic step. The magic of TLA+ is that in doing this exercise you understand your problem and then your solution more clearly. The model checker completes this process by verifying your thinking.</p>
<p>Thinking problems creep up even in the simplest programs. Working memory is estimated to hold 7 things <sup id="fnref:2"><a href="#fn:2" class="footnote">2</a></sup>. That means that even a program with just 4 booleans in it, is hard to grasp. This state space explosion is one of the core problems of programming<sup id="fnref:3"><a href="#fn:3" class="footnote">3</a></sup>.</p>
<h2 id="its-not-code">It’s not code</h2>
<p>TLA+ is not a programming language and specs are not code. At first this may seem like a downside of the tool but it’s a major strength. Code serves as an isomorphism between what is in the programmer’s head and what the computer will run. While we put a lot of effort in to keeping these two separate, they are inevitably tied together. To deal with this, we come up with abstractions. However, these abstractions always leak because we have to deal with the friction between the mental model and the implementation.</p>
<p>TLA+ lives completely in the world of abstractions. Because TLA+ is not code, we don’t have to worry about implementation. The quirks of your programming language, framework, etc do not exist in TLA+. This dramatically reduces the cost of creating specs because they don’t depend on code that already exists. You’re small feature doesn’t need to integrate with the existing code base. With TLA+, you can model how you believe the other code works and then spec your new work around that.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Even after a week of using TLA+, I was really blown away. I definitely want to integrate this in to my dev process as it seems like a powerful tool for augmenting my problem solving skills. The learning curve was a lot less steep than I anticipated, especially because of the newer resources I mentioned above. My main takeaway from my week was that the economics on TLA+ are very different from what I thought. The language is purposely designed to be simple and you get a lot of value out of writing and checking the spec. The scope of projects that can benefit from this is a lot bigger than just mission critical software.</p>
<hr />
<div class="footnotes">
<ol>
<li id="fn:1">
<p><em>Computer Science and State Machines</em> by Leslie Lamport. <a href="http://lamport.azurewebsites.net/pubs/deroever-festschrift.pdf">PDF</a> <a href="#fnref:1" class="reversefootnote">↩</a></p>
</li>
<li id="fn:2">
<p><a href="https://en.wikipedia.org/wiki/Working_memory#Capacity">https://en.wikipedia.org/wiki/Working_memory#Capacity</a> <a href="#fnref:2" class="reversefootnote">↩</a></p>
</li>
<li id="fn:3">
<p><em>No Silver Bullet – Essence and Accident in Software Engineering</em> by Frederick Brooks. <a href="http://worrydream.com/refs/Brooks-NoSilverBullet.pdf">PDF</a> <a href="#fnref:3" class="reversefootnote">↩</a></p>
</li>
</ol>
</div>Matthew ParkerI recently attended Never Graduate Week at Recurse Center. For my project, I chose to tackle learning TLA+. Prior to the week at RC, I worked through Learn TLA and the TLA+ video course. With some familiarity of the mechanics, my goal was to create my own specs. I shared my experience of going through this exercise with a few people and some similar questions/concerns came up. Here are my thoughts on those concerns.Structuring Structs in Swift2017-02-19T04:46:00+00:002017-02-19T04:46:00+00:00/2017/02/19/structuring-structs<p>What’s the difference between a class and struct? The goto answer I often hear is about the memory model of reference vs value types. While technically correct, this misses the bigger picture. Structs produce data, classes produce objects. At first glance the difference is subtle but I will go through some of the design considerations while setting up a struct that will highlight the differences.</p>
<h2 id="data--functions">Data + functions</h2>
<p>Many functional programming languages, organize their namespaces in a similar way. A name space will consist of a data definition and some functions that operate on that data. For example in Clojure you might see:</p>
<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">defrecord</span><span class="w"> </span><span class="n">Person</span><span class="w"> </span><span class="p">[</span><span class="n">first-name</span><span class="w"> </span><span class="n">last-name</span><span class="w"> </span><span class="n">dob</span><span class="p">])</span><span class="w">
</span><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">full-name</span><span class="w"> </span><span class="p">[</span><span class="n">person</span><span class="p">]</span><span class="w">
</span><span class="p">(</span><span class="nb">str</span><span class="w"> </span><span class="p">(</span><span class="no">:first-name</span><span class="w"> </span><span class="n">person</span><span class="p">)</span><span class="w"> </span><span class="s">" "</span><span class="w"> </span><span class="p">(</span><span class="no">:last-name</span><span class="w"> </span><span class="n">person</span><span class="p">)))</span><span class="w">
</span><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">change-name</span><span class="w"> </span><span class="p">[</span><span class="n">person</span><span class="w"> </span><span class="nb">first</span><span class="w"> </span><span class="nb">last</span><span class="p">]</span><span class="w">
</span><span class="p">(</span><span class="nb">assoc</span><span class="w"> </span><span class="n">person</span><span class="w"> </span><span class="no">:first-name</span><span class="w"> </span><span class="nb">first</span><span class="w"> </span><span class="no">:last-name</span><span class="w"> </span><span class="nb">last</span><span class="p">))</span></code></pre></figure>
<p>This code will be in one file under one name space. Every function here deals with either deriving some information from the exisiting data or providing new data. If you squint your eyes, you can see a Swift struct in this Clojure code. To make it easier, here is the translation:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">struct</span> <span class="kt">Person</span><span class="p">{</span>
<span class="k">var</span> <span class="nv">firstName</span><span class="p">:</span> <span class="kt">String</span>
<span class="k">var</span> <span class="nv">lastName</span><span class="p">:</span> <span class="kt">String</span>
<span class="k">var</span> <span class="nv">dob</span><span class="p">:</span><span class="kt">Date</span>
<span class="k">var</span> <span class="nv">fullName</span><span class="p">:</span> <span class="kt">String</span> <span class="p">{</span>
<span class="k">return</span> <span class="s">"</span><span class="se">\(</span><span class="n">firstName</span><span class="se">)</span><span class="s"> </span><span class="se">\(</span><span class="n">lastName</span><span class="se">)</span><span class="s">"</span>
<span class="p">}</span>
<span class="kd">func</span> <span class="nf">changeName</span><span class="p">(</span><span class="nv">first</span><span class="p">:</span> <span class="kt">String</span><span class="p">,</span> <span class="nv">last</span><span class="p">:</span> <span class="kt">String</span><span class="p">)</span> <span class="o">-></span> <span class="kt">Person</span> <span class="p">{</span>
<span class="k">return</span> <span class="kt">Person</span><span class="p">(</span><span class="nv">firstName</span><span class="p">:</span> <span class="n">first</span><span class="p">,</span> <span class="nv">lastName</span><span class="p">:</span> <span class="n">last</span><span class="p">,</span> <span class="nv">dob</span><span class="p">:</span> <span class="n">dob</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>People with functional programming experience may lean towards only having static methods in a struct, making it impossible for those methods to contain any state. I would caution against that. If this was supposed to be the Swift way, we wouldn’t need the extra static keyword. It adds verbosity and Swift already provides syntax for that.</p>
<h2 id="pure-functions">Pure functions</h2>
<p>Avoid creating methods with side effects. Methods in classes produce behavior, methods in structs produce data. Calling a method on an object is about telling the object to do something. Calling a method on a struct is always about asking for data. The easy rule of thumb here is that every struct method should return something unless it has the mutating keyword in which case it’s implictly returning a new instance of the struct. Your data shouldn’t know how to make network calls, save to the database, etc. Data is simple and declarative.</p>
<p>One more point to add is that structs have no deinit. This removes the opportunity to do some clean up for anything that’s stateful.</p>
<h2 id="var-properties-not-let">Var properties, not let</h2>
<p>Define your properties as vars not lets. While immutability does make everything easier, the truth is we often need to change our data. Most languages that use immutable data make this easy by providing some functions that help you change the value of one or more keys. Swift does not provide anything like this. This means that to update any value we have to use our constructor. This becomes more and more cumbersome as the number of properties grow. The Swiftier thing to do is default to making your properties vars. This allows the user of your struct to decide how they want to treat your data. The use of let on their side will enforce immutability. This gives us the flexibility within the struct to temporarily treat it as mutating.</p>
<p>For example:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">struct</span> <span class="kt">Person</span> <span class="p">{</span>
<span class="k">var</span> <span class="nv">firstName</span><span class="p">:</span> <span class="kt">String</span>
<span class="k">var</span> <span class="nv">lastName</span><span class="p">:</span> <span class="kt">String</span>
<span class="k">var</span> <span class="nv">dob</span><span class="p">:</span> <span class="kt">Date</span>
<span class="kd">func</span> <span class="nf">changeName</span><span class="p">(</span><span class="nv">first</span><span class="p">:</span> <span class="kt">String</span><span class="p">,</span> <span class="nv">last</span><span class="p">:</span> <span class="kt">String</span><span class="p">)</span> <span class="o">-></span> <span class="kt">Person</span> <span class="p">{</span>
<span class="k">var</span> <span class="nv">temp</span> <span class="o">=</span> <span class="k">self</span>
<span class="n">temp</span><span class="o">.</span><span class="n">firstName</span> <span class="o">=</span> <span class="n">first</span>
<span class="n">temp</span><span class="o">.</span><span class="n">lastName</span> <span class="o">=</span> <span class="n">last</span>
<span class="k">return</span> <span class="n">temp</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>To the outside world, changeName is a pure function.</p>
<p>Of course, this doesn’t mean never use lets in a struct. It means that a let should be used for constants. A let in a struct should be for something that truly will never change. Things like pi will never change and make perfect sense as a let. However, if you’re unsure about whether something will eternally hold true then go with a var.</p>
<h2 id="create-initializers">Create initializers</h2>
<p>Given the benefits of immutable data, which I won’t go into here, we should make it easy for users to instantiate our struct as a let constant. In order to do this, we need to provide good initializers.</p>
<p>Swift gives us a number of tools to create good initializers.</p>
<p>By default, we get an initializer that allows us to set all of our properties. The problem with this is that as soon as we declare our own init then we lose the default one. Although it may be verbose, it’s easy enough to rewrite this default init.</p>
<p>Swift allows us to have default parameters in our methods. This gives us the option of not passing that parameter in our method call. We can use this to create succint initializers.</p>
<p>For example:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">struct</span> <span class="kt">Passenger</span> <span class="p">{</span>
<span class="k">var</span> <span class="nv">seat</span><span class="p">:</span> <span class="kt">String</span>
<span class="k">var</span> <span class="nv">bags</span><span class="p">:</span> <span class="p">[</span><span class="kt">Bag</span><span class="p">]</span>
<span class="nf">init</span><span class="p">(</span><span class="nv">seat</span><span class="p">:</span> <span class="kt">String</span><span class="p">,</span> <span class="nv">bags</span><span class="p">:</span> <span class="p">[</span><span class="kt">Bag</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]){</span>
<span class="k">self</span><span class="o">.</span><span class="n">seat</span> <span class="o">=</span> <span class="n">seat</span>
<span class="k">self</span><span class="o">.</span><span class="n">bags</span> <span class="o">=</span> <span class="n">bags</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">let</span> <span class="nv">passenger1</span> <span class="o">=</span> <span class="kt">Passenger</span><span class="p">(</span><span class="nv">seat</span><span class="p">:</span> <span class="s">"25A"</span><span class="p">)</span>
<span class="k">let</span> <span class="nv">passenger2</span> <span class="o">=</span> <span class="kt">Passenger</span><span class="p">(</span><span class="nv">seat</span><span class="p">:</span> <span class="s">"25B"</span><span class="p">,</span> <span class="nv">bags</span><span class="p">:</span> <span class="p">[</span><span class="kt">Bag</span><span class="p">(</span><span class="nv">id</span><span class="p">:</span> <span class="s">"123"</span><span class="p">)])</span></code></pre></figure>
<p>Also, reduce the number of states that can’t be reached through an initializer. While it may seem more intuitive to restrict data changes with methods, it’s more user friendly if that state can also be created directly with values. This is especially helpful when testing since the data won’t require any complex set up.</p>
<h2 id="equality">Equality</h2>
<p>One of the most basic operations we need to perform in a program is comparison. We often make decisions within our programs based on two values being equal or not. This idea of equality is independent of place and time when dealing with values. If we have:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="k">let</span> <span class="nv">x</span> <span class="o">=</span> <span class="mi">1</span>
<span class="k">let</span> <span class="nv">y</span> <span class="o">=</span> <span class="mi">1</span></code></pre></figure>
<p>then x always equals y regardless of where and when x and y are assigned. With reference types this isn’t the case. Two instances of a reference type are not equal by default since we compare their place in memory not the values they reference.</p>
<p>Implement the Equatable protocol for your structs. You may never check equality between your structs but it’s an important exercise for understanding the domain that you are modeling. It forces you to think about what the struct is representing. For example, if we have a Person struct and want to implement Equatable. Our default may be to just compare all of the properties. If a person changed their name, should they be considered a different person now? While at first this may seem too philosophical, it will help you model your domain.</p>Matthew ParkerWhat’s the difference between a class and struct? The goto answer I often hear is about the memory model of reference vs value types. While technically correct, this misses the bigger picture. Structs produce data, classes produce objects. At first glance the difference is subtle but I will go through some of the design considerations while setting up a struct that will highlight the differences.