Tests vs Types

Colt Frederickson

@coltfred

Who am I?

  • Senior Software Engineer at Rubicon Project
  • Over 10 years working in the code mines
  • Striving to make better software, always

Types?

Crappy Types?

public boolean equals(Object anObject) {...}
List<? extends Number> l = new ArrayList<>();

l.add(new Integer(3));  //ERROR
l.add(new Double(3.3)); // ERROR
private final List<Map<String,Object>> myList = 
    new ArrayList<Map<String,Object>>();

Crappy Types?

public class Person {
    private String firstName;
    private String lastName;

    @Override public String toString() {
        return "Person{" +
            "firstName='" + firstName + '\'' +
            ", lastName='" + lastName + '\'' +
            '}';
    }

    @Override public boolean equals(Object o) {
        if (this == o)
            return true;
        if (o == null || getClass() != o.getClass())
            return false;

        Person person = (Person) o;

        if (firstName != null ? !firstName.equals(person.firstName) : person.firstName != null)
            return false;
        return !(lastName != null ? !lastName.equals(person.lastName) : person.lastName != null);

    }

    @Override public int hashCode() {
        int result = firstName != null ? firstName.hashCode() : 0;
        result = 31 * result + (lastName != null ? lastName.hashCode() : 0);
        return result;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }
}

Crappy Types?

No Types?

>>> ["foo", "bar", "baz"].index(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: 1 is not in list

No Types?

>>> x = "hello"
>>> 1 + x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) 
  for +: 'int' and 'str'

Types are just labels!

Text

What are good types?

  • Implicit
  • Terse
  • Easy to use for prototyping
  • Self documenting
  • Easy for people to read

Implicit

scala> val x = 1
x: Int = 1
λ: let x = 100
λ: :t x
  x :: Num a => a
addOne x = x + 1

λ: :t addOne
  addOne :: Num a => a -> a

λ: addOne 100
  101

λ: addOne 100.01
  101.01

Terse

addOne x = x + 1

-- Or even shorter

addOne = (+) 1
data Maybe a = Nothing | Just a  

data Day = Monday | Tuesday | Wednesday | Thursday | Friday | Saturday | Sunday  
case class Person(firstName:String, lastName:String)

Prototyping

def getFilesInDir(dir: Path) = ???

def getFilesInPath(dirOrFile: Path) = 
  if(dirOrFile.isDir) getFilesInDir(dir) else List(dirOrFile)
doit x y = undefined

f x y
  | x > 1 = doit x y
  | otherwise = x + y

--Example usage

*Main λ: f 1 100
  101
*Main λ: f 2 100
  *** Exception: Prelude.undefined

Self Documenting

def getFilesDir(dir: Path): List[Path] = ???

def getFilesInPath(dirOrFile: Path): List[Path] = 
  if(dirOrFile.isDir) getFiles(dir) else List(dirOrFile)
*Main λ: :t elemIndex
elemIndex :: Eq a => a -> [a] -> Maybe Int


-- Will fail to compile
elemIndex 1 ["foo", "bar", "baz"]

-- But these work great!
*Main λ: elemIndex "foo" ["foo", "bar", "baz"]
  Just 0
*Main λ: elemIndex "foobar" ["foo", "bar", "baz"]
  Nothing

I came here for a fight!

Let's begin!

Given a list and a value, return the index of the value in the list or signify that it is not found.

  • Documentation
    • Example usage
    • Some of legal values
    • All legal values
  • Specification
    • Works for one case
    • Returned value is valid
  • Common Errors
    • No typos
    • No unexpected null
    • Caller deals with failure
  • Guarantees
    • Forced to call with correct type
    • No side effects
    • No exceptions
    • No infinite loops

The Contenders

  • Python
  • Python + Tests
  • Haskell
  • Haskell + Type Annotation
  • Haskell + Type Annotation + Tests
  • Idris
  • Idris + Tests

Python w/o Tests

def find(xs, x):
    #implementation omitted 
  • Documentation
    • Example usage
    • Some of legal values
    • All legal values
  • Specification
    • Works for one case
    • Returned value is valid
  • Common Errors
    • No Typos
    • No Unexpected null
    • Caller deals with failure
  • Guarantees
    • Forced to call with correct type
    • No side effects
    • No Exceptions
    • No infinite loops

Python with Tests

def testHappy():
    assert find(["foo", "bar"], "bar") == 1

def testSad():
    assert find(["foo", "bar"], "baz") is None
  • Documentation
    • Example usage
    • Some of legal values
    • All legal values
  • Specification
    • Works for one case
    • Returned value is valid
  • Common Errors
    • No Typos
    • No Unexpected null
    • Caller deals with failure
  • Guarantees
    • Forced to call with correct type
    • No side effects
    • No Exceptions
    • No infinite loops

Haskell

find xs x = 
  --Implementation ommitted
  • Documentation
    • Example usage
    • Some of legal values
    • All legal values
  • Specification
    • Works for one case
    • Returned value is valid
  • Common Errors
    • No Typos
    • No Unexpected null
    • Caller deals with failure
  • Guarantees
    • Forced to call with correct type
    • No side effects
    • No Exceptions
    • No infinite loops

Haskell + Annotation

find :: Eq a => [a] -> a -> Maybe Int
find xs x = 
  --Implementation ommitted
  • Documentation
    • Example usage
    • Some of legal values
    • All legal values
  • Specification
    • Works for one case
    • Returned value is valid
  • Common Errors
    • No Typos
    • No Unexpected null
    • Caller deals with failure
  • Guarantees
    • Forced to call with correct type
    • No side effects
    • No Exceptions
    • No infinite loops

Haskell + Annotation + Test

testHappy = assertEqual "Should find foo" (Just 0) (find ["foo","bar"] "foo")

testSad = assertEqual "Should not find baz" (Nothing) (find ["foo","bar"] "baz")
  • Documentation
    • Example usage
    • Some of legal values
    • All legal values
  • Specification
    • Works for one case
    • Returned value is valid
  • Common Errors
    • No Typos
    • No Unexpected null
    • Caller deals with failure
  • Guarantees
    • Forced to call with correct type
    • No side effects
    • No Exceptions
    • No infinite loops

Idris

%default total

find : Eq a => Vect n a -> a -> Maybe (Fin n)
find xs x = ...
  • Documentation
    • Example usage
    • Some of legal values
    • All legal values
  • Specification
    • Works for one case
    • Returned value is valid
  • Common Errors
    • No Typos
    • No Unexpected null
    • Caller deals with failure
  • Guarantees
    • Forced to call with correct type
    • No side effects
    • No Exceptions
    • No infinite loops

Idris + Tests

ex : find ["foo", "bar"] "bar" = Just 1
ex = Refl
  • Documentation
    • Example usage
    • Some of legal values
    • All legal values
  • Specification
    • Works for one case
    • Returned value is valid
  • Common Errors
    • No Typos
    • No Unexpected null
    • Caller deals with failure
  • Guarantees
    • Forced to call with correct type
    • No side effects
    • No Exceptions
    • No infinite loops

Summary

Python Python + Tests Haskell Haskell + Annotations Haskell + Tests Idris Idris + Tests
Example Usage
Some legal Values
All legal values
Works for one case
Returned value is valid
No typos
No unexpected null
Caller deals with failure.
Forced to call with correct type
No side effects
No exceptions
No infinite loops

Thank you!

Please don't throw things!

Tests vs Types

By Colt Frederickson

Tests vs Types

  • 1,459