There are a lot of programming languages out there to build cool apps in, and one of the major concepts you'll encounter in all of them is the concept of the many types a piece of data, such as a variable, can be. Common data types you'll encounter are numbers
like 34313, strings
of text like "words for the win", arrays
for when you want a collection of items, null/nil
for representing nothing at all, and objects/structs/classes
for when you're composing together possibly many different pieces of data to represent something, like details about a car or a user account on a website.
And a difference between the many programming languages you can choose from, is that they have different sets of rules for how you work with different data types. Those rules, are called type systems, or typing disciplines. So in this tutorial, I'll show you an overview of different programming languages' type systems, as told by my Havanese dog Lola the Micropanda.
Lola and I will give you a look at some of the major kinds of type systems in different languages, and for each type system, we'll talk about a different kind of thing Lola does in real life that is similar to that. We'll look at:
- Static types, like in C++
- Dynamic types, like in JavaScript
- Duck types, like in Ruby
- Inferred static types, like in Go and Haskell
This tutorial is for you if:
- 💻 You've done some programming in at least one programming language, and are familiar with functions and variables
- 📚 You're interested in learning some more programming languages
- 🐼 You like puppers who look like pandas
Static types: 💊 Going to the vet
Statically typed languages are ones where it's explicit what type a variable is from the moment it's declared, and it's strict what types you can pass into a function or use as fields on a data structure. For example, in C++, if you declare a variable to be an integer, like int myNumber = 2;
, then from that point on, myNumber
is an integer. Once you've declared myNumber
, then you can't say things like myNumber = "some text"
because "some text" is not an integer, it's a string.
A real-life example of statically-typed stuff with Lola would be going to the vet. When a vet is prescribing medicine for an adult small dog, we can't send the vet a big German shepherd, a sloth, or a small puppy (even if Lola still acts like a puppy sometimes). So some statically typed code for a vet appointment would look like this C++ code:
Prescription prescribeForAdultSmallDog(AdultSmallDog dog) {
cout << "Let's see..." << endl;
Diagnosis d = diagnose(dog);
Prescription p = makePrescriptionForDiagnosis(d);
cout << "We're all set! Here's your prescription!" << endl;
cout << "And take a biscuit for the road!" << endl;
return p;
}
Notice that in the function signature:
Prescription prescribeForAdultSmallDog(AdultSmallDog dog) {
The dog
you pass into this prescribeForAdultSmallDog
function has to be an AdultSmallDog
, like Lola. And the object the function returns is a Prescription
. The code won't run if we pass a different kind of dog or other animal into that function.
Furthermore, we also see information about types in the variables we declare:
Diagnosis d = diagnose(dog);
Prescription p = makePrescriptionForDiagnosis(d);
The variable we get back from the function diagnose
will always be a Diagnosis
. And the variable we make from makePrescriptionForDiagnosis
will always be a Prescription
.
While it feels a bit clunky to have to say the name of each variable we declare, the advantage of static types is that for any function and any variable, you know the types of the objects you're passing in. Knowing your code won't be working with the wrong type is called type safety. Another thing I like about static types when you're developing big software, is that if you run into a function you've never used before, you don't need to do so much research to find out what type of object it is expecting.
Some programming languages with static type systems include ones like C++, C, Java, Crystal, Go, Swift, and TypeScript.
Dynamic types: 📱 texting about your pet
While a statically typed language is one where a variable always stays the same type, dynamically typed languages are more laid-back about types. When you declare a variable, it doesn't need to always stay the same type, and you don't need to do anything special to call the same function using arguments of different types.
A dynamically typed part of a day in the life of Lola is texting with my family about what she's doing. Whether I texted "Lola ate two biscuits", or "Lola ate 2 biscuits", people know what I mean. Here's an example of that in JavaScript.
function textAboutDogEatingBiscuits(dogName, biscuitCount) {
let msg = dogName + " ate " + biscuitCount + " biscuits";
console.log(msg);
}
// pass in a string for Lola's name, and a string for how
// many biscuits she ate. prints "Lola ate two biscuits"
textAboutDogEatingBiscuits("Lola", "two");
// pass in a string for Lola's name, and a number for how
// many biscuits she ate. prints "Lola ate 2 biscuits"
textAboutDogEatingBiscuits("Lola", 2);
Notice that:
- We call
textAboutDogEatingBiscuits
twice: once withbiscuitCount
getting the string "two", and once withbiscuitCount
getting the number 2. - We didn't need to do anything special to do that. We can pass values of any type into
textAboutDogEatingBiscuits
! - The function's signature doesn't even mention any types!
An advantage of dynamic types is that it can be conducive to rapidly building software since you don't need to specify types of everything or do anything special to make a function allow arguments of different types.
A disadvantage, though, is you can get type errors and unexpected behavior if you use a type your code wasn't written for working with. For example, if we passed a JavaScript object into textAboutDogEatingBiscuits
, like textAboutDogEatingBiscuits("Lola", {})
, the output would be the message "Lola ate [Object object] biscuits", which only makes sense if [Object object] is a new brand of dog biscuit (hm, maybe that might make a good brand in the Bay Area).
Some programming languages with dynamic type systems include ones like JavaScript, Python, Ruby, Elixir, Julia, Perl, and much of the long-running LISP family.
Duck types: 🐱 My dog being a cat
While it's not specifically a type system, a lot of languages have what's called duck typing, which comes from the saying "if it walks like a duck and quacks like a duck, it just might be a duck".
In duck typing, a function can take in arguments of any type, and it's all right with that as long as it has all the methods or fields that get used in that function. As an example of a real-life thing that's duck-typed about Lola, Lola is quite a cat-like dog. She steals the yarn when someone is knitting, she likes to climb on top of the couch, and as we'll see in this Ruby code, she plays fetch like a cat; hiding under furniture because she's more into capture the flag than fetch.
def fetch_at_home(kitty)
toy_location = throw_toy_to_kitchen()
kitty.run_to toy_location
kitty.hide_under_the_footrest()
end
This function is made for a cat, but Lola can still be the kitty
we pass into the function too; she's got a run_to
method, and a hide_under_the_footrest
method!
Some examples of programming languages with some degree of duck typing are Ruby and Python, and Go through its interface types.
Inferred types: 🎾 Playing with your dog at the dog park
Remember in the C++ example how for static types, we had to declare the type of each variable we're using? There actually are some statically-typed programming languages where in some spots, the language can figure out what types you're working with. If a language can do that, it has type inference, and it's been around for a while but getting quite popular lately!
A real-life example of type inference with Lola is playing fetch at a dog park she and some other dogs are at. Here's what that looks like in one of the languages with type inference in the language I do the most with, Go.
func fetchAtTheDogPark(dogPark []Dog) {
lola := dogPark[0]
fmt.Printf("%s, fetch! 🎾\n", lola.name)
ballLocation := throwTennisBallSomewhere()
lola.runTo(ballLocation)
lola.bringItBackIfSheFeelsLikeIt()
}
Here's what happens with types in the Go code:
func fetchAtTheDogPark(dogPark []Dog) {
Like in the C++ example, we do still need to specify the types of our function parameters. In this case, we're passing in an array-like collection of Dogs called a "slice" (slices in Go are used the same way as arrays in other languages). So dogPark
is supposed to be the list of all dogs at the dog park.
lola := dogPark[0]
Where we don't need to specify types, is when we're making a variable for Lola as the first dog. We told Go that dogPark
is a []Dog
, so because of that, Go can infer that dogPark[0]
is a Dog because the items in the slice can't be any other type. Just like in a real-life dog park, the only animals there I'm able to throw the tennis ball to are dogs. We didn't need to specify that Lola is a dog, Go already knows that part!
fmt.Printf("%s, fetch! 🎾\n", lola.name)
ballLocation := throwTennisBallSomewhere()
lola.runTo(ballLocation)
lola.bringItBackIfSheFeelsLikeIt()
Finally, we now are able to use our Lola variable as a Dog
. We call her by her name, we throw the ball, she goes to runTo
that ball, and finally, she'll bringItBackIfSheFeelsLikeIt
.
By the way, if you like type inference and want to take that to the next level, another interesting language is Haskell (no relation to my family, Haskell in this case is a first name, not a last name). It takes some getting used to if you're more familiar with languages like Python and JavaScript, but to demonstrate what its type inference can do, here's an example of a function in Haskell.
-- larger returns the larger item out of the two
-- items passed in
largerItem oneThing otherThing =
if oneThing > otherThing
then oneThing
else otherThing
We declared a function largerItem
that takes in two objects of the same type, and didn't even need to mention a single type! We can compare two numbers with it, or two strings with it. Or, if we had a type for different dog breeds compared by size, we could call larger havanese greatDane
and get back greatDane
because Great Danes are gigantic!
But this is not dynamic typing just because our code didn't mention types; because of this line
if oneThing > otherThing
The function's body used the greater than sign, so Haskell knows that the only variables that are allowed to be passed into the larger
function are two objects of the same type can be compared with the greater-than sign. So if in your Haskell code, you call larger 3.14 sheepadoodle
, it won't run at all because 3.14
is a number and sheepadoodle
is a dog breed.
In addition to Go and Haskell, some other languages with type inference are ones like Swift, OCaml, Crystal, and Rust.
We've taken a look at five languages and four kinds of ways to do types. Type system isn't the only thing to consider when picking out a language, but hopefully when you're picking one out or learning a new language, this helps you understand that part of the programming languages and how you structure your codebase. And understand why Lola can do so many kinds of coding! 🐼
Shoutout to Vicki Langer for peer reviewing!
Top comments (3)
This was mad creative! I liked how you related concepts in type systems to your pet. You made learning engaging.
Thanks! It was inspired by a conversation with @vickilanger ! Her Python and Ruby tutorials have a lot of references to her many pets and always make me smile
Thanks for sharing this type of information. If I use the same code with the help of HTML? Recently I build Website Shepherd with the help of html. Please check my website and give me reviews.