 Hi, everyone. OK. Oh, wait, one more thing here. Show this. OK, great. So let's get started. My name is Michael Bernstein. I'm a New York City local. I work here for a company called Code Climate. I'm really happy to be speaking here at Goruko for the second time. Thank you so much to the organizers and to everyone else for supporting me and for your attention today. I've been a community, a part of this community for a long time, so it's always a thrill to speak here. So I like to start out by saying I'm obsessed, not with anything in particular, just in general. And that's kind of the tenor of this talk and of the few talks I've been giving over the course of the past year and a half. And I want you to be obsessed too, and I don't have a ton of time, so let's get started. This is what my desk, quote unquote, my desk at home usually looks like. This is my little end of our dining room table that I'm allowed to use when my family is not eating at the dining room table. And at that very table, I started studying types back in January or so, and I became really obsessed with the deep beauty to be found in the origins of types and type theory and how they interact with computer science and programming languages. And so this talk is gonna be a presentation of some of these primary sources that are in this photo. And I'll be referring to a lot of literature throughout the talk. And the idea there is to provide you with a primary source driven exposure to these ideas that I'm trying to introduce you to so that you can go on and figure out for yourselves how to interpret this information. So I'm gonna go through a lot of terms. We're gonna go pretty deep and hopefully we'll come out the other side with something practical. So let's get started. So what are types? Type systems and static typing come up a lot in the discussions of programming languages, but we don't always take the time to define what we're talking about. That's sort of true of computer science discussions in general, but it seems to be endemic to discussions of types. And during my research, I found a really great quote from a guy named Nick Benton, who's a researcher at Microsoft. He gave a presentation called What We Talk About, What We Talk About Types, and he summed it up in the beginning of the slideshow he has this quote, we don't talk about types, we argue about them, right? And so why is that? Because I think we don't take the time always to define our terms. The subject matter is controversial, as controversial as things get in computer science. And so like I said, we're gonna start out with some definitions. And the definitions that I'm gonna show you today all come from one really great reference work written by Luca Cardelli in 1996 called Type Systems. You might wonder why should we trust Cardelli, right? Why do we care about his particular point of view on the matter? Well, let's just take a look at his webpage, okay? So you see how outdated his webpage design is. And I'm gonna clue you in actually, this is a breaking piece of research that I've been doing. I've been trying to correlate skill in computer science with homepage design outdatedness, somewhat prematurely calling it Bernstein's Law. I'm sure you'll give me the latitude there. You are my people after all. So I don't have time to dig into this now. It's fascinating research actually, but let's get back to the definition. So a type according to Cardelli is a collection of values. It's an estimate of the collection of values that a program fragment can assume during program execution, okay? That's a little bit dense, but what I mean by that is types are, and this is the thing you know what types are. Types are these things, right? Types are representations of values within the semantics and within the actual guts of your programs. Things like int, bool, string, or the bottom is a function type, a function that accepts an integer and returns a boolean. Here are some types. And so after from a type on our way up to building to a type system, we have things that are called type rules. And a type rule is a component of a type system, a rule stating the conditions under which a particular program construct will not cause forbidden errors. So a type safe programming language respects the set of type rules that are built on top of the types that comprise that system. Here's an example of a general, the shape of a type rule that you might see in the language called math in a research paper. And this might describe, for example, how operations behave over a certain set of types. So you could discuss how the addition operator should behave over various types. You would give rules for booleans, or strings, or integers, and those would be the type rules that you would build up to create your type system. Which is a collection of type rules for a typed programming language, the same as a static type system. So that's what we're talking about when we talk about types. When we talk about type systems, we're talking about collections of rules that are formalized on top of collection, on top of types, which are this idea that we have about collections of values. We know that we need to represent these raw bits as something in our programming languages, and those are types. So here's a little bit more of that funny looking language called math. And this is a type system, a collection of type rules, a pretty simple type system. I think it's some system W variant or something like that. You don't really need to know what it is, but it's kind of cool to look at it for a second. That was convenient. Okay, so from that, we have a statically checked language. This is very important. Does not say statically typed, it says statically checked. And I really like this little twist that Cardelli gives us on this definition. So practically speaking, we speak of languages as being statically typed or dynamically typed, but really they are statically or dynamically checked. So a statically checked language is a language where good behavior is determined before execution. So before we execute the program, we can use this type system to determine whether or not our code abides by those rules and therefore will execute safely. Good behavior means that the program doesn't go wrong or produce garbage output. Examples of statically checked languages are Haskell, Go, Java, et cetera. So we have a type system, we use it before we run our code, we can statically determine some aspects of our code. And that is as opposed to a dynamically checked language or a language where good behavior is enforced during execution, like our beloved Ruby or Clojure or JavaScript or a variety of other dynamic languages. This is what we're used to programming in Ruby, but of course it has certain trade-offs as do the statically typed variants. And this trade-off, the question, why do we choose a dynamic language sometimes and why do we choose a static language sometimes is sort of at the heart of this talk. What are the inherent trade-offs? What are the good things about dynamic languages? What are the bad things about dynamic languages, et cetera, et cetera. It turns out it's all about the kind of work that you're trying to do and the potential workload that that program will do when it's in production. So typically we think about dynamic programming languages being good for building, rapidly building smaller programs to prove specific points, to say I wanna know, can I write a program to do this simple thing? Does this algorithm make sense? I want a minimum viable product for my startup.io startup, right? I might use a dynamic language to accomplish that for a variety of reasons. On the other hand, if I'm building a financial application, or if I know from the beginning that my program is going to be quite large, I might choose a statically typed programming language in order to get some of those benefits. It's all about trade-offs. Not trying to sell you anything without telling you that there is always another side to the story. So that checking is called type checking, right? That idea that we check to see whether or not the execution of the program will work or not is called type checking. That's what we're used to. What we usually talk about when we talk about a type system or types in a statically typed language is we're usually talking about that part of the program that checks the types for us. The process of checking a program before execution to establish its compliance with a given type system, which are those rules, and therefore to prevent the occurrence of forbidden errors. So just as types had that definition of a collection of values or the potential for a collection of values, type checking has, sorry, type checking has this dual in type inference. They're sort of two sides of the same coin. Type inference is the process of finding a type for a program within a given type system. So you can have code that you annotate with types and ask the type checker if it is well typed, or you can give it code that is unannotated to a type inferencer and attempt to extract from that code what the types are in that code. Sounds challenging. Ta-da, it's magic. It actually works. It's pretty cool, okay? The power of type inferences and the power of type checkers are completely entangled with each other. It's foolish to say that one is more powerful than the other because they really are kind of exactly the same in their own way. So that's what types are, okay? In less than 10 minutes. So how can they help us? So we walk through the definition. Let's talk about how they can help us and why they matter, okay? Why do we want to investigate the origins of types? Why are typed programming languages something that people care about? Why are we talking about this at a Ruby conference where we're all somewhat secure knowing that we like our dynamically typed programming language and actually that's fine. So the reason that I want to talk to you about it is because I think we have a lot to learn from these lessons and the first paper that really made me fall in love with the deep philosophy and the deeper inner beauty of types and their relationship to programming language and mathematics is a paper by Philip Wadler from 2014 called Propositions as Types where he sort of looks back on his career and writes a love letter to this subject matter that he spent so much time studying. It made me dig really deep and the reason was I was flipping through the paper and I saw this image in the paper. Not the typical image that you see in a computer science paper, right? This is an image of the plaque, a gold anodized plaque that was sent on board the Pioneer 11 spaceship in 1973. This is supposed to be to express to alien lifeforms who find this plaque what humanity is all about and how we communicate, okay? It's one of several plates so we'll give them the benefit of the doubt there. And so Wadler is trying to point out some of the fundamental universal laws of logic and communication as expressed through computation and types play a big role in this. And just to do our little authority check, okay, it's Wadler's homepage, okay? We're good, right? So this paper discusses what is known as an isomorphism, a deep structural equivalence. So let's stare at that word. If everyone stares at that word all at the same time, something cool will happen, right? I'm breaking it down into its constituent parts, iso and morph or equal shape, right? So this paper is about this deep structural equivalence between what we do with logic and what we do with types in programming languages. In other words, there is this deep, very well-known idea that propositions are types. The guts of mathematical proofs are made of the same kind of stuff that make up the types in the programming languages that we use, whether dynamic or static, right? Keep in mind that the fundamental laws here hold over the execution of your program, whether you choose to use them or not, right? Because even if your language is dynamically checked, it's still checked, right? You're using a type safe language. And the foundations of this are fundamental to how they work. And so what we see here, what Wadler points out, this is from Wikipedia, actually, which is its own thing, but on the left, we have the bits and pieces that make up logic. And on the right, we have the bits and pieces that make up type systems in programming languages. And those things that are next to each other are on the same line because they correspond with each other. There is an isomorphism between the stuff on the left and the stuff on the right. There is a deep structural relationship between these things. Wadler in this paper is really trying to get you excited about the fact that this exists. So this was something that was discovered independently by various people, computer scientists, mathematicians. They started to see some similarities and the deeper they dug, the more they saw that these similarities just sort of didn't stop. So proofs are programs. And normalization of proofs is evaluation of programs. You normalize a proof to run it, to see is it doing the thing it's supposed to do, the way that you evaluate a program. So you might ask me as a programmer, what does it mean to normalize a proof? And I could say to you, oh, it's exactly like evaluating a program. And that's not a metaphor. That's actually how it works. It works the same way. So now you know what proof normalization is. And so to prove this point, Wadler does something really cool. See, Wadler, you know, he's bilingual. He knows math and he knows computers. I only know really computers, okay? But I can, I noticed something really cool, a trick that he played in this paper when he was trying to point out this deep equivalence that I was talking to you about. On the left, this is two segments from the paper. On the left you have a proof and on the right you have a program. And you have the rules that comprise how to instantiate and evaluate proofs on the left and on the right. So if you draw the little line on the left, and you draw a little line on the right, you'll see that they look kind of similar. They're the same. And Wadler doesn't really point that out, but that's like, it's there for a reason. That's why that text is laid out that way. There is a deep structural equivalence between types and logic. And so since we know an awful lot about logic, and we've been studying math for a long time, the cool thing is that we can use those lessons to learn things about how programs work, or maybe how programs should work or could work, or whatever. So out of this relationship, if you study this, you'll get a deep understanding. Maybe a little too deep for practical application, but that never scared me before. We shouldn't scare you either, because after deep understandings comes things like mathematical certainty. Not the typical feeling that web developers have about their code, right? I'm not mathematically certain about almost anything in my Rails stack, right? But that's a trade off, right? On the other hand, I was actually able to make it, right? So there's that. It was a good thing, right? We like that. We're fine, right? But it's nice to be certain sometimes. We can leverage proof tools for programming purposes. And finally, we can reach enlightenment, okay? That's a good word. But more practically, what do we really get out of types? Like what does having a rich modern type system give us that we don't have already? So in a book called Types and Programming Languages, also known as TAPL, Benjamin Pierce, who's a professor at the University of Pennsylvania, this is kind of like the textbook. If you're learning computer science through types, you'll do it with this textbook. It's its own way, its own perspective on how the semantics of programming languages work. We'll visit that idea soon again in a minute. So it's a great, if not very heavy introduction. It has a lot of practical information about how to apply type systems. And in the first chapter of the book, Pierce covers what they're good for, why you should care about them. And you're just gonna have to trust me with respect to his webpage. I didn't wanna overdo the gaggle too much. Okay, so detecting errors, that's one thing. You detect them early on in the process, right? The idea of a statically checked program is one where you can run it through a type checker first to tell you a certain class of errors, like typos or the fact that you didn't provide all of the parameters to a function or any number of things that are extremely frustrating when you encounter them in a dynamic program because sometimes you make a change over here and something breaks over there or you have 7,000 source files all being glommed into memory at the same time with no actual introspection about how those objects connect with each other once they're in memory that can get frustrating and is not very mathematically certain, right? It's really challenging to feel like you know what's going on sometimes. So in addition to detecting errors, types give us new angles on abstraction. They give us ways to reason about programs, to reason about the design of our programs and are actually tools for design. You can design a program through its types. Sometimes we talk about that as designing programs through their interfaces. What you're really talking about is the fact that the interface is the thing that's exposed to the rest of your program is its type representation. When you discuss a method or what a method does, you can write all the pros you want about whatever you wanna put after your pound signs in your source file. But the bottom line is what does that method accept and what does it return? That's really what matters in the long run. And on that note, it doesn't change and when it doesn't change, it's called documentation, right? So you have documentation that you can extract from your programs that does not break or get out of date. And that's a really nice thing that you can get from type systems that you are not capable of leveraging in a purely dynamic language. I went back to that, sorry. Abstraction documentation and finally safety. So it can prevent memory safety issues, recent issues with C in various SSL vulnerabilities have shown us that there's a price to pay for not having memory safe type safe programs. And safety is good. Safety and certainty are both really nice things to have in large systems and there's so much more. So interacting with a modern type system, it feels like magic. You can have a robotic guide to help you with your programs, right? You can call it names if you don't like it and it won't get mad. Practically it can catch all kinds of things and actually can guide you in the creation of programs that are very well thought out. Because just as you front load the checking in the statically checked program, right? You must front load some of that design effort, right? Those two things come with each other. When you use a statically checked language, you can't write the programs the same way as you do a dynamically checked language. Though the combination of an inferencer and a type checker is quite potent. And in OCaml and Haskell land, for example, the type inferencers are capable of inferring quite amazingly what your potential, what your types are. So you actually have to annotate everything and you can still get some of the benefits. So now let's talk a little bit about Ruby since we're at a Ruby conference. We've seen the potential value of modern and fully featured type systems, right? We've seen that there is a class of theoretical and practical applications for them. And so what prevents us from having this in one of these cool type systems? I want a cool type checking robot and Ruby, why can't I have it? Has anyone tried to do that before? It turns out, yes. Let's talk a little bit about that. So the first exposure that I had to this idea of bringing a type system to a dynamically check language was in the work of this guy Ambrose who wrote his honors, I think his undergraduate honors thesis on a practical optional type system for closure. So if you don't know closure, it's a Lisp, it runs on the JVM, it's dynamically typed. And the fact that it is dynamically typed creates some pretty painful problems when you're writing closure in a practical sense, particularly around the null problem. So JVM has nulls, your integer types under the hood and closure can cause some really nasty problems. So Ambrose asks a question, can you create a practical optional type system? So is that idea practical? Can you type check part of your program statically and the rest of it dynamically? What kind of, I don't know, that was the big question. So it turns out you can. Here's an example. We don't really have to go over this, but if you just see this ANN and Colatz, this is some numerical algorithm and it is annotated saying it takes a number and it returns a number. So that's a new thing. It's a macro enclosure, which is really cool. You can do a lot of really interesting things with that. So it turns out that his work was based on work done by Tobin Hochstadt called type scheme from scripts to programs. So there comes that idea again of where you might want dynamic types and where you might want static types. So in dynamically typed languages, it's really easy to create these things that we call scripts, right? That's why they're called scripting languages. You kind of just wanna pick up some computational stuff, do some things with it and say, okay, I'm done. I don't wanna have to really think about it. I'm not, it's not really worth doing any of that upfront design thinking. I just kind of wanna have some stuff flow in and some other stuff flow out. So it turns out that this work was done for typed racket, which is another Lisp and Ambrose took the work from that and extended it for closure. And this work came out that was based on some earlier work by Tobin Hochstadt and this other paper by Seek called gradual typing for functional languages. So it turns out that this whole idea of bringing a static type checker to a dynamic programming language is something that has been around for a long time. People have been trying to do it forever in various different ways, ever since the first Lisp's in the 60s or the 50s. By the time the late 60s, early 70s were rolled around, people were like, well, how do we annotate this program to talk about what it actually does at a higher level so we don't have to just kind of run it to see what goes wrong? So this term is called gradual typing. It's a cool term and Tobin Hochstadt defines it as safe interoperability between typed and untyped portions of a single program. So it turns out that this is possible. Having your dynamic free-for-all in one file and your very statically locked down module in another file is okay. They proved it using math and the big idea here is that as long as the boundaries between the dynamic and static parts of your language are annotated or realized, then you'll be fine. You don't actually have to declare types for everything within the body of a function. If I go back to that closure example, you'll see just the function declaration itself was typed. You don't have to type every variable declaration inside the body, right? And that becomes easier in a functional programming language. So has anyone tried to do this for Ruby? Yes, they have. In 2011, some researchers publish a paper about dynamic inference of static types for Ruby. This was really exciting for me when I came across it because it uses a lot of the same ideas. Essentially, what they did is they created what they called a constraint-based dynamic type inferencer. And it's a technique that infers static types based on dynamic program executions, which is brilliant. So what you can do is you have a class, class A, you annotate it by saying infer its types, then you run it, like with your tests, or you manually execute it, and it tells you what the types of the functions, of the methods in that class are. On the other hand, once you get those types back, you can annotate them, right? And then use that to statically type check your program later. It turns out that in practice, this is very challenging to get to perform very well. You might not be very surprised by that, right? So you're not gonna do this in production, right? So this work was taken a little bit further, a couple years later, by some of the same researchers at the same programming language lab, and they created what's called the Ruby type checker. And this is a compromise because, like I said, type inference in these languages is very challenging to get 100% correct. So the idea is how do we make it easy to migrate parts of our program to being statically checked from being dynamically checked? And so RTC is designed with that in mind. It's designed so programmers can control exactly where the type checking occurs. Type annotated objects serve as the roots of the type checking process, and unannotated objects are not type checked. So you can provide types where you want, and that looks something like this. You have a person class, you say that it's annotated, there are some type signatures, okay? So personnel ID takes nothing and returns a fixed num. The type sig from ID takes a fixed num and returns a person, and type sig manager takes nothing and returns a manager or false, okay? That's something that's called the union type, which is really helpful. Part of that deep equivalence, the reason why we know union types work is because they existed math, and you can sort of draw one of those deep parallels between logic and types at that very spot. So the challenges are kind of obvious. It's hard to do this very well on Ruby. Not a lot of people are spending any energy on it. This is the work of some researchers who are just kind of interested in it. They're working toward doing things like providing a type safe rails, which would be a really interesting thing. Not as impossible as you would think, okay? It's hard, but it's possible. It's not impossible, okay? That's math too. One more thing I wanna shout out in this section is a paper written in 2005 called, experience from developing the dialyzer, a static analysis tool detecting defects in Erlang applications. So if you're curious about, have any dynamically typed programming languages successfully utilize type theory to make their program development better? The answer is yes, Erlang, like a lot of other things. They've been doing it right for a really long time. And he says, in this paper, testing, no matter how thorough, cannot of course detect all software defects. Tools that complement testing such as static analyzers have their place in software development regardless of language, okay? It speaks very closely to the relationship between types and tests. It's a very interesting piece of software. You should check it out. So in conclusion, what can we learn from all of this? This whirlwind that I just took you on. Number one, type systems are really, really cool. I hope no one disagrees. Types are a prism. There are things through which you can see your code in a new way. And they're kind of trippy if you stare at them in the light. A type system can be a guide. A technology that we don't leverage that we could. Remember all this research is out there. If someone wanted to build an efficient version of either of those pieces of software that I showed you that pertain to Ruby, you could, I'm positive. So why don't we do that and push it forward, get obsessed, learn more, make programming better, and let's focus on solving the problems and not fixing silly things that could be fixed with a type checker. Thank you.