 Okey, this is a talk about soft typing Nw soft typing is something that Mark spoke about at Rubynoise for last year And he spoke about it as one of the possibilities for Ruby 3 So word of soft typing, well in essence, it's a way of combining the benefits of static typing, with the flexibility of dynamic typing So to give you an example of what this might mean here's a little piece of Ruby code, very simple thing It's just a method that takes an argument, anything and then comes times 2 in it Now, because this is Ruby, we don't need to tell it anything about the types, but this also means we don't know much about the types but there are some things that we can work out. So, for example, just by looking at the code, we can say that this thing, whatever it is, has to respond to the times 2. And we go a little bit further. We can say if we call this method passing in some kind of object, this object also has to respond to times 2. And we know that if it doesn't respond to times 2, then our code is going to explode with some kind of runtime error. The important thing to get across here is, when I say static typing, static means that we can work stuff out without having to run the code. This is a huge idea for Ruby. Previously in Ruby there was no way of thinking about things like correctness without running the code, but this is a big philosophical shift in the way that we think about things like correctness in Ruby. Given that this is a candidate for Ruby 3 and given it's such a big deal, you think a lot of people would be talking about soft typing, but nobody really seems to be. There was one talk by Tom Stewart at RubyConf Australia called Consider Static Typing, and this serves as a kind of primer on types and what they're useful for, and at the end he says a little bit about soft typing in Ruby, but not that much. But apart from that, I've heard nothing. And even worse than that, going to conferences and speaking to people, nobody seems to be thinking about it and nobody seems to be talking about it. So I'd like to address that today, and I thought it would be a good idea to give a talk about what soft typing is and how it works, and that really means talking about type reconstruction for a lit binding structurally-typed language such as Ruby, and then working out how we deal with inevitable failures. I thought it would be a good idea to talk about this, but then I wrote the talk and it turns out it's actually really boring. There's an awful lot of really heavy academic theory in there, and I think a lot of you would just get up and leave. And worse than that, it's boring and it's also really difficult. I mean, I'm not sure I actually could talk to you about how soft typing works. It's too hard for me, and if that wasn't bad enough, it's also really impractical. I mean, even if I could teach it to you by the end of it, all you'd know was some mathematics about type systems in functional languages like ML that have never left academia, and you couldn't really take that and apply it to your everyday Ruby work. Boring, difficult and impractical. This is going to be like the best talk you've ever seen. Now, I think this is the way a lot of people think about type theory in general, and certainly if you've read the soft typing paper, these are some words that are very good for describing it. But this isn't the way that I feel about type theory, and it's not the way that I feel about type theory in Ruby. I think it's actually a really exciting area. I think it's something that we can engage with as Rubyists, and I think it's something that can improve our Ruby code. So, what I want to do today is try and make it more accessible. I can't tell you everything you need to know about soft typing, but I can maybe tell you where to start or give you an idea of how to explore this topic. I'm going to do this today by writing a static type analyser. So, what is a static type analyser and how do you write it? Well, there's a talk by a guy called Matt Might. He's a professor at the University of Utah, and the talk is what is static analysis. Quite early on in this talk, he gives a general formula for how we write a static analysis tool like a static type analyser. He says, what we do is we take an interpreter and we make it more abstract. So, to give you an idea of what this means, imagine you've written a programme and you feed it into the interpreter, and what the interpreter is designed to do is run the code and produce the resultant value. So, you might run your programme and it produces the value 42. Now, to make this more abstract, we still return the same kind of thing just with less detail. So, instead of saying the answer is 42, we'd say the answer is just a number. It's something more abstract about the value. Now, it doesn't have to be as clearly about types as a number. You could say it's a number greater than 10, or maybe we just say it's a value, and that can be a useful thing to know on some occasions. So, I'm going to write a static type analyser by writing an interpreter, but I'm not going to do this for Ruby. Ruby is a very complicated language, and we get bogged down into details very quickly. So, instead of that, what I'm going to do is create a very simple language, a very simple programming language. Then I'm going to write an interpreter for this in Ruby. Then I'm going to write a static type analyser by making my interpreter more abstract. Okay, so this is a language I want to start with, and it is very, very simple. I can take some numbers and I can assign it to a variable, and then later on I can recover that variable. So, how do we write an interpreter for this? I think it's kind of helpful to think about how we interpret this as human beings. The first thing I want to observe is that we don't just see this as a kind of big amorphous blob of code. We break it down in our minds into some structure, and we say, okay, these different lines are different expressions in our language, and they're not the same kinds of expressions. We can say that the top line is probably an assignment, and the second line is just using a variable. When we look at this assignment line, well, the reason we think it's an assignment is probably because it's got equals in the middle of it. And once we recognise it's being an assignment, we can say the thing on the left-hand side is some kind of identifier. It's telling us where to store the value, and it's not just any value. We can look at this and say this is clearly a number. So, that's maybe how we see it, but for a computer it looks entirely different. It just looks like an undifferentiated sequence of characters, like an A in a space and equals and so on. So, the first challenge is to take this and impose the structure back on it. And the way we do this is by writing a parser. So, we're going to take this string of characters, feed it into a parser, and it's going to spit out some kind of structured data. So, how do we write a parser? Well, this is Ruby, so of course there's a gem for it. In fact, there are many gems for it. The one I'm going to use today is called Parslet. I find it quite easy to work with. And I'm almost ready to get started, but before we start I need to work out a name for this. Just for practical reasons, I need to name some classes. So, I'm going to give a name to my language. And what do we call this? Well, maybe we can pick a name based on the characteristics of our language. So, like it's simple. I thought it was simple, it's like really, really obscenely simple. And this makes it kind of accessible at the start, but after a while we realise it's actually quite limiting. And not only is it limiting, the more we work with it, the more frustrating it becomes. So, I'm going to name it something that's like obscenely simple and really very limited and just becomes more and more frustrating. Oh, sorry. That shouldn't be in there. So, for no particular reason, I'm going to call this language Trump. Okay. So, let's start writing a parser. To do this, I'm just going to write a class and I'm going to call it Trump parser. And it's going to inherit from Parslet parser. And the way that the Parslet parser works, oops. Is you give it a set of rules that describe how to match the input string. Okay. And we're going to start by telling it where to begin. You give it something called a root. And the root that we identified when we first looked at the programme was that it was a series of expressions. So, we just say to start parsing this programme, look for expressions. Okay. Now we need to tell it what we mean by expressions. So, we'll write a rule for that. We'll have a rule that says expressions or a single expression. But we're going to repeat this. So, it could be more than one of these things. Okay. This is how we tell Parslet that this thing repeats at least one time. So, now we need to tell it what we mean by expressions. So, again, we just write another rule. We say an expression. Now, there are different types of expressions in our language, but we're going to start by just assuming it's always going to be a number. This helps us get started and we can add in the structure later. So, I'm going to say an expression is just a number. And finally, we'll tell it what we mean by number. Okay. So, a number is one or more digits. So, we can tell it to match a digit by saying match and then passing back slash d, which will match 0 to 9. Let me say there may be more than one of these. So, we'll say repeat, and there's going to be at least one. Okay. So, now what we can do is we can create like a sample program, a source string for a program. We'll just set it to be a number for now. And then we can create an instance of a or a parser class. We'll just call new and that. Then we can tell it to parse the input string. And then we look at what this... Oops. If you look at what this produces, we can see it gives the string back to us followed by this at 0. What this is actually telling me is that our parser will accept the input program. It thinks it's a valid program. Now, if we make it invalid, so let's say we add some letters into here that we haven't told it how to match and then we run this again, it throws an error and it says, I don't understand how to parse this. It rejects our program. We put it back to being a number and run it. OK, we don't want to match just one number and we want to match multiple expressions. So in theory, we should be able to do this. But when we run this, it explodes and it says there's something I don't recognize at character 4 which is a space character. So we need to be really, really precise with our parser which is very annoying when you're live coding. So what I need to do is tell it how to handle space. So I'm going to create a couple of rules for this. I'll say a space, we just want to match and then we can use backslash s which is any kind of space character. And there may be many of these. So I'm going to run this once. And then we'll add another rule which is called space question mark which says it's a space or it might not be. The reason we use this is because our first number is followed by space but our second number is not followed by space and we want a rule that matches both of those. What we then do is we say when we have a number, it may be followed by a space. OK, so the double chevrons just say followed by. If we run this again, we can see it now gives our numbers back to us. Now, this isn't very good. We don't just want a string back. What we want to do is apply some structure back onto our input code. So what we can do in parsley is we can say I'm interested in this thing, this number, and then we just give it a tag. We say match this as a number. And now when I run it, the output is very different. It's now an array of hashes and these hashes say here's a number that you told me to match and here is the bit of the input string that I identified when I matched this. OK, so that's how we match numbers. We can now bulk out our expression because we have more than just numbers in our language. We can say it could also be an assignment or it could be just a single variable. And now we need to write rules for these. OK, so the rule for an assignment is we have some kind of identifier and this is followed by an equal sign and then that's followed by some kind of value. So now we need to write the rules for all these things. So identifier we'll start with. So identifier, I've decided identifiers are just lowercase sequences of letters so we can match a to z, lowercase a to z. OK, this repeats at least one times and it's maybe followed by space as well. Equals is even simpler. So we write a rule for equals and this is just literally the character equals. OK, so straw here just means literally this character, we have fancy rejects matching and again this might have a space after it. Everything is going to have a space after it. Now value is interesting. The things that we can assign to any kind of identifier is not just a number. It could also be another variable or it could also be another assignment. So really what we're saying here is any kind of expression is valid as being the value. Then we'll just tag these things. So we'll say we're going to match this as a value. We're going to match the identifier as a target. OK. Let's get to it along now. And then we're going to wrap this whole up. I'm going to say we've matched a whole thing as an assign. OK, and then we have another rule that we need to write for variable. Now this is really easy. A variable is just an identifier. So we just match the identifier and we match it as being a variable. We just tag it. Now another couple of things I need to do. So I want to change my source string to be more realistic. I'm going to type A equals 6 and then A. Now you see we've got a new line at the end of here and there's a new line at the very end of our program so I need to tell it how to cope with these things. So we'll say it may start with a space and our expressions may also end with a space. OK. If I've typed this right first time live on stage which would be amazing. Oh, I did. OK, it works and it gives us back a structured representation of our input string. Whole day applause, this may still be broken later. OK, so what we've done so far is we've taken our source of just characters and we've turned it into these tree-like structures. We've imposed some structure back on top of it but I don't really want to work with arrays of hashes. That's not a good way of working in Ruby. So I'm going to take it a step further and turn these things into objects. To do that, parts that provide something called a transform and it's a similar principle instead of matching against our characters we're going to match against our tree and we're going to turn those into something more useful. OK, new class and it's going to be called transform and this is going to inherit from parcel transform. Again, we're just going to build up some rules on here. The output tree from our parser step looks a little bit like this. It's not exactly this but it's pretty close to this. If I create an instance of my transform and then I apply it to that tree what this does is it gives me back an unmodified version of the tree. OK, so it doesn't have any rule to tell it how to transform any of this so it just leaves it untouched. I'm going to start with variable because it's a very simple structure inside this tree. So the rule for this is we just say when we match the key variable followed by some kind of value it's just a simple value here just a little bit of text and I'm going to call that identifier and then we give it a body because there's a replacement for where they've been matched so I can just get it to say, for example, hello. If I go down and run my code again we can see that the variable has gone from the end and it's replaced it with the stuff in the blocks here. Now I don't want to really return a string saying hello I want to return a Ruby object that we can use so I'm just going to require a file where I've defined some basic objects and all that's in here at the minute is the initialiser to take an argument and then some kind of inspect so we can see what's going on. Now I'm going to use it in here so instead of returning a string a new instance of my variable class I'm going to pass on the identifier to that and then we can write something very similar for the other parts of our input string so we can write it for matching numbers and for matching assignments. So if I go down here and run this we can see that it's now turning into Ruby objects when it says assign, call on and then number call and so on this is showing me that it's matching my Ruby objects so we can now go from a source input string to Ruby objects and we're kind of in this situation now and these objects represent single expressions inside my program. Okay, so now that we can match stuff I can start to write the interpreter proper and what I'm going to do is I'm going to take each expression so we'll take for example this assign and I'm going to call eval on it and what I want eval to do is basically run this code for me and really what I mean is I want it to turn it into the simplest possible form it can. Now the simplest possible form from an assignment is going to be the simplest possible form of its value and the value here is a number so then we need to go again and say okay number what is your simplest possible form and for that I'm just going to return the Ruby integer so in this case calling eval on assign is just going to return the value of 6 now because it's an assignment we also need to do some side effect as well so we need to go to our environment at this point and say please remember that the identifier has got the value of 6 and that's it, that's what eval means when we call an assignment now if we're calling it on a variable we're going to go to the environment and say hey give me whatever the value is for the identifier A and that of course is also 6 so I'm going to go and pull this all together now I've written a class for the environment it's just really a wrapper around a hash that lets me get and set stuff using a nicer API but I've got all the parts in place that I can start writing my interpreter to do that I'm going to write a method called nodes and it's going to take some source string what I'm going to do is I'm going to take that and pass it to the parser and then we call pars and that passing in the string and then I'm going to take the output from that which is going to be some kind of tree I'm going to pass it straight into the transform and then that's going to give me the Ruby objects back so when I say nodes a single node is really just an expression in my program I'm just going to test that works because if that doesn't work we're going to be in trouble later so our program looked a bit like this and we just call nodes with the source and then check it excellent that's never happened before so we now get the Ruby objects back out again which is what we wanted so now we have our nodes we can write a method called interpret oops we can't because we can't spell and this is going to take the source thing again now when I said we were going to write interpreter by calling eval one of the things we had to deal with the side effecting and putting stuff into the environment so I'm going to create something called end which is a new instance of the environment in the program so we're going to put all our global variables then I'm simply going to take the nodes by calling this nodes method and I'm going to say I'm going to map these into take each node and I'm just going to call eval on them so I'm going to take node.eval and I'm going to pass in the environment at that point and then these eval methods will modify the environment as they go along so now we can replace this call to nodes with a call to interpret but this isn't going to work yet it says I've got to a sign and I've tried to call eval but I can't call this yet so what I'm going to do is go and write those eval methods and these are all very simple okay so to evaluate a variable it gets passed in the environment and I said we'd simply go and look it up in the environment so we just say m.get and then we pass and identify for a number again we'll write eval it gets passed in the environment but it doesn't use it we'll just chuck it away so it's a numerical value okay so all we do here is write value and then finally to evaluate an assignment it gets passed in the environment now I said to evaluate an assignment we go to its value and we evaluate that passing in the environment at this point okay then we take that and we set it in the environment so we say m.set then we'll use the identifier as the key and then we'll set the value so we go back here and run this we can see it now gives us a new array with each value set to six so when the value is the first expression it says the simplest form of this is just the value six and then if it values the second expression it then goes and looks up in the environment and says okay this is also a six so that's our interpreter and that's great but we're not really here to write an interpreter we're here to write a type checker so the next step to do is to write the type checking algorithm now this works just like eval so instead of calling eval on our on our nodes we're going to call type instead when we call type it works in the same way the assignment will go to its value and say hey value what is your type in this case it's a number now a number is going to be like a primitive type in this language so we can just give it a name I'm going to say the type of number is number type so the type of assignment is also number type and again because of side effects we're going to go and update something called a context now the context is just like the environment I mean it stores types so when I talk about context I'm always talking about types when I'm talking about environment I'm always talking about variables I'm sorry values okay so that's how we work out the type for assignment to work out the type for the variable it's the same process we just go back up to the context and say hey what's the type that's been set for the identifier A okay so we can go and write these things so in here we can say a new method called type it gets past the context these are going to look very similar to the eval so instead of saying m.get we say context.get and we just pass in the identifier okay for number we call type it gets past the context doesn't use it this is a primitive type so we say number type.new okay I need to go and write number type because I don't have that yet so we'll just write a quick class called number type the only thing inside here I'll just add an aspect method so we can see what's going on it'll just print out the number like that okay so that's the type for number the type for an assignment is going to be very similar to the eval for an assignment it gets past the context okay we go to our value we say what is your type passing in the context and then we take that and we set it in the context so it gets remembered for later pass in the identifier is the key and it's like that okay so if we come back to here we can then write our type check method in fact it's very very similar to the interpret method it's so similar I'm going to just copy and paste it always a good programming practice okay so we could call this type check now instead of creating an environment I'm going to create a context so we can remember that it stores types we'd still map over the nodes that it seems before but instead of calling eval we call type and instead of passing an m we pass in context okay and then down here I can change the call to interpret to call to type check run the code and you can see it says number for both these expressions so that's great what we've just written is something which is a type analyser for a very simple language and it does this without us having to add any type information to our source code it goes and works it out just by looking at what the source code is and this is okay but it's a little bit boring because we only have one type in our language so it's always going to say number so what I'm going to do is make this a little bit more interesting I'm going to add some more contracts to our language particularly I'm going to add functions because functions are pretty useful so to write a function I'm going to use the keyword fn it's going to be followed by a parameter list so say like x and y then it's going to have a body inside these braces and in this case I'll just return the first argument okay so that creates a function then we're going to assign it to a variable like we do for a normal value so we'll say like first is equal to a function which returns the first argument and then once we've created a function like this we can then call it by taking the identifier following it by a bracket and then passing in some arguments so we can pass in an example a and 2 now when we run this we would expect it to return the first argument so we expect to return a which has got the value 6 so I'll write an old code for functions here but I can go through quickly how they work and then show you in practice so evaluate a function I'm going to go through the parser step and then go through the transform step and it's going to spit out two objects two new types of objects either a function or a call like a function call when we evaluate a function it just returns itself okay it doesn't actually do anything at that point it just returns itself the reason we return self is because then when we assign it the assignment will have the type or the value of that function and it's that function that gets stored in the environment and that means when we do a function call we can then go to the environment and look it up by the identifier in this case first it'll return that function object and we just pass the call back to the function when we call a function we call it with two different types of arguments we call it with our list of arguments that we want to use for a function call and we pass in the environment at that point and the way that function violation works is actually really really simple we call it with our outer environment at that point but then we set up some special values we set x to be the first argument that's passed in in this case six and then y to be the second argument passed in in this case two then we go to our function body and we just call eval on that on each expression inside there but instead of calling eval with our outer environment we call it with this new inner environment and then that's it that's how you write function evaluation okay so I've written this in a file called basic functions I hope that's a name okay and we go down to here and then I can take our source code with a function in it and run it through the interpreter you can see that the first thing it's still a equals six it still goes back to value six the second thing is a function declaration which gives us back one of these function objects and the final thing is a function call and it values this correctly it works out that the first of a and two is in fact a which has got the value six okay so just so it works we pass in a different value last thing in our output stream here okay so that's how we write a function let's look at the type checker for this now the type checker works in a very similar way I'm not going to go through the code again but instead of calling eval on game back a value we just always return the type of that value so if I run the type checker it says we've got a number followed by a function type followed by a number type having to add any type annotations to the code I'm going to write a more complex function I'm going to write something called apply and this takes three arguments it takes an x and a y and a z and what it does is it assumes that x is a function so I'm going to say we're going to call this first thing that you pass in as though they're a function passing in the arguments y and z and then we can replace our call to first with a call to apply and the first just becomes the first argument now if I go back and run this to the interpreter you can see that this still works it's getting a bit long now so let's require a pb and then pre-print this we see it still works so it's still a value it's the first thing to be a 6 then we've got two function declarations for first and then apply and then when we call apply with this complex of arguments it still just works, it still goes back to the answer we would expect then we run this type checker it says that we have something of type number and then two things of type function and another thing of type number now I'm not really happy with that, it's correct but there's more information that we can get out of this when we say that things are the same type we typically mean that we can interchange them in the program and the program will still work you get different values out perhaps but the program should still work but that's not the case for functions the way the actual type of first is not the same as the actual type for apply they are both functions but first is a function that accepts two arguments is a particular return type and apply is something that takes three arguments and one of those arguments has to be another function and it's got a very different return type so I'm not really happy with the way this works I think we can do better so this is our function and we can say what do we think the type of this is now when we declare a function we don't know what the type of X is we don't know what type of Y is we're not going to know these things until run time but we do know there are two of them and we know that they can have any type at all then it returns something and we don't know what it returns yet we won't know until run time but we can say it returns something of any type and that's kind of true but it's not the full story because the thing that is passed in as the first parameter X is also the thing that gets returned so the any type for the parameters also the any type that has to be used for return so instead of just saying any type I'm going to use type variables I'm going to say the type of X is A we don't know what A is yet it's just a variable that will be filled in a letter with a value and it's the same thing and that's the important point and for apply we can go even further if we start off the same way we say we use type variables for the three different arguments that passed in and then when we look at return type well the return type is whatever A applied to B and C is the trouble is that we've said that A is any type and applying any type doesn't make any sense so we get an error at this point it doesn't know what the return type is but we can work at more information by looking at the definition of the function body so we look at where these variables are used we can say that A isn't any type that means we don't yet know what it's going to be but it's not any type really it's constrained to be a function in particular a function that takes two arguments of types B and C now because we know it's a function we then know it's going to return something and we don't know what it's going to return yet because we don't know what the type is for sure but we do know it's going to have some return type so we can use a new type variable D in this case as the return type and we can go back and we can say the full type of this function is something that takes another function of type B and C mapped to D and then two more arguments of type B and C and then return something of type D now don't worry about the detail here I know this looks really confusing the most important thing to notice from this is we could find a lot of information about the type of this function just by looking at the source code we haven't had to tell it anything we haven't had to tell it what the types are so I've written this up and again it's in a file called better functions this time so I can go down here and just to make sure it still works I can call my interpreter but it still gives me back the correct answers but this time when I ask it to do a type check it gives me back a lot more information instead of just saying it's any old function this time it's saying it's a function which takes something of type A and then returns something of type A again and for the apply it gives me this detailed thing about the type of function it's something that takes another function and then a couple of arguments returns return type of the first parameter there's quite a lot of information we can extract from this this can be quite useful as well if we make a mistake in our code for example if I call apply and pass in A and A in this case is the number it's not a function it's going to raise an error but it can only tell us that it's wrong it can tell us or it can give some hints about why it's wrong and what it expects instead so it's saying you gave me a number but I expected a function and in particular I expected a function of type B and C map to D so this is kind of useful and we can do all of this without having to add any type information into our source code we can do it all just by looking and reconstructing the types okay one final thing I want to add to our language I want to add something called maybe the way maybe works is you can add it before any expression and the behaviour of maybe is it will either run the expression or it won't now this seems a little bit weird but I'm using it to simulate a lot of the uncertainty we get at runtime in real programming languages and what the implementation maybe has got a random number generator in it and half of the time it will just run the function and return whatever the value is from that and the other half of the time it will just kind of mess with you as a developer so I'm going to go and pull in definition of this it's in a file called maybe okay if I go down and run the interpreter in this sorry I'm going to set it back to first as well okay I don't know at this point whether or not it's going to work it's up to the random number generator it may work and just give me back the answers that we saw before or it might explode so first time running it it worked okay so it picked the random number that was high enough and it said run the expression if I run it again it worked this is the great thing about random things if I run it again if I run it again yay it broke that time so we can see in this case instead of returning the thing it just returned ha ha and the whole thing blew up so what about types for this if I run the type checker what can we say the type of for example first is because it's got this maybe in place we don't know what's going to happen and what's the type of applying something with a maybe so think about this what's the type of A well it depends on what happens at run time now if at run time we decide to run the expression then the type of A is going to be something of function type there's a bit more detail but if maybe it decides not to run the function then it's going to be this funny method of developer type called null here for no particular reason so what's the type of A well actually the type of A is either null or function type but we don't quite know it's like a kind of Schrödinger's type we don't know until we look in the box so when I run a type checker this is exactly what's going to come back now we have a bit of a problem because if the type is this special new type of either a null or the function can we say that this works because remember we had something that checked the argument past it was the right type and the answer is we don't know we don't know whether it's going to be the right type how to deal with that now in a strict language you might reject it and say we cannot guarantee that your program works therefore we're going to reject the whole thing and we're just going to stop and say go away and fix this in a more permissive language something like Ruby for example we're more likely to perhaps warn you about it we'll say hey it's a bit of a danger here you know this thing might not work but I trust you as a developer to get it working you know what you're doing okay so if I run the type checker now first of all we can see this warning it's saying you're passing in this union type this or type the thing that's either a function or it's this kind of null thing and this might not work there's no guarantee that your program is valid but then we look at it it carries on and does the full type check actually the final return type it doesn't quite know what's going to happen either it says the whole thing is either going to produce a number or it's just going to explode we don't really know it's not always going to be this ambiguous though so for example if I say A is maybe a 6 on the type checker okay you can see the type of our first expression is now set to either number or this null type we know that neither of those work for a function so if I then pass in our maybe type here as the first argument to apply it knows enough to reject it it knows enough to say this can never work it doesn't matter which path we go down it's never going to work so there are things we can reject but we can't reject everything and this is a big idea non determinism in programs is not our friend when writing a type checker if we don't know what's going to happen our type checker is not going to work very well and in particular it changes the nature of our type checker I mean initially when we wrote type check and we said is this program valid it can come back and say yes it's valid or no it's not valid when we throw non determinism into the mix and we say is it valid it can come back and say it's not valid it'll definitely never work or else it can say you go and run it and find out and that's not very useful and there's a risk that as the non determinism increases the value of the type checker decreases because it's saying I don't know more of the time I think one of the important questions asked is how much Ruby is non deterministic I don't know the answer to that I think a lot of it is but until we actually run this against real programs we're not going to know for certain okay so this was supposed to be a talk about type checkers but I spend an awful lot of time writing an interpreter and talking about how evaluation works and that's no accident in order to understand types we need to understand how our interpreter works we need to build an interpreter and this is part of a deeper truth that understanding types is not separate from understanding the syntax and semantics of your program they're not two separate things they're just different views on the same concept and there is this idea thing that we can take Ruby as it is and add soft typing to it without addressing the syntax and semantics without changing the nature of Ruby and I think that idea is optimistic at best I'm not convinced it's going to work but I don't want to end here I don't want to end on a negative note and I really don't know the answers to whether or not soft typing would be good for Ruby without changing the syntax and semantics I certainly don't want to stop here because there's something more important I've been trying to get across today and this is the type theory in Ruby go together type theory is not the kind of thing that we say we don't have to use this we don't have to be interested in this because we don't write Java with static type checking or we don't play with Haskell like the other weirdos type theory is something that we can engage with as Rubyists and it's something that can potentially give us some benefit back it's something that can improve our confidence that our programs are correct or will behave in the way that we expect I think that's the most important thing I've tried to talk about today so is type theory boring and difficult and impractical well, that's not for me to answer that's the question that you guys answer and I'm not stupid here, I know that some of you think yes to all of these and that's fine, I mean type theory is not for everybody but I hope at least some of you here have found it interesting and I find that it's more accessible than you maybe previously thought and maybe think that there is some use for this in Ruby and if even one person here today goes away and thinks I'm going to explore this some more then I can't just talk as a success so I've written up all of the code that I've used today, it's available on github under this URL but that's it, thank you very much for listening okay that went way quick and expected because the code worked first time, this has never happened before I do this from time for questions I'm generally not a big fan of questions at the end but I am a wrong for the next three days so if you've got anything to say to me please come up and discuss this with me even if it's just to say you find it very boring and annoying but has anybody got any questions they think they want to share with everybody else or any observations that we've been worth sharing with everybody oh there's a hand up over there okay so the question was what they think about the gem contracts so the gem contracts is an attempt to add some kind of structural typing on to Ruby you kind of say this is the type of my function it's like a way of adding type annotations where you are describing to Ruby the types without actually changing the Ruby parser and the interpreter is that a good summary of contracts? okay cool so what I think about it I think any tool you can run statically that gives you some more information about Ruby and types of Ruby and the safety of Ruby is worth exploring personally I don't think the contract is the right way to go I think type reconstruction to demonstrate here today is probably a lot more interesting where you go to your code and we try and pull out as much information as possible but I think that contracts is an interesting attempt to add type signatures and to get people thinking about types in Ruby but unless we have a good way of running a tool and Nick coming back and saying yes program is valid or no it's not and being able to trust that tool I'm a little bit dubious as to the value okay so the question or the observation was that nil is very important in Ruby it's like a first class citizen it comes up a lot and this is going to be a stumbling block to adding something like static type checking yep yes you guess okay so yeah you're an idiot nil is not a stumbling block in itself nil in itself is not a problem if we can correctly identify nil then everything is fine okay we can handle nil properly and we can just say nil will never satisfy the constraints that we need for our particular piece of program okay nils do exist in other languages you know something like ML for example has got something it's not undefined it's another thing but you know in something like ML which is strongly typed they do have concepts like nil inside there a unit for example can act in a null like way so I don't think that's the problem with type checking in Ruby I don't think just having a nil is a problem I think the bigger problem is we don't know when there are going to be nils until we run the code so when you access an array for example you don't know whether it's going to give you back a number or a hash or another array or some object or a null we don't have access to that at the static analysis phase and I think that's the big problem the non-determinism is the problem the problem is not the fact that one of those types may be a null that makes sense any more yep over there what plug-in is it using to evaluate the code the plug-in is called seeing as believing by Josh Cheek who's around somewhere I can't see him oh there he is standing up at the back I really recommend you check this out it'll plug into pretty much any editor and it's a really good way of doing this kind of quick evaluations you're writing code yeah definitely go and check that out seeing as believing I'll tweet it afterwards as well okay so the question was is there a way we can write a code to avoid non-determinism the answer is yes stop using Ruby go and use Haskell maybe not the answer you're looking for I think that non-determinism is something that we like as Ruby developers part of the attraction of Ruby is that we don't have to be very specific at the outset about what's going to happen in our program and it allows a whole class of programs that would not be possible otherwise every language will have non-determinism in it somewhere any language that's got IO for example you cannot guarantee at compile time what the IO is going to be there are probably ways we could reduce non-determinism or even guard it so we can say this bit is non-deterministic but I think if the program works properly it will return this type you could do something like that I think that's very complex this is an idea I'm kind of interesting exploring over the next year or two the idea of how do we kind of marry this non-deterministic language with trying to get guarantees about strong typing or static typing but I don't yet know what the answer is going to be to that any more? okay more hands up, okay go right hand side first so the question is what I think about gradual type systems like type script so gradual typing systems are perhaps a little bit different in intent in gradual typing systems as far as I understand it you add type annotations as you go so you're saying to the compiler this is the type of this thing and the compiler then does a check and then there's something called type eraser it says okay I've checked it, I'm happy that it's valid and it throws it away and at the run time it never uses that information again I think that it's probably not going to catch on I think if you are prepared to write the annotations you're probably going to be happy enough to move to a strongly typed language there's maybe something like Elm instead that compiles onto JavaScript rather than using TypeScript it's seen as a stock gap movement I've never really seen it used that successfully but I'm sure people do but I've never seen it used that successfully because if you're only kind of half thinking about types you don't get the consistency you don't get the strong guarantees but you do have all the extra effort of having to go along and annotate your code and deal with situations where you don't really know what the type is going to be or the type is very complex so the question was could the approach I was using be extended to Erlang Dialyder okay you could do some kind of type reconstruction error line it would probably be more successful than it would be in Ruby Erlang is interesting actually because they use what's the name of the approach property typing to give some guarantees so the tools use property checking to give some guarantees about safety rather than using static types and property testing is kind of it'll find a value and then try and break it it'll try all sorts of different things in there and see what breaks it that's surprisingly successful I think tools like that are probably going to be successful enough that doing some type reconstruction is not worth the effort it wouldn't give you any better guarantees okay we're out of time exactly zero minutes so thank you very much everybody