AssertEqual
, AssertNotEqual
)string
?string
to a function that expects an integer
.string
to a function that expects an integer
.Person
and a BankAccount
. You can't capitalise an integer
. In software, constraints are often extremely helpful.interface{}
which means "anything".%d
format string to print our messages, so change them to the general %+v
format for a better output of any kind of value.interface{}
AssertX
functions are quite naive but conceptually aren't too different to how other popular libraries offer this functionality​interface{}
the compiler can't help us when writing our code, because we're not telling it anything useful about the types of things passed to the function. Try comparing two different types.got 1, want 1
is unclear; but do we want to be able to compare strings with integers? What about comparing a Person
with an Airport
?interface{}
can be extremely challenging and bug-prone because we've lost our constraints, and we have no information at compile time as to what kinds of data we're dealing with.AssertX
functions for every type we ever deal with. We'd like to be able to have one AssertEqual
function that works with any type but does not let you compare apples and oranges.interface{}
offers but retain type-safety and provide a better developer experience for callers.comparable
and we've given it the label of T
. This label then lets us describe the types for the arguments to our function (got, want T
).comparable
because we want to describe to the compiler that we wish to use the ==
and !=
operators on things of type T
in our function, we want to compare! If you try changing the type to any
,any
) type.any
describe... anything?any
does mean "anything" and so does interface{}
. In fact, any
was added in 1.18 and is just an alias for interface
.InterfaceyFoo
with any combination of types (e.g InterfaceyFoo(apple, orange)
). However GenericFoo
still offers some constraints because we've said that it only works with one type, T
.GenericFoo(apple1, apple2)
GenericFoo(orange1, orange2)
GenericFoo(1, 2)
GenericFoo("one", "two")
GenericFoo(apple1, orange1)
GenericFoo("1", 1)
interface{}
the compiler cannot make any guarantees about the type.Push
items to the "top" and to get items back again you Pop
items from the top (LIFO - last in, first out).int
s, and a stack of string
s.StackOfStrings
and StackOfInts
is almost identical. Whilst duplication isn't always the end of the world, it's more code to read, write and maintain.StackOfInts
and StackOfStrings
to a new unified type Stack
AssertEquals
- we've lost type safety. I can now Push
apples onto a stack of oranges.interface{}
they are horrible to work with.Pop
returns interface{}
it means the compiler has no information about what the data is and therefore severely limits what we can do. It can't know that it should be an integer, so it does not let us use the +
operator.Stack
implementation, yuck.Stack
implementation, featuring a generic data type.Stack[Orange]
or a Stack[Apple]
the methods defined on our stack will only let you pass in and will only return the particular type of the stack you're working with:Pop
return T
so that if we create a Stack[int]
we in practice get back int
from Pop
; we can now use +
without the need for type assertion gymnastics.Push
oranges to an apple stack.Assert
functions which we can safely re-use to experiment with other ideas around generics, and we've implemented a simple data structure to store any type of data we wish, in a type-safe manner.interface{}
in most casesinterface{}
makes your code:interface{}
tells you nothing about the dataStack
implementations we importantly started with concrete behaviour like StackOfStrings
and StackOfInts
backed by tests. From our real code we could start to see real patterns, and backed by our tests, we could explore refactoring toward a more general-purpose solution.Hmm, these things look similar - but a little duplication is better than coupling to a bad abstraction
OK, I'd like to try to see if I can generalise this thing. Thank goodness I am so smart and good-looking because I use TDD, so I can refactor whenever I wish, and the process has helped me understand what behaviour I actually need before designing too much.