How Rotating Your Team Members Improves Your Product and Developers

In life, change is inevitable. This is particularly true in the realm of software, for both technology and personnel. People may leave your team for a different team, or for a different company altogether, or new team members may be hired onto the team and need to learn the ropes.

Frequently, team member transitions can be stressful. This is true for both managers and for clients working with a software consultancy like Atomic. The stakeholders or project owners naturally want productive work to continue. They want to keep the team they know and see no upside to changing the existing team. They may fear that the new developers will be less technically skilled, or lacking in communication skills or organization. In any event, every transition period has costs.

Change isn’t all bad, though. I would argue that semi-regular changes to a team can actually strengthen the software product and increase the quality of developers within your organization. When team member changes are a semi-regular occurrence and not a sudden crisis, good things can happen.

Rotating Team Members on a Software Project

Smoother Transitions

For one, with regular additions to the team, existing team members are incentivized to keep documentation current. They are also naturally going to make the ramping-in process as painless as possible. Team members will remember what it was like to ramp into the project themselves, and they will want to pass on the lessons they learned.

Team personnel changes are inevitable anyway, so you might as well get used to them. Intentionally swapping out team members from time to time serves as helpful preparation for those moments when circumstances dictate change.

“Well-Rounded” Code

A team which must absorb new members every five or six months will produce more “well-rounded” code. That is, such a team will produce more approachable, standardized code than a team whose members stay the same for years at a time.

At a former job, I was a member of a large team that inherited a complex code base from a small, tight-knit group of developers. We found the code nearly indecipherable in places; it was impossible to tell what unintended side effects minor changes might have. Moreover, we couldn’t go to anyone for help–the original small team of developers all left the company together to start a new venture. Had the initial team rotated members in and out, I have no doubt the rough edges in the code would have been smoothed out. They would have had no choice but to make their code “friendlier” to newcomers.

You could make the argument that rotating team members encourages “lowest common denominator” choices with regard to tech stack, programming techniques, and software architecture. The concern is that teams will only use the technologies “everybody” knows and never try anything new, even if said new technique would be tremendously beneficial.

But I’m doubtful. I find that “lowest common denominator” syndrome is generally due to interference from above, and not from within teams. Rather, needing to bring new team members up to speed exerts pressure to utilize only the most beneficial among “advanced” programming practices.

Speaking as someone who loves functional programming intensely, when I’m writing a project in an imperative language, I find it helpful to have the pressure of explaining my choices to others. Otherwise, I’m tempted to go hog-wild with whatever cool technique I’m reading about, whether it is appropriate for the project or not.

Inertia

Then there’s the issue of “inertia.” When you’ve been on a project for a while, you stop noticing some of the tradeoffs and tech debt that you’ve built up. You might have sprinkled some TODOs in your code, but the Vegas odds on actually addressing those TODOs gets lower with each passing day.

But when a new member joins the team, she is seeing the code–and the broader context of the project–with “fresh eyes.” Issues or challenges you tabled long ago don’t seem as intractable to someone coming in from the outside. A new developer joined my current project recently, and I was impressed with how quickly he spotted many of the technical debt or wish list items I’d long forgotten. He also has a much better sense of which parts of the project are the hardest for a new person to understand. This provides helpful guidance towards improving those aspects of the system–or at least providing better documentation.

Developer Growth

Finally, I firmly believe that it is in a developer’s best interest to change projects from time to time. I am of the opinion that two years is about the maximum time a developer should stick with the same project.

Developers can and should integrate new libraries or techniques into their ongoing projects, but for my money, there’s nothing like jumping onto a new project–possibly with a different tech stack–to spur growth. Developers or teams that are siloed for long periods of time may become “comfortably numb” and lose the drive to keep learning. In my opinion, rotating developers occasionally will increase the quality of your organization’s pool of developers.

Looking for the Upside

Changes to a project team can cause anxiety, but those same changes can also bring fresh energy and improve the project. The key is being ready to see opportunities when team members join the project, and capitalizing on those opportunities. When have you seen new team members bring clear benefits?

The post How Rotating Your Team Members Improves Your Product and Developers appeared first on Atomic Spin.

Escaping the South Indian Monkey Trap

When I joined Atomic Object, one of the first books that I read was The Passionate Programmer by Chad Fowler. In this book, Fowler shares his collection of tips and tricks for becoming an exemplary programmer. As an entry-level software developer, I found many of these tips incredibly helpful, but the one that stuck with me the most was called The South Indian Monkey Trap.

The South Indian Monkey Trap

The South Indian Monkey Trap, as described by Fowler, is a way of catching monkeys which involves digging a hole in the ground, making sure that the bottom of the hole is larger than the opening. The hole is then filled with food, with the intent that a curious monkey will reach for it. Upon grabbing the food, the monkey will no longer be able to pull its hand out of the hole. However, it places so much value on the food that it doesn’t realize it could be free if it were just to let go of the food.

The monkey is trapped because it believes the value of the food overrides all. It can no longer objectively question the predicament it is in. This is a concept known as “value rigidity.”

You might ask how catching monkeys relates to software development or question why Chad Fowler included it in his collection of tips. After only working in the field for a few months, you will likely have had numerous discussions with colleagues about how programming language X is better than any other language, especially language Y. These claims could very well be legitimate, as there is a plethora of criteria to judge a programming language. However, to assume there is one language that is vastly better and should always be used is a prime example of the value rigidity that Fowler warns about.

Recognizing that you are stuck in this trap is the first step, but here are some tips that will help you let go of that food:

1. Complete a side project in another programming language.

One of the greatest forms of professional development is completing a side project, whether it is in a new language or one you already know. This is one of Fowler’s tips, and I think it is excellent advice.

Side projects are useful because they provide a real problem to work through, unlike a “Hello World” or “To-Do List” application that you’ve already made 12 times. Even if it is a side project that you have completed before, learning a new language might inspire ways to make it better and implement it differently.

2. Attend conferences, events, or meetups.

Back in college, I was introduced to Common Lisp, which of course I loathed at the time. After attending a few local events as well as Strange Loop, I spoke with many software developers who all suggested that I should check out Clojure. Because of their recommendations, I have had an excellent time learning Clojure in my spare time, investigating the fundamentals of functional programming in a Lisp-based language.

The software development community is a powerful resource. Make sure to utilize it to your benefit.

3. Re-evaluate the “all-powerful” language.

The last tip I want to share is to re-evaluate the language that you deem to be so powerful. Objectively review previously completed projects: Did it take a significant amount of time to complete a trivial task? Were there countless errors with managing the development environment or build steps? Was the type system getting in the way of what you wanted to do? Was the lack of a type system causing frustration?

The South Indian Monkey Trap is quite easy to escape as long as we realize that we need to let go of the food. Have you ever been caught in the trap? Let us know how you learned to let go.

The post Escaping the South Indian Monkey Trap appeared first on Atomic Spin.

Immutable.js Records in TypeScript

React apps with Redux are built around an immutable state model. Your entire application state is one immutable data structure stored in a single variable. Changes in your application are not made by mutating fields on model objects or controllers, but by deriving new versions of your application state when you make a single change to the previous version.

This approach comes with some huge benefits in tooling and simplicity, but it requires some care.

It’s certainly possible to model your Redux application state using plain old JavaScript/TypeScript objects and arrays, but this approach has a few downsides:

  1. The built-in data types aren’t immutable. There’s no real protection against an errant mutation of your application state, violating the basic assumptions of Redux.
  2. Creating copies of arrays and maps is inefficient, requiring a O(n) copy of the data.

Fortunately, Facebook has also released Immutable.js, a library of persistent immutable data structures. This library provides maps, sequences, and record types that are structured to enforce immutability efficiently. Immutable.js data structures can’t be changed–instead, each mutation creates a brand new data structure that shares most of its structure with the previous version.

Since it’s immutable, this is safe. It’s also efficient. By using fancy data structures, each change is O(log32 n), effectively constant time.

Immutable.js comes with type definitions for TypeScript’s built-in data structures. Less fortunately, creating immutable record types (e.g., classes) with heterogeneous values isn’t as straightforward as Map<K,V>.

I looked for a solution and found a few libraries, but they all were too complicated. I wanted something that:

  • Is simple and easy to understand
  • Provides typed access to properties
  • Offers a type-safe, statically enforced way to perform updates
  • Resists incorrect use, without requiring static enforcement

Example

Here’s an example of the simplest approach I could come up with:

type CounterParams = {
    value?: number,
    status?: Status,
};
export class Counter extends Record({ value: 0, status: OK }) {
    value: number;
    status: Status;
    constructor(params?: CounterParams) {
        params ? super(params) : super();
    }
    with(values: CounterParams) {
        return this.merge(values) as this;
    }
}

(Assume Status is an enumeration type with a value called OK.)

This approach supports:

  • Creation via a strongly typed constructor with defaults for omitted values
  • Statically-typed updates using the with method
  • Statically-typed property access

Immutable.js includes a bunch of other methods, such as merge, that are inherited from Record or Map and present but untyped. I don’t see these as part of the usual API for my records, and that’s OK with me. I may not get a static error if I try to set a property, but I’ll get a runtime error. That’s safe enough for me, given that the convention will be to use the with method.

Use

Here’s how you’d use one of these immutable records:

let c1 = new Counter({ value: 2 });
c1.value // => 2
c1.status // => OK
c1.foo // Type error
let c2 = c1.with({ value: 3});
c2.value // => 3
c1.with({ status: 'bogus' }) // Type error

How It Works

All of the actual functionality is inherited from the Immutable.js Record class. We’re using the type system to create a simple typed facade. There are a few key steps.

1. Define a parameter type

First, define a type that contains all of the fields you’ll have in your record. Each one is optional:

type CounterParams = {
    value?: number,
    status?: Status,
};

Since Counter will have a value and a status, our CounterParams type has optional entries for both of those. This is an object type that may have up to a value of type number and a status of type Status, but it may be missing either or both of those.

This would be the logical type of the Immutable.js constructor argument and update method, if we’d written it from scratch. Since we’re inheriting the implementation from JavaScript, it doesn’t have more detailed type information.

2. Inherit from Record

Next, we define our class and inherit from Record:

export class Counter extends Record({ value: 0, status: OK }) {

There’s nothing special here. This is just straight-up, normal, untyped Immutable.js record type definition with zero type help. We’re just providing default values for value and status to the Record class creation function.

3. Type the constructor

Now, we define our constructor to take an optional CounterParams argument and delegate to super. If we construct our object with no argument, it gets all default values. If we construct it with params, we end up overriding just the supplied arguments:

    constructor(params?: CounterParams) {
        params ? super(params) : super();
    }

Note that we don’t call super with undefined to get the proper default behavior.

4. Define with

Our with method is just a typed alias for the merge method inherited from Immutable’s Map type. Unfortunately, merge won’t statically enforce our type signature for parameters, and is typed to return a Map. We use CounterParams as our type argument to solve the former issue. We cast as this to solve the latter, which works because merge does, in fact, return an instance of our type–it just doesn’t specify that in its signature at the time of writing this. Using as this convinces TypeScript that with will, in fact, return a Counter.

    with(values: CounterParams) {
        return this.merge(values) as this;
    }

And that’s it! A little repetitive, but simple and easy to understand. And we now have nice, statically typed persistent immutable model types.

The post Immutable.js Records in TypeScript appeared first on Atomic Spin.

Four Noble Truths of Ember.js

Interested in learning Ember.js? Based on my experience, I’ve put together four simple steps to help beginners start out right.

1. Ember-Ace the CLI

Ember is best with `ember-cli` (and while you’re at it, get Ember Inspector, too). Ember-CLI provides out-of-the-box project scaffolding, test creation, boilerplate code generation, file watching, and automated testing–a feat that would otherwise cost significant development time. This is one of Ember’s best features because it makes starting a project easy, and it’s extremely helpful during development.

If you aren’t a believer in convention over configuration, it may be because you haven’t worked on a large project in which several styles and project structures uneasily coexist. Since Atomic is a software consultancy and in many cases we are writing code that may be picked up by another developer or team, convention brings a great deal of value to our clients.

2. Learn Some MVC

Whereas some front-end frameworks (looking at you, Angular 1.x) blur the line between model, view, and control, I especially appreciate that Ember insists on model, view, and control being distinct entities. This makes them easier to test as individual components, and it makes the templates far easier to read and reason about. Ember has a track record of supporting this separation as evidenced by the recent deprecation of `Ember.View` in favor of `Ember.Component`.

The most important concept to get familiar with is `Ember.Component`. Not only are components the best way to write DRY, tested, and maintainable code, but they’re also the future; eventually Ember will deprecate `Ember.Controller` in favor of routable components.

One thing that the Ember beginner ought to keep in mind is that code reuse is not the most important reason to make a component; the isolation of logic (and therefore ease of testing) is at least as valuable as the potential for reuse. Code written by seasoned Ember developers will have loads of components.

3. Data Down, Actions Up!

It’s a good idea to complete the guide, especially since it will help you get acquainted with proper Ember conventions. Probably the most important thing to learn here is the way data flow works. The adage is data down, actions up.

The idea is that data should pass from the route to the controller to the component to display in the view. Then the user can trigger actions that bubble back up to be handled either by the controller or the route, changing the view. The tricky part is that because components can be nested and routes can be nested, you may find yourself bubbling things up a long way. This can be frustrating, but it’s a tradeoff you make in order to neatly encapsulate your logic into testable and understandable units.

4. Contribute to the Community

First, join the Slack! It’s full of smart people who will readily help you and, since Ember is open-source, you can get insight into the development process and vision for the future of the framework. Because Ember is open-source and has such a vibrant community, it moves very quickly. Blog posts and StackOverflow answers have a short shelf life–as they do for many front-end frameworks.

Second, get involved. If you completed the guide, perhaps you found a typo or a missing link. Contributing to the community is part of what makes Ember great.

Hopefully this guide and the links inside it are helpful in getting you up to speed on Ember. Leave a comment if I left anything out!

The post Four Noble Truths of Ember.js appeared first on Atomic Spin.

Migrating a VM from one SmartOS node to another

As noted in my previous post, migrating a VM from one SmartOS node to another is pretty straight-forward, and is documented on the SmartOS wiki.

One thing that is not mentioned is that if your VM is based on an image, then that image must already be installed/imported on the target host. If it is not, you will see an error like this:

[root@source ~]# vmadm send 0983fe0f-677e-4f54-b5cf-40184d041b6d | ssh smart01 vmadm receive
Invalid value(s) for: image_uuid

For example, on the source machine:

[root@source ~]# vmadm get 0983fe0f-677e-4f54-b5cf-40184d041b6d | grep image_uuid
      "image_uuid": "5e164fac-286d-11e4-9cf7-b3f73eefcd01",

[root@source ~]# imgadm list uuid=5e164fac-286d-11e4-9cf7-b3f73eefcd01
UUID                                  NAME      VERSION   OS     TYPE  PUB
5e164fac-286d-11e4-9cf7-b3f73eefcd01  centos-7  20140820  linux  zvol  2014-08-20

On the target machine:

[root@target ~]# imgadm list uuid=5e164fac-286d-11e4-9cf7-b3f73eefcd01

[root@target ~]# imgadm import 5e164fac-286d-11e4-9cf7-b3f73eefcd01
Importing 5e164fac-286d-11e4-9cf7-b3f73eefcd01 (centos-7@20140820) from "https://images.joyent.com"
Gather image 5e164fac-286d-11e4-9cf7-b3f73eefcd01 ancestry
Must download and install 1 image (336.9 MiB)
Download 1 image                            [==============================================================================================>] 100% 337.00MB   4.72MB/s  1m11s
Downloaded image 5e164fac-286d-11e4-9cf7-b3f73eefcd01 (336.9 MiB)
zones/5e164fac-286d-11e4-9cf7-b3f73eefcd01  [==============================================================================================>] 100% 337.00MB  25.48MB/s    13s
Imported image 5e164fac-286d-11e4-9cf7-b3f73eefcd01 (centos-7@20140820)

[root@target ~]# imgadm list uuid=5e164fac-286d-11e4-9cf7-b3f73eefcd01
UUID                                  NAME      VERSION   OS     TYPE  PUB
5e164fac-286d-11e4-9cf7-b3f73eefcd01  centos-7  20140820  linux  zvol  2014-08-20

You should now be able to successfully perform the migration.

SmartOS new install + VM migration

Set-up public key ssh auth for root user (and disable password auth)

  • put the public key(s) in /usbkey/config.inc/authorized_keys
  • add the following lines to /usbkey/config

    config_inc_dir=config.inc
    default_keymap=uk
    root_authorized_keys_file=authorized_keys

  • edit /usbkey/ssh/sshd_config. making sure that the following items are set:

    # Disallow password authentication
    PasswordAuthentication no
    # Permit root login (which should be already set)
    PermitRootLogin yes

  • reboot the server

Migrating a VM from one SmartOS server to another

This is pretty easy, and documented on the SmartOS wiki (although it's marked as "experimental" and is not documented in the man pages).

One gotcha – if you've already disabled password login as per the previous section, you'll need to create a new key pair on the source SmartOS node and copy the public key into /root/.ssh/authorized_keys on the target SmartOS node.

Assuming you can ssh from the source node to the target node, VM Migration is as easy as running the following command on the source node:

vmadm send $VM_GUID | ssh $target vmadm receive

This command stops the VM on the source node, and sends it to the target node. You will then need to start the machine on the target node and destroy it on the source node (once you're happy it's working in its new home).

Sending Data Between Actions in Android

Android Intents are objects used to trigger actions from other Android Activities. One of the most common uses of Intents is to open a new Activity in your app. Often, you will want to pass information to the new Activity. In this post, I’ll discuss a few ways to pass data between Activities using Intents, including passing primitives, Strings, and object types.

Intent Extras

Intent objects all have an extras property. This is a Bundle, storing key value pairs in which keys are Strings. It’s the same structure you see every time you override an Activity’s onCreate method. The bundle is one of the mechanisms through which Intents transfer data to other activities, and it is my main focus in this post.

Primitives, Strings, and Arrays

Primitives and Strings can easily be put into Intents. For example, the following code will store an int variable called intToSend in an Intent, giving the int the key “my-int.”


Intent intent = new Intent(this, NextActivity.class);
int intToSend = 42;

intent.putExtra("my-int", intToSend);

You can do the same with other primitive types, Strings, or arrays of primitives or Strings.

At this point, you can signal that we intend to start a new Activity that will receive the Intent we just made, as you normally would with:


startActivity(intent);

A quick digression: In case you didn’t realize this, putExtra returns the calling object. This means you can choose between writing code like the example above or chaining calls, like this:


startActivity(new Intent(this, NextActivity.class)
    .putExtra("my-string", "hello intent!")
    .putExtra("is-snowing", true));

Okay, so at this point, things are pretty simple. Now, let’s see what happens if you try to pass arbitrary objects using the Intents. Say we have a Computer class, and we have an instance called computer.

If you write:


intent.putExtra("my-computer", computer);

you should see an error; Bundles–and so Intents–can’t hold just any type of object. But if you need to pass objects between Activities, there is still hope!

Serializables

One way to pass objects in Intents is for the object’s class to implement Serializable. This interface doesn’t require you to implement any methods; simply adding implements Serializable should be enough. To get the object back within the Intent, just call intent.getSerializableExtra. You’ll probably want to cast the return value to the expected value type.

This is a very simple approach. Unfortunately, it can be slow.

Parcelable

Another approach to sending objects is to implement Android’s Parcelable interface. This interface requires three things: a public void writeToParcel method, a public int describeContents, and a non-static field called CREATOR that implements Parcelable.Creator. writeToParcel is responsible for serializing the data, and the Creator is responsible for deserializing it to reconstruct the original Parcelable object.

I’ve created a simple class called Sample to illustrate how this works. It has two data members: foo, and bar. Here’s what the code looks like:


public class Sample implements Parcelable {


    private int foo;
    
    private String bar;

    
    
    public Sample(int foo, String bar) {
        this.foo = foo;

        this.bar = bar;

    }

    
    
    protected Sample(Parcel in) {
        foo = in.readInt();
        bar = in.readString();
    }

    
    
    @Override
    
    public String toString() {
        return String.format("[Sample: %d, %s]", foo, bar);

    }

    
    
    public static final Creator CREATOR = new Creator() {

        @Override
        public Sample createFromParcel(Parcel in) {
            return new Sample(in);
        }

       
        
        @Override

        public Sample[] newArray(int size) {
            return new Sample[size];
        }
    
    };

    
    
    @Override
    
    public int describeContents() {
        
    	return 0;
   
    }

    
    
    @Override
    
    public void writeToParcel(Parcel parcel, int i) {
        
    	parcel.writeInt(foo);
        
    	parcel.writeString(bar);
    
    }

}

Breaking It Down

Let’s take a quick look at what’s going on. The writeToParcel method writes instance variables to its parcel argument. The protected constructor does just the opposite; it reads data from its argument, and stores it in instance variables. describeContents can be used to set bitwise flags indicating special data types. The Creator is used to take in an input Parcel to be deserialized. In this case, it calls the constructor, passing in the Parcel.

This is a lot of code for such a seemingly simple task. However, using Android Studio–and likely other IDEs if you choose–almost all of the code is written for you. In fact, the only part of the code above that I wrote myself is:


public class Sample implements Parcelable {

    
	private int foo;
    
	private String bar;

    
	
	public Sample(int foo, String bar) {
        
		this.foo = foo;
        
		this.bar = bar;
    
	}

}

One last point: Why would anyone use Parcelables when they take more work than Serializables? The main reason is that Parcelizables work much faster than Serializables. Experiments in this blog post show Parcelables to be 10 times faster in serializing objects than Serializables. The speed difference is due to Serializables using reflection to determine the serialization.

Well, that’s it for the basics of transferring data between Intents using extras. Happy data sending!

The post Sending Data Between Actions in Android appeared first on Atomic Spin.

Terraform Version Restrictions

One of my favourite forthcoming Terraform 0.8 features is the ability to restrict the versions of terraform a configuration file can be run by. Terraform is a rapidly moving project that constantly introduces new functionality and providers and unless you’re careful and read the change logs, and ensure everyone is running the same minor version (or you run terraform from a central point like Jenkins), you can easily find yourself getting large screens of errors from using a resource that’s in terraform master but not the version you’re running locally.

The new terraform configuration block allows you to avoid these kinds of issues by explicitly declaring which versions your code requires -

    $ cat resources/my_resources.tf

    terraform {
        required_version = "> 0.8.3"
        # or specify a lower and upper bound
        # required_version = "> 0.7.0, < 0.8.0"
    }

    $ ./terraform-8 plan resources

    The currently running version of Terraform doesn't meet the
    version requirements explicitly specified by the configuration.
    Please use the required version or update the configuration.
    Note that version requirements are usually set for a reason, so
    we recommend verifying with whoever set the version requirements
    prior to making any manual changes.

    Module: root
    Required version: > 0.8.3
    Current version: 0.8.0

While it’s not a shiny new piece of functionality I think this change will be greatly welcomed by terraform module authors. It shows a further step in maturity of both the tool and its emerging ecosystem and I can’t wait for it to become widely adopted.

Tips for Making Persuasive Arguments

We can all agree that making persuasive arguments is both exceedingly critical to collaboration and exceedingly difficult. Thus, I’ve been delighted to work through a book on persuasion–The 7 Triggers to Yes. It’s a new favorite of mine, and in this post, I’ll share the seven triggers, discuss one in more detail, and describe some of the other great content in the book.

7 Triggers to Yes

The Approach

The author of the book begins with one persuasive argument–that to make your own arguments, it’s better to appeal to someone’s emotions, intuition, and gut instincts, rather than to the the logical, data-driven parts of the brain. He then introduces seven tactics for appealing to your partners’ biases.

The Triggers

The triggers are, in no particular order:

  • Friendship
  • Authority
  • Consistency
  • Reciprocity
  • Contrast
  • Reason why
  • Hope

Illustration of appealing to gut instincts.
The author argues it is better to appeal to someone’s gut instincts (the amygdala) than their rational brain. Image credit : The 7 triggers to yes.

The Friendship Trigger

Allow me to go into a little more depth on the friendship trigger. The author suggests that this trigger forms the groundwork of the others. Without first establishing a basic human connection to our partner, we aren’t going to get anywhere.

Similarity is important–by dressing similarly, discussing shared interests like children, and finding a common background, we establish a base connection with others.

Thus, my tip for you: When engaging with a new person, work first to get to know the other person a bit. Getting straight down to business will lead to only moderate success at best.

The Rest of the Book

Of course, the book also goes into much more detail on all of the triggers by introducing them, giving examples from real life, and sharing ideas for how to make use of each trigger. It concludes by providing a framework with which to form your persuasive argument–start with this trigger, weave in this other one as appropriate, etc.

I highly recommend this text to anyone who needs to collaborate, which is pretty much everyone on the planet. I found the tips concrete, useful, and immediately applicable to every aspect of real life.

Many thanks to Darrell Crawford of The Vantage Group for recommending this book and discussing it with me and my colleagues!

The post Tips for Making Persuasive Arguments appeared first on Atomic Spin.

Type Systems Aren’t Magic

There’s a lot of enthusiasm for typed languages these days, now that languages with good type systems are closer to the mainstream. I’ve even seen some people go so far as to suggest that the future of programming involves typed languages, and dynamic languages are on their way out.

For practical reasons, most of my career has involved completely dynamic languages like Ruby and Clojure, or inferior typed languages whose type system confers poor value to pain ratios, like C and Java. I’ve been learning Haskell recently to gain more exposure into what it’s like to have a good type system, and what I’ve seen so far is that types aren’t magic or a panacea.

What is a Modern Type System?

I’ve come to think of a type system as an intentional engineering tradeoff where you provide constraints around values as they flow through your source code. In return, the compiler can do some fancy constraint solving to double-check that what you’ve written makes some semblance of sense.

In a good typed language, functions are values, too, and the language uses some super cool techniques to infer things you’ve left unspecified. It’s almost like having miniKanren in your compiler.

What kind of information can you specify about values in your type system? Well, that’s really up to the language, and it’s totally orthogonal to whether the language has any type inferencing built-in. When you hear talk of a strong type system, it’s common to, at a minimum, decomplect the possibility of nil from other values. For example, by indicating that a value has a type of string, it cannot possibly be null.

If you really need to indicate that a value could either be a string or no value at all, Haskell provides algebraic data types that can express this (among other things). This particular type is very common and has a name: Maybe. Swift, on the other hand, allows you to approximate this using enum types, which are very similar but more cumbersome in syntax and less flexible with recursion.

What Can Types Express?

Let’s consider what a Haskell type would look like for a coordinate in a two-dimensional Cartesian system:

data Point = Point Int Int

This is an algebraic type: Point is a type that has an Int AND another Int. You can use the first as the x coordinate, and the second as the y coordinate.

What if you wanted to use the type system to constrain a value such that it could only be a point in the second or fourth quadrant? That is, either the x coordinate or the y coordinate (but not both) must be negative? As it turns out, you can’t do this.

As another example, let’s say that you’re creating a data type that describes the state of a game such as Othello, using an array of Ints or some other type. There’s no way to define your data type so that it is impossible to represent invalid or nonsensical game states, such as having pieces that don’t touch any others on the board.

If this is a concern, the common solution to this problem would be to create two constructors for your data type. The first constructor would blindly accept anything that type-checks, and it would be declared private or isolated from the rest of your application. The second constructor would apply some domain logic or validation to the values that are passed in and conditionally leverage the former constructor.

When used carefully, this will give you some confidence that, across the rest of your application, you’re not likely to have an instance of your data type that doesn’t actually make sense.

Conclusion

Types aren’t a panacea, and they’re not magic. It’s possible to use a strongly typed language to express logic that is actually not correct for your problem domain. Types won’t prove your software for you, except in an extremely narrow fashion. For some reason, I was slightly surprised when I realized this.

Now that I’ve gained some more familiarity with types, I’m looking forward to contrasting this to Clojure’s Spec. In Spec, for example, it would be possible to express and validate (and generate!) that data matches my above examples.

Inherently, when you use a type system, you are giving up some flexibility. You trade the ability to express certain things. In return, you are reassured that, from a narrow perspective, you’ve expressed something that makes sense in a broader scope.

The real magic of a language like Haskell comes from its algebraic types. By going a bit further than, say, Java, and encoding some simple logic in the types themselves, you gain some limited ability to check the logic across your application.

The post Type Systems Aren’t Magic appeared first on Atomic Spin.