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 usageSome of legal valuesAll legal values
- Specification
Works for one caseReturned value is valid
- Common Errors
No TyposNo Unexpected nullCaller deals with failure
- Guarantees
Forced to call with correct typeNo side effectsNo ExceptionsNo 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 TyposNo Unexpected nullCaller deals with failure
- Guarantees
Forced to call with correct typeNo side effectsNo ExceptionsNo infinite loops
Haskell
find xs x =
--Implementation ommitted
- Documentation
Example usageSome of legal valuesAll legal values
- Specification
Works for one caseReturned 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 ExceptionsNo 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 caseReturned 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 ExceptionsNo 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 ExceptionsNo 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 caseReturned 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