Type systems as told by my dog


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.

Adorable black and white Havanese dog in the kitchen standing on her hind legs

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:

This tutorial is for you if:



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:

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!

Adorable Havanese dog poking out of the footrest with her tennis ball

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.

Lola sprinting around on Crane Beach

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!

Source: DEV Community

November 27, 2021
Category : News
Tags: beginners | codenewbie | programming

Leave a Reply

Your email address will not be published. Required fields are marked *

Sitemap | Terms | Privacy | Cookies | Advertising

Senior Software Developer

Creator of @LzoMedia I am a backend software developer based in London who likes beautiful code and has an adherence to standards & love's open-source.