Listen in on Jane Street’s Ron Minsky as he has conversations with engineers working on everything from clock synchronization to reliable multicast, build systems to reconfigurable hardware. Get a peek at how Jane Street approaches problems, and how those ideas relate to tech more broadly.
Richard Eisenberg is one of the core maintainers of Haskell. He recently joined Jane Street’s Tools and Compilers team, where he hacks on the OCaml compiler. He and Ron discuss the powerful language feature that got him into PL design in the first place—dependent types—and its role in a world where AIs can (somewhat) competently write your code for you. They also discuss the differences between Haskell and OCaml; the perils of trying to make a language that works for everybody; and how best a company like Jane Street can collaborate with the open source community.
Richard Eisenberg is one of the core maintainers of Haskell. He recently joined Jane Street’s Tools and Compilers team, where he hacks on the OCaml compiler. He and Ron discuss the powerful language feature that got him into PL design in the first place—dependent types—and its role in a world where AIs can (somewhat) competently write your code for you. They also discuss the differences between Haskell and OCaml; the perils of trying to make a language that works for everybody; and how best a company like Jane Street can collaborate with the open source community.
Some links to topics that came up in the discussion:
Welcome to Signals and Threads, in-depth conversations about every layer of the tech stack, from Jane Street. I’m Ron Minsky. It is my great pleasure to introduce Richard Eisenberg. Richard’s a senior contributor to the Haskell ecosystem. He’s worked on Haskell’s type system in a lot of different ways over the last eight years. He’s on the GHC Steering Committee. He’s the Chair of the Haskell Foundation. One might wonder, what’s he doing here? Well, it turns out Richard has joined Jane Street. He’s working on our Tools and Compilers team, and in particular, working on the team that works on the front end of the OCaml compiler. So, thanks for joining me, Richard.
A real pleasure to be here.
So, maybe a good place to start out with is to talk about your background. You’ve done all this exciting work in the Haskell ecosystem. Maybe tell us a little bit more about how you got involved in Haskell and what kind of work you’ve done there.
Yeah, sure. I first really encountered Haskell when I went back to graduate school. I’d been a high school teacher for a little while and then I thought, ‘oh, you know, it’s time to get a Ph.D. in programming languages,’ kind of, as you do.
In the process toward that, I discovered dependent types. I didn’t wanna just show up in grad school and not know anything. So I did some background reading and I discovered that, you know, these fancy type systems have so much expressiveness, and so much of a way of being able to sort of define what it is that you’re programming before you even write a line of code, that you can just express your ideas so much more clearly and then get the compiler to check that you actually did it correctly. This was just sort of, you know, my brain exploded when I learned about this.
I feel like, you know, I kind of pretend to know something about programming languages on TV, but like, actually my academic background is distributed systems, not programming languages, and there’s lots of gaps in my knowledge. And so, I kinda don’t really know what dependent types are. Can you give a suitably beginner-friendly explanation of what dependent types are?
Sure. And I’m gonna start by avoiding the trap of trying to define the term, ‘dependent type.’
And instead, just sort of describe ‘a language with dependent types allows you to do this thing.’ And that thing is to essentially encode a proof of correctness into your program such that every time you compile your program, that proof is checked. And so you know—if you’ve set things up the right way—that your program does what you think it does. And so, I’ll give a concrete example.
And that is, to imagine a sorting function. So normally we think of a sorting function, let’s just say, it’s a function from a list of ‘ints’ to a list of ints, right? It takes this list of ints’ input, and then does some transformation, and then produces a list. But actually, that’s a very loose description of a sorting function. We can do better than that.
I wanna say that a sorting function takes in a list of ints, and it produces a list of ints in, I’m gonna say, non-descending order (because maybe there’s duplicates, so I don’t want to say ‘ascending order’ because someone out there might say that’s wrong). So we get this list out that’s in non-descending order. Okay, that’s closer, but that’s still not quite right because maybe my function just always returned to the empty list. That’s a list in non-descending order.
So instead, I really want to say that it’s a function that takes in a list of ints, and returns a result that is in non-descending order, and is a permutation of the input list. And that’s a rather glorious type that I can assign to a sorting function. But it accurately describes what that sorting function does.
And then I can implement that in any number of ways, using any of the common sorting algorithms that you learn in computer science, and any of those different algorithms will meet that specification, and then, if I’ve encoded this, my compiler will check that my algorithm actually does sorting and doesn’t do some other thing. And so now, having sort of explained that, I can return back and actually say what I mean by ‘dependent type.’
A dependent type is a type that can depend on some input that you give into a function, essentially. So, in this case, the output of the function, not just a list of ints, but the output depends on the input. The output must be a permutation of the input. And so, that’s the dependent type.
So when I started grad school, then I saw Haskell, and here was a language that had this fancy type system and had all this expressiveness. Didn’t quite have dependent types, but it had a lot of that stuff. That just got me excited. And then, through that I started working with Stephanie Weirich at University of Pennsylvania. She was doing a lot of Haskell work. She collaborated a bunch with Simon Peyton Jones, who then I started collaborating with, and things just sort of took off from there. It was a pretty fast introduction into that world.
I wanna go a little deeper on that. I feel like we’ve started with the, like, ‘Well, of course I got a Ph.D. in PL and was interested in dependent types.’ Like, how do you even get there? Where does your interest and background in programming languages come [from] in the first place?
So, once upon a time, I wanted to program. I don’t know why I wanted to program. I was too little to have that level of introspection.
So I guess, uh, I learned Basic when I was, I think, five somehow. There was a friend of my parents that decided I should learn Basic and so that happened.
But I then got more into it, I guess, when I was in 7th grade, so this was, probably I was 12 years old. And someone told me that Pascal was a good language to learn. So, you know, I pleaded with my parents and eventually they bought me the Pascal compiler ‘cause back in those days, you actually had to pay for compilers. At least on, sort of—we had a Macintosh. If I had some kind of Unix system, maybe it would’ve been different.
Well, which Pascal compiler did you use?
Um, THINK Pascal? Was that a thing?
THINK Pascal, so not Borland. I guess Borland was more in the, like, DOS side of the world.
Yeah, yeah. Borland, yes, Borland was for like, ‘real programmers.’
So I got this, and somewhere along the way, I had decided that every piece of software should be, like, if you read the instruction manual, that should tell you everything you need to know about that piece of software. I don’t know why I assumed that, but that was my starting point. And so, it came with these two big books and most of the books were just like on compiler flags and setting up projects and none of this stuff about programming. At the end of one of the books was a syntax reference with no glosses, no further information, just a syntax reference using those, sort of, block and line diagrams to represent recursive loops in the syntax. And I was like, ‘Okay, well, if my assumption is that this book defines how to program, and this is the only thing it has, then all the information must be here.’
So I just stared at these syntax diagrams probably for hours and hours. Like, ‘Okay, what does all of this mean?’ And then eventually sort of bootstrapped my way into learning how to program Pascal. And so, that being my first ‘real programming experience,’ somehow that built the structures in my brain just around syntax and languages, and it kind of went from there.
And then fast-forward 10 years or so, and I took a PL course as an undergrad from Norman Ramsey and it just lit me up. It was just like, this is really cool stuff. I really enjoy this kind of thinking. And it turned out, I took that at the end of my senior year of undergrad, and so it was sort of too late to immediately go into grad school at that point and I had this whole machine going toward high school teaching. So I did that for a while, but then I ended up returning.
I love that your entrée into programming was, roughly speaking, a traumatic experience with syntax. That seems… (laughs)
But it wasn’t traumatic. It was fantastic. Somehow, this was just what I needed. This was what my 12-year-old-self needed: big syntax blocks.
(laughs) Okay. So, you went off, you got your Ph.D., you started working on Haskell. Where did it go from there?
Well, I was working on my Ph.D. It was basically, Stephanie and I had this idea about how to encode dependent types, but it required some more support from the Haskell compiler. And so, I was pleading [with] the Haskell people to create this for me. And then, eventually Simon just wrote and said, ‘Why don’t you just do it?’ And that was a new concept for me. I was like, ‘I could influence this language? Well, you know, I’m just this random person.’
But between Stephanie and Simon mentoring me, I got to a point where I could contribute and then it just grew from there. So, [I] did a bunch of that as a Ph.D. student. Then after leaving my Ph.D., I was a professor for a few years at Bryn Mawr College. What was great about that experience was, they were open to the idea that open source contributions were a form of publication, right? A lot of professors have a lot of pressure to keep producing papers. That was definitely there for me, too. But alongside papers, I couldn’t just do software. But I could do, sort of, traditional scholarly publications and software. So that gave me a chance to continue my implementation work, remain a part of this open source community, while still doing the high-level scholarly research that I had been doing. So that was a really nice part of it.
It turned out that, to my great surprise, that—well, this wasn’t to my great surprise—it turned out that this was a lot of work. That maintaining a research program with a bunch of papers every year, and doing these open source contributions, and teaching, there was just more than could comfortably fit into one job, especially during term time. And to my own surprise, I realized that the teaching part was the part that maybe I could pause on. I had done that full-time as a high school teacher for eight years, now I was back doing it. It was fun, but I thought it was time to sort of push in a slightly different direction.
So after a couple years at Bryn Mawr, I left to join Tweed which is a software consultancy based in Paris. They do a ton of Haskell work. And they hired me, basically just to continue my contributions in the Haskell space. So, I continued doing research there, continued contributing to the compiler there. And, you know, after a few years of that, that was fun, and I got to a point where I was looking to be part of a cohesive team all working together, and realizing that after so many years just working in one language, I was really ready to see what things were like in a different space. And so the timing worked out really well when you, Ron, reached out to me and started the discussions that led to me joining Jane Street.
Right. And often in these interviews, I’ll ask someone, like, ‘so how did you get here?’ But actually, I was part of the dramatis personae of how you actually arrived.
You were the—the dramatis personae, yeah. (laughs)
(laughs) Right, which is, at some point, I was like, ‘oh, it would be really good if Richard worked here,’ and I started reaching out and trying to convince you to come. Which took, it’s important, I calculated, 11 months from (laughs) when I started.
Yeah, yeah. That’s right. I mean, you know, of course the story [of] me coming to Jane Street starts with you asking me, and me saying ‘No.’
And again, it was a matter of timing. I’ve been aware of Jane Street for a long time, been sort of wondering what was happening over there, and somehow when you first reached out, it just wasn’t the right time for me yet. And then the second time, it was sort of a better time. I couldn’t even tell you why. It’s just, this is the way that life flows.
But, you know, one of the barriers which we’ve joked about is that I grew up in the New York area and actually ended up going to high school in New York and commuting from New Jersey. So every day I was going back and forth across the George Washington Bridge, and swore to myself then that I would never commute to and from New York City. And also just sort of had this negative vibe from the financial industry, and I was just like, ‘I don’t know. Like, I want to be creating something that other people use,’ right?
But actually, this is fantastic. I’m having great fun. Part of it is that I’m working as part of this larger ecosystem. It’s not just a closed thing where I’m only doing stuff that’s internal to Jane Street. But instead, contributing to this open source ecosystem, working on an open language, OCaml, and really continuing some of these research trends that I started 10 years ago. Now that I’m here, it just all fits very naturally together.
I feel like, in general, one of my takeaways is that there’s lots of interesting work in lots of different places and the preconceptions you have when you are young often (laughs) don’t work out over time. So a thing you talked about earlier is a topic that I feel like I should know something about, but don’t. I like your first high-level description ‘cause it actually lines up with the very little bit I know about dependent types.
And there’s also something that’s always made me a little nervous about them because a basic fact about proofs is—proofs seem pretty hard to construct. People go to great trouble to build automated proving systems where you have tactic libraries, like, little tiny programs that you can run to, like, ‘try this way or try that way’ of coming up with a proof of a thing. And it all sounds really hard and when you talk to people who do kind of theorem proving on large scale software systems, it sounds like a pretty challenging operation where this is kind of a lot of engineering work that just goes into just constructing the proof. So I’m curious how you think of the practicalities of that? Like, how convenient can you make it? And if you make it a deeply embedded part of the programming process, does that work well with the other things you might wanna do? Does it interfere with the ordinary process of programming? How do you see the pragmatics of that working out?
Yeah, that’s a great question. And this is a question that doesn’t have an answer yet. This is, I think, still something that the programming languages community is grappling with. I have my own takes, but this is still sort of an open question out there.
I think, first of all, that for a programming language to be effective, everything needs to be opt-in, right? If you have a language that supports dependent types—and there’s a bunch out there. So actually, OCaml’s module system is a dependent type system. It’s missing one or two key features that makes it really sort of go. But that definitely has aspects of dependent types in it. Haskell has aspects of dependent types.
There are other languages, Coq, Agda, Idris, Lean, these are the ones that really have embraced dependent types. But in any of these languages, you don’t have to use these dependent types. You can just write a sorting function that is described as ‘a list of ints to a list of ints.’ That’s fine. And I think that’s a key aspect of any kind of system that’s gonna be using this dependent types feature, because not everyone wants to prove everything all the time. As you say, proofs are expensive. And so, what you need is, you need the ability to only prove the parts that you think are worthwhile proving.
So let’s say we have an important part of the system that might be easy to get wrong that we want to prove something about. How do we do that? Is that still worth it? And one thing to think about is, what’s your cost-benefit analysis, right? If you do this proving, can you spend less time on testing? Can you spend less time on debugging? Can you spend less time on catastrophic failure because you missed something when debugging?
It is a lot harder to do these programs with proofs. But sometimes, that’s really worth it. It’s a matter of applying good taste, like it is everywhere in programming, right? Any programming language gives you a ton of tools to work with, and it takes time and experience and taste to figure out when it’s time to use which tools.
Right. And I do really like that, kind of, pay-as-you-go kind of attitude of, like, you want some kind of base language that’s relatively simple to work with, and then the ability to add on more features that give you more power of various kinds, and not be forced to pay for that complexity constantly in everything you do. Which is, I think, actually a really hard thing to get right in the design—of like, not having these more powerful features leak in and corrupt the rest of your experience within the language.
That’s right. It’s a very careful thing. And my push in Haskell has been to try to increase or add new features to help support dependent types, and there’s been a large community of people really excited for this. And in my measurement, my biased measurement, a smaller community of people who have been saying, ‘No, no, no. Don’t do this because it’s gonna make it impossible to write simple programs.’ And many, many, many times, I’ve had to reassure people, ‘No, no. All those simple programs will continue to exist, they’ll continue to work. You can do what you want.’ But I also wanna give power to people who want to use the fancy dependent types.
Great. Okay. So let’s leave Haskell and dependent types behind for the moment because that’s not primarily what you’re working on here. Maybe you can tell us, what are you working on here? Like, what is the mission that you are on here, working on OCaml?
So, I’ll start more broadly and then I’ll get narrower. My broad mission is my belief that type systems are a fantastic way to structure thought and structure programs. So by being able to write down what you intend to do before doing it, then you can check what you’ve done, and you can also help formulate your thoughts. And when I say, ‘write down what you intend to do before doing it,’ I mean, write down a specification in types.
The flip side of that, is that it must be the case that you can do that without paying a one-time cost. It’s gonna be problematic if you want to write an efficient program, and yet, by defining your program carefully ahead of time, now it’s no longer as efficient at runtime. This checking and this writing of a specification should all happen at compile time. This should be happening in front of the programmer and not in front of any users. If any of those details leak into user land by slowing your program down or somehow interfering with the operation of your program, then there’s a misdesign in the language.
So coming down to what I’m actually working on here at Jane Street, so the feature I’m working on is something called unboxed types. There’s various different ways of characterizing it, but one way to do so is, it allows inlining of type definitions. So what we might want to have in our design is to have a structure that has four fields that describe some aspects of a trade. And then maybe we store that structure in some larger structure, and maybe that’s stored in some larger structure.
Well, if you do that the naïve way in OCaml, every time you store one structure inside of another, it’s stored by a pointer. And it means now when I wanna access the price of this trade that’s all the way down (it’s a nested structure) I have to follow this pointer, then that pointer, then that pointer, then that pointer, each time I have a cache miss, each time I have to load into memory. This is really bad.
Right. And this highlights a kind of basic fact about OCaml, that it has a very fancy, at least on the scale of modern language that people might use, a very fancy type system and a brutally simple memory representation. OCaml’s internal implementation might have been like a Scheme or Lisp written in the mid-‘60s or something, which is to say, everything looks like either some immediate value, it’s no bigger than a word that fits in a register, or a heap-allocated value, which is like one header word and then one full word for every slot inside of it, and we have all the fancy, like, type goo on top of it that you want, but all of the physical representation looks just like that.
Exactly. Exactly. It means that we’re building this beautiful type discipline in our software to try to avoid errors, and try to express ourselves, and try to communicate more clearly among developers—and we’re paying for that at runtime! This is ridiculous. To me, every time a type system feature slows down your program or otherwise interferes is just a misdesign. And so, the fact that this is our current state of play is problematic. The task I’m working on is coming up with a new design for OCaml’s type system, or this aspect of OCaml’s type system, that allows us to have this rich type structure without paying for it at runtime.
That’s right. Although it’s not that you can pick a type structure that’s kind of oblivious to the performance details, right? In some sense it’s the opposite. The idea of the unboxed type work and a lot of the work that we’re doing now is really about taking design decisions that have to do with performance and making them explicit at the level of types.
Yes, that’s right. Because you can’t, I mean, in the end, we need to execute this somehow. There needs to be an execution model. And so, if we try to ignore all of these execution details in the type system, then there’s not gonna be a way of making it fast. And so, the work is, in some sense, taking some of these execution-oriented details, making them manifest in the type system, getting programmers the ability to write types that take these into account, so that we can then execute fast.
Right. In some sense, I think of this as really being about enabling performance engineering, right? There’s kind of two different lines of people thinking about how to optimize programs via compilers. One is, you make your compiler smarter so it can do more and optimize things for you. So the user can write kind of an ordinary program that just reflects, ‘what do they want to achieve and what’s the logical structure,’ and then the compiler will, like, do something good. And then, the other direction is trying to give a lot of precise understanding to the programmer so they can know exactly what they’re doing and they can optimize their program. And these are both important.
But, like, in some sense the scale of optimizations are very different. Like, if you come up with a way of making your compiler faster that, like, takes most user programs and makes them 20% faster, that’s an enormous win. Like, that’s a shockingly good outcome. Whereas, if you give people good performance engineering tools, the idea that they can think hard about a particular program and make it five, or 10, or 100 times faster is like, in some sense, totally normal. And so, there’s a kind of extra power of giving individuals the ability to optimize. It obviously misses out on scope and there’s something great about being able to go and in one swell foop (laughs) make 20 different, or 100 different, or all the different programs faster. But you can’t get the same kind of order of magnitude of improvements that you can get from giving one programmer the ability to really carefully engineer their system.
Well, there’s a real tension because what in the end we’re doing in programming languages is, we’re trying to find a way for humans to communicate precisely to computers. And we have at our disposal our tool, one of our tools is the fact that this is happening on a computer. So we can write computer programs to try to sort of help cover this distance, right?
In the early days, humans were just completely at a disadvantage and the humans wrote in assembly code. And this was kind of terrible. And we’ve come some way since then, and so now instead of writing sort of instruction-by-instruction that’s gonna be executed by the processor, we can write in a high-level language and have a compiler translate that high-level language to something that the computer can execute. Hopefully along the way, it can actually make that faster. But sometimes, we’ve gotten ourselves sort of too high-level. If we abstract away too many of the details of execution, it’s exactly as you were saying. It means that the programmer can no longer do the performance engineering. And so, we need to expose just enough to the programmer so that now there’s some more details that they can think about. Now they can do that performance engineering.
But still, I think the burden sits on the compiler to take those few hints that the programmer gives to make blazingly fast code come out the other end. And it’s just really about this interplay between how many details do we wanna expose? Certainly not all the details because that’s too hard for the programmer. But we need to expose some. And so, it’s about, over time, programming languages developing more and more features that sort of figure out the right line and the right balance between this desire for high-level languages, closer to human thought, and low-level details, easier to execute.
So, before when we were talking about dependent types, we were talking about how it’s important to have this kind of pay-as-you-go phenomenon, like, it gets harder as you try and get more power. It feels like the same kind of thing shows up here.
So, when you try and get more control over the shape of data in the way that you described, what are the trade-offs? And how do you think about designing the language in such a way that you don’t have whatever extra complexity is there infect everything that you do?
Well, we have to be really careful. So, with unboxed types, one of the challenges is gonna be that unboxed types interfere with polymorphism. So, in a language like OCaml, you can write a function, say, that computes the length of a list. And that’s gonna work over a list, no matter what the list contains. And so the way that we say that, is that in the type of the length function, there’s a type variable. We say that it’s an ‘alpha list’ where alpha can stand for any type that you might wanna put in there. Once we introduce unboxed types, that no longer works out. Some of these types won’t be suitable replacements for alpha. And so it means that, by having these unboxed types in the system, now this really powerful feature of polymorphism becomes constrained. And so, algorithms that people are used to being able to use everywhere, no longer work so well.
So one of the challenges that we have is, how can we recover that? How can we still keep this nice feature of polymorphism and without having unboxed types interfere? And so, one of the things that we’re thinking about doing is some amount of essentially what could become runtime code generation. It comes down to building experience with the feature. As we start understanding this language feature better, we’re going to create programs using these unboxed types, recognize areas in which the current programming paradigms that we engage in no longer work, and then figure out ways around them. It’s this give and take as we’re doing the language design.
And just to focus in on this code generation thing for a second, the basic issue here is that the way you make in OCaml today, separate compilation of work, and make the ability to write a function in one place and use it polymorphically on lots of different types is, again, you’re just kind of taking advantage of this rock stupid memory representation. Like, what’s a list? A list is a collection of heap-allocated values, every one has, like, two slots. One is a place to put the data and the other is a place to have a pointer to the next thing on the list. And when you iterate over it, that thing always has the same shape, it always has the same number of bytes, and so you could just write one piece of code that kind of uniformly walks over it. And what happens in the unboxed types world is, suddenly you want the system to contemplate multiple different possible shapes of the data. And so, kind of at a mechanical level, you need different machine instructions in order to do the walking of the data structure, depending on what that structure is.
And so, that’s how you might end up with wanting code generations. The way to kind of claw back the polymorphism is to generate the code. Maybe you can generate the extra code at compile time, maybe you have to generate the extra code at runtime. But you somehow now have to generate code for multiple different scenarios that essentially represent different physical shapes of the data.
Exactly. And right now, our best thought is that this will happen at compile time, that we’ll be able to figure out what code we need to generate at compile time. That will prevent a few programming idioms that someone might potentially want. I don’t think anyone will ever want those (laughs) idioms in practice so I think we’ll be able to get away with it. But it does mean we’re thinking carefully about this design and it’s a hard thing. I’m not aware of another language that’s really tackled the problem in the way that we expect to tackle it over the next few months.
So another language I think is interesting to compare to, in all of this, is Rust.
So Rust is a language which tries really hard to do two things at the same time which are not trivial to do at the same time. One is to give stronger sense of type safety so that you can write programs that you’re confident aren’t going to crash, so you get lots of protection from bugs from the type system. And also give the user a lot of control over low-level details over how memory is managed. And we’re essentially trying to push OCaml in some sense at that high level of description in the same direction.
How do you think the Rust approach differs from the approach you see us trying to take with OCaml?
This is a good comparison. This takes us a little bit away from unboxed types and polymorphism because I think the way that that appears in Rust is quite different. But the notion of this sort of finer level of control is definitely accurate. But Rust gives up something really big to do what they do. They give up garbage collection. Within Jane Street and generally in programming, I think a lot of the time garbage collection is fantastic, right? It allows us to program without worrying about these low-level details that for most programs, we don’t need to.
So, for instance, when I’m writing the OCaml compiler—which is written in OCaml, of course—I do want it to be performant, but I don’t really care about microsecond latency. And so, I’m happy to write my compiler in a way that allocates a bunch of memory and then when it’s done with that memory, the garbage collector comes and gets rid of it. This is really, really convenient. In Rust, we don’t have that. In Rust, we are forced to think about memory, allocation, and deallocation at every point in time. And this gives us this fine level of control, but it comes at a real cost, and it means now we’re sort of fighting against this ‘pay-as-you-go principle’ that we were talking about earlier. In Rust, for memory, you don’t pay as you go. Everyone has to pay all the time for Rust fine memory control. So we wanna keep the high-level, garbage-collected OCaml for most applications, and then when we really want to have this fine level of control, we wanna have that, too.
And so, some of the work that we’re doing is around adding that finer level of control, but just where we need it.
So, one thing I wonder is whether this pay-as-you-go principle really applies in general or whether it’s like itself a kind of trade-off? I think, if you’d asked the question of like, ‘well we should think of types in general as pay-as-you-go.’ Maybe your baseline should be by default untyped, and then when you want some extra control, you should add types to it and lock it down more.
That seems in principle reasonable. But all the languages that I’ve dealt with that have pay-as-you-go types or what you might call gradual type systems, actually inherit quite a lot of complexity and problems from that choice. It’s often simpler and better, at least for some kinds of problems, to have a uniform approach where you sort of figure out what trade-offs you wanna do and you kind of apply those trade-offs uniformly. And I think, at least from my perspective, the base choice you make in a language like Haskell or OCaml of like, ‘actually, even though it has some ups and downs, we’re just going to have types everywhere and that’s just how it’s gonna be,’ I think is at least a reasonable place in the trade off space of designs.
And I wonder if the same is true about this kind of thing about pay-as-you-go control? Whereas, I can imagine for some kinds of applications, you would want the property that, ‘yeah, I am explicit about memory everywhere. I kind of have a hard guarantee that anywhere I look I have all of this extra information.’ I don’t know. How do you think about this question of where pay-as-you-go is the right way or the wrong way to think about designing something?
I think that’s a great question and I agree. I mean, pay-as-you-go types, people have wanted that for some time. It hasn’t ever really happened. And, I mean, you could almost say that C++ and C have pay-as-you-go types and that they have type systems, but the type systems are sort of completely broken.
(laughs) Pay-as-you-go but then what do you get? I like that.
Well, right, but, I mean, just to clarify for our listeners. What I mean by ‘completely broken’ is that at any point in time in C and C++ you can just say, ‘Oh, ignore the type system. And I’m gonna just take this thing and change its type to be something else.’ And the languages, you know, they take this stance of, trust the programmer, that when the programmer decides to do this, that this is a good idea. But we lose the guarantees that one would want from a type system like that when you dereference a pointer that there’s actually something there when you dereference it.
So, getting back to memory control, I like the idea of pay-as-you-go there, or maybe it’s not even pay-as-you-go as much as a language that can have multiple different modes of use with very easy interop. In that we can have a system where there’s not fine memory management, that you just sort of use the current OCaml’s system of uniform memory representation, and then another part where maybe this particular team or this particular file says, ‘No, no. Here I really want careful memory management.’ This is a different kind of pay-as-you-go. It’s sort of at the file level or something like that, or the package level, I suppose. And then as long as one piece of code can easily call the other, that also works well.
We also see this kind of idea coming out in polyglot systems where we actually have multiple languages all calling one another because maybe this part of the system is easier to write in some language, that part of the system is easier to write in some other language. It’s all a way of, these sort of different approaches to attack the same problem that we don’t wanna have all of this complexity. We want to sort of narrow down what complexity we have, where.
Right. So there’s almost a notion of different dialects within the same language?
With a really high quality FFI that really captures all of the important details so you can kind of interoperate, right? In fact, the work of polyglot stories are actually really challenging to engineers. There’s like, a ton—
… of hard work. I mean, we’ve done a bunch of work trying to get the Python-OCaml interop story working just right. We’ve made good progress there but it’s actually quite challenging. And I feel like the work is quadratic in the number of different (laughs) languages that you need to hook together ‘cause it’s often unique, weird issues that come with every pairing of things that you wanna make all one to the other.
I think that’s right. And that’s one of the appeals of having one language that maybe is flexible enough to operate in multiple different modes. So, in particular, in our design for unboxed types, we are planning to make a box operation explicit. And the box operation is going to be ‘the spot’ that allocates memory. And so, normally, you’ll be able to get uses of this box operation for free. Programmers won’t ever notice it, they won’t write it. It just, all the existing code will have all the boxes inserted. But it would be really easy to imagine some kind of compiler setting that turns that feature off. Meaning that every time you wanna allocate memory, you have to explicitly ask for it.
So this doesn’t bring you all the way to Rust, in that there’s still a garbage collector operating, you still don’t have to manually deallocate memory. But if I wanna write a program that absolutely, where every allocation is known, we haven’t designed this out. Maybe we’re gonna go there, maybe we’re not gonna go there. Nothing is for certain. But it turned out that in our design, it just naturally fell out that we could just add this compiler flag that just turns off boxing and it would be super easy to implement and we could experiment to see how easy it is to work with.
So, here we’re talking about a lot of very ambitious changes to OCaml. One uncomfortable fact about all of this is, like, we don’t own OCaml, we are not the (laughs) primary upstream developers. Every time we wanna make a change to OCaml, there’s a bunch of people who we have to convince that it’s a good change. So one of the things that you’re thinking about in particular is, the kind of relationship of the work that we’re doing here and our connection to the larger OCaml community. Say a little bit more about how you think about this whole process.
So, I wanna start by pushing back against ‘unfortunate fact.’ I don’t see that as an unfortunate fact at all. So OCaml was born as a research project out of Inria in France and still is there. That’s sort of its beating heart. And we are really significant benefactors of that work that others have done, that others continue to do. And so, Jane Street has chosen to work in part of this open source language community where we’re taking others’ ideas and we’re contributing our ideas back.
So in this podcast and in our work, we have these grand ideas. ‘Oh, we’re gonna add this to OCaml. We’re gonna do this, we’re gonna do that. Of course, what I really mean is, we’re going to experiment with this idea internally. And then as that experiment unfolds, as we gain experience and become more sure that it’s a right—I shouldn’t say a right idea. There’s many right ideas. But as we gain more confidence in a particular design, we can then work with the rest of the OCaml community to try to make this part of everyone’s OCaml and not just Jane Street’s.
And in that way, we’re giving back to that community. At the same time, we’re continuing to reap rewards from other work happening in that community. So the biggest example of which is the multicore support that’s coming in OCaml 5. Still gonna be some time before Jane Street’s ready to upgrade to OCaml 5. But that was a huge pile of work done mostly outside, or maybe entirely outside of Jane Street’s walls, and we benefit by being part of this open source ecosystem. And so, I think that this is a really great place to be where we’re participating, we’re getting new input of technical content, like multicore OCaml, as well as design ideas. And I’m even thinking of something just a few weeks ago where I found an infelicity in the compiler, made an improvement, pushed that upstream, and then we got fresh ideas from folks outside of Jane Street’s walls about, ‘Here’s actually an even better way to do it,’ and that was great. And then we could incorporate that here. Without being part of this open source ecosystem, we wouldn’t have gotten that insight and we would be poorer for it.
So, in the Haskell world, you spent a lot of time working on exciting new type system features for Haskell and now you’re doing, in substance, similar kinds of work here. But I think that work flow is pretty different, right? I think then, in the Haskell world, it was like the primary work and ideation and evaluation was all deeply integrated from the beginning in the open source world. And here, we’re doing this work where we’re still open source, still, you know, you can go look at the results on GitHub. But we are mostly iterating internally, and then, over time, as we gain experience, as we gain more confidence that the things we’ve built are good ideas, working to see what subsets of these we can get upstreamed. I’m curious how that has changed your feeling about the work, how it changes the texture of the work that you do?
So working within the context of Jane Street gives us a lot more opportunity for experimentation. So a real big challenge of doing language design is that, by necessity, you come up with an idea and maybe you think it’s a good idea and you experiment with it on a few small programs on your own machine or you get your friend to sort of experiment. You bounce the idea. Maybe, you know, you’re even proactive and you convene a committee of experts on this idea and now you have, you know, five people thinking really hard about this one idea and trying to come up with the absolute best design.
But then, once you come up with the design, if you’re just in an open source environment—without the context of a place like Jane Street—you develop the idea, implement it, and release it, and then now you have thousands or hundreds of thousands of programmers using it, and maybe it was a bad idea. But by the time you discover that it was a bad idea, it’s a little bit too late because maybe you have 90,000 programmers who think it’s a bad idea, but you have a thousand who think it’s a fantastic idea and will be very, very, very upset if you break their programs by changing it. And now you’re in a bad way.
And even the people who think it’s a bad idea, who would like to get rid of it, don’t necessarily want you to break their programs in the interim before they’ve (laughs) stopped relying on the feature.
That’s right. And so, it’s really, really hard to make changes to a proper open source programming language. In the context of Jane Street on the other hand, it’s—I don’t wanna say dead easy. There’s work involved, but it is tractable and somewhat easy in that we can develop an idea, push it out there, get other programmers in the firm to start using it, and then as that happens, we can say, ‘Mm. This isn’t quite working out the way that we thought it would.’ That, you know, theory meets reality, and theory loses.
And so, then we can make a change to the feature and we have sort of an operation that we use internally called a ‘tree smash,’ where some Jane Street engineers work together to come up with a big diff that happens to all of Jane Street’s code, all at once, and we can change the spelling of a keyword if we want to. And in fact, we’re imagining this now. So Jane Street has been working on a feature called ‘local types’ or ‘stack allocation.’ These two things are kind of the same thing. And we’re realizing that one aspect of the design was just a bit wrong. And there’s already a ton of code written using the old design, but we’ve invented a new keyword and a new place for the keyword to appear, and all this stuff.
And so, it’s gonna be some work to fix it, but there’s no part of us that’s saying, ‘Oh no. Now we need to think about a migration plan and we need to make sure that no one is too unhappy with this.’ We’re just gonna go do it. And so, it means by the time we get to upstreaming, everything is battle-tested. And so, it just increases the level of confidence in the design when we go through that process.
One of the concerns I could see people having about this kind of process is, it’s going to do a lot of training on, ‘What are the requirements within Jane Street?’ But there are all sorts of ways in which you could imagine the requirements in Jane Street and outside of Jane Street being different. I’m curious how you think about the design process in a way where you end up with language features that are likely to be of quite general utility, ‘cause I think, in the end, the only ones you’re gonna get accepted upstream are the ones that people think are more broadly useful than just being useful for us.
So that’s indeed a challenge, right? It’s a challenge of overfitting, right? We have a very particular style of code within Jane Street and a very particular use case that we’re working on. I think that is sort of a harder question to answer in some sense. I think one way that we address that is by seeking outside feedback from the very beginning. So, the plan that we have for unboxed types, another Jane Street engineer, Stephen Dolan, he made a presentation about this design for unboxed types, I think in 2019. We’ve been thinking about this for a long time. The local types and stack allocation feature, this has also been featured in a number of presentations that we’ve made to the OCaml community, out in public.
And so, by incorporating that process early, we can get broad feedback. That’s not the kind of feedback that tells us, ‘Oh, should the keyword go here, or should it go there?’ Right? That’s the kind of thing that we develop internally. But I think that, by giving these presentations, by involving the broader community, even from the very beginning, that helps to prevent some of this overfitting problem.
So, another interesting aspect of your background is, you’ve spent a lot of time, years really, working in the Haskell community and working on Haskell. And now, you’re working on a totally new programming language using it both as the surface language in which you write your programs, and also helping to design that language. I’m curious, what struck you about difference? I’m curious, what are the differences that have struck you about Haskell and OCaml at multiple different levels?
It’s been really fun over the past, I guess, six months now being part of these two language communities. So, I should say, I’m still quite active in the Haskell world. I have not left the Haskell world. I’m living both Haskell and OCaml on a daily basis. So let me think of a couple of interesting points of comparison.
So, one is, it seems kind of simple, but actually I think it affects the way that we program. The approach toward interfaces. In Haskell, you tend to write your type signatures sort of right next to the function and Haskell uses a lot of type signatures. OCaml doesn’t so much. And so, OCaml there’s these separate interface files where you define the interface to your whole module, but it’s kind of apart and it means, in my experience, looking at a bunch of OCaml code. OCaml code tends to be rather less documented than similar Haskell code, and I think that’s in part because in Haskell, you put your types right there and that’s a form of documentation. And once you put your types in, then you can also put more documentation there and it becomes part of the habit of the Haskell programmer to put those comments right in there.
A flip side of this, is that Haskell has this feature called typeclasses. And typeclasses allow you to use essentially one functioning to mean a variety of different things. OCaml doesn’t have that feature. And so, what that can mean sometimes is that Haskell code can become quite a bit harder to understand because if you have a bunch of these function names, any of which can mean a variety of different things, all strung together, then it takes a lot of work on the part of the reader to try to understand what on earth is going on there. And that problem just doesn’t arise in OCaml. OCaml is much more explicit about what operations it’s taking.
So maybe I can even, taking these two examples, generalize that a little bit and say, I find Haskell to be somewhat more explicit about what’s happening at compile time. Whereas, OCaml is somewhat more explicit about what’s happening at runtime. So another place where that comes into play is that, the Haskell compiler—and this connects with a topic we were talking about earlier—the Haskell compiler optimizes much more aggressively than the OCaml one does. And that’s because in Haskell, Haskell’s a lazy language which means that if you say, ‘Let X equal some big computation,’ we’re not gonna compute the big computation until we need the value of X. In OCaml, if you say, ‘Let X equal a big computation,’ we just compute that thing right away.
And so, that means, on the one hand you might say, ‘Oh, well maybe Haskell is more efficient,’ because maybe some code path never uses the value of X and so it means that we can discard that whole big thing. But it also means that it’s much harder to predict in Haskell how fast your program is going to run or what its performance characteristics are gonna be. In OCaml, it’s much easier to predict that. You can just sort of read your file top to bottom and, within a function, it just performs the operations that you see roughly in the order that you see them. And that means, OCaml again is more explicit about what happens at runtime, but without some of the type annotations that we see in Haskell, a little less explicit about what’s happening at compile time.
You mentioned a couple of things that are different that I wonder if they’re really language differences, or just cultural differences. If you look at the tendency to do fewer type annotations on the definition of functions, you could put more type annotations. And I think there are some people who write OCaml who do, but there’s certainly a tendency not to and I think the Jane Street house style certainly is one that does not have lots of type annotations, outside of interface files where those are essentially required everywhere.
And the documentation thing, too, like, the interface files are a very natural place to put documentation and I am constantly dissatisfied that I think we put way too little documentation in the (laughs) places that we could. And I don’t know that there’s anything exactly about the language that forces that. Like, I’m not sure that I buy ‘the need to put type annotations right on functions’ is the thing that really makes that difference.
That may be true. It’s hard to say. I do think that form follows function, function follows form a little bit, in that, when you’re used to describing the specification of a thing right next to that thing, that, to me, is going to encourage you to write documentation. Whereas, in OCaml, you write the specification over there, in that other file, nowhere near where you write your implementation. And sometimes I think that means that you’re less likely to put those comments on the implementation when you need them.
It’s funny. My intuition is the opposite of, like, ‘Oh, I’ve written this very bare interface file that just has types,’ and I think empathetically about the person who’s reading it. It’s like, ‘How will they have any idea what this means? I had better put a comment here.’ (laughs)
Right. So the comment and the interface files are good, but I think it’s not just that. I mean, and this could also be, I have a bias in that, most of the OCaml that I’m reading and writing is in the OCaml compiler itself. Maybe this is different than everything else at Jane Street. In fact, I’m sure it is. And it has been striking for me after spending so many years working in Haskell’s compiler, that Haskell’s compiler has many, many, many more comments than OCaml’s compiler does. Maybe I’m overfitting on this particular piece of software instead of just the language in general.
I do think looking at the innards of a compiler is always a weird place to think about how one should write code for the language. Because, like, one of the facts about OCaml—as it is often the case for many languages—is that OCaml was written by people who didn’t yet really know how to program in OCaml kind of by definition. And lots of the code that’s there now has been there for a long time and you can sorta see that. I think if you went to those same people today and been like, ‘How would you write this now?’ They’d all be like, ‘Oh, really very differently.’ But, you know, parts of it just kind of haven’t been rewritten and have the shape that they did years ago and are harder to understand than kind of anyone would exactly like it to be today.
And to be fair, that exact same thing is true in the Haskell compiler. There are stretches of that that no one would write it that way today. But it was written that way before anyone really knew how to program in Haskell.
So another difference that’s always struck me about Haskell is the different approach to language extensions. And I think about this in two different ways. One is, OCaml is just massively more conservative. There was a period in time early in OCaml’s development where you could almost see the process of Ph.D. students graduating. It’s like someone graduates, they write some language feature, and it gets added to the language. And that stopped pretty early in OCaml. OCaml got pretty conservative about making changes. It was like, ‘No, no, no, we’re gonna add things that are like, really good, and it was pretty restrictive about what would go in. And throughout that time, it kind of had one language, pretty much. OCaml worked in one particular way and that was kinda that.
Haskell on the other hand, has had a lot of glorious experimentation around language features and those experimental language features were often hidden behind pragmas, right? There’s lots of different things you could turn on and off. And so there kind of isn’t one Haskell or two Haskells, but there’s like 10,000 different Haskells, depending on which collection of flags you wanna turn on or turn off. You have 10 different pragmas and now you have two of the 10 different possible arrangements of the language features. I’m curious how you think about those different sets of choices?
Yeah. I mean, that’s a great question. So I do have to push back against your sense of scale. So Haskell has, when I last counted, and this was a few years ago, 150 or so extensions. So Haskell has actually 2150 languages.
Ouch, indeed. So I think that this comes from the origin of Haskell versus the origin of OCaml so actually you probably know more about the origin of OCaml than I do. But I know Haskell was started by committee. There was a committee that met starting in, I think, 1989 into 1990, coalescing these ideas around a lazy functional programming language. And then this eventually became a Haskell standard and there were multiple different compilers for Haskell that all implemented the same standard Haskell. And then maybe one compiler would think, ‘Ooh, wouldn’t it be fun if we had X?’ And so, then they would add that feature, but everyone still wanted to make sure that it was a Haskell compiler and not this other special language based on Haskell compilers. So there was an idea of language extension. So you could have base Haskell plus these various extensions.
So, in other words, OCaml is like Python and Haskell is like Scheme?
Sure. Yes. In that OCaml has a manual for the language, but there is not a standard. There are not multiple different software artifacts that all compile some OCaml language that exists beyond a single compiler. But, in any case, going back to Haskell, that was the early days of Haskell and there were multiple Haskell compilers. As time has gone on, all of the other ones have either died off or there are still a few other Haskell compilers out there, but there’s not really an attempt to stay current with the newest features. And there’s really now just one: the Glasgow Haskell Compiler, or GHC. And that’s the one that, when I say that there’s 150 language extensions, the GHC is supporting that.
And so, when you have multiple different things, if you wanted to find a common core that all of these different compilers can interop with, it makes a lot of sense to have individual extensions when you say you’re deviating from this common core. Now there’s really just one compiler.
I think that the current system, the current plan at Haskell, is not very well motivated. And instead, the feedback that I hear from users is that they find the whole language extension system very heavy and it means that, to get much done in Haskell, you need to now start your file with the list of 20 extensions that you’re using. And newcomers can’t tell the difference between brand new experimental extensions that might change from one compiler to another, or extensions that have been around for 20 years, or there are some extensions that have been around for 20 years but actually we know are dangerous and you really shouldn’t use.
And when I say, ‘We know,’ I mean, like, I know this and a couple of other Haskellers know this, but someone just picking up the language won’t know that. And I personally find that quite problematic. And so, there’s a debate going on right now within the GHC Steering Committee who is a body chosen to sort of help evaluate the evolution of the language to reimagine all of this. And I’m hoping that we end up with a structure that looks quite different from what we have today, while still retaining backward compatibility.
So if you have your 20 language extensions at the top of your file, that will continue to work in this new vision. But I am hoping that we can reorganize it in a way that’s a little bit more user-friendly.
Do you think in the end it would make sense to move toward something where there’s closer to one standard language that includes most of the extensions, and so that most users end up not thinking about that ‘two to the whatever’ configuration space?
I do think settling on one standard language would be good. I think we can get 80% of the way there and go from 150 extensions to maybe seven or something like that. I don’t think we’ll be able to get all the way there because there’s just too many different use cases for Haskell, there’s too many different people who have slightly different ideas of what that core should be. So if we just sort of got rid of the extensions mechanism, I think that that would cause too much of a rift in the community and probably not be tenable. But we can get close.
So, throughout this conversation, I feel like there’s been a particular kind of vision and idea of what programming is that’s motivated a lot of your thinking. And that has something to do with having a language that’s simultaneously expressive, it lets you say what you wanna say, but also has real tools for reasoning, in both type systems and, you know, more sophisticated mode-dependent types, or essentially automated tools for reasoning about your program. So in the last, I don’t know, few months, couple of years, there’s been an enormous amount of motion in a totally different direction for making programming better, which is various AI-assisted ways of making programming easier. With all of this kind of recent work on large language models, things like ChatGPT, Codex, all of that, making an enormous difference to how people are programming on a regular basis. I’m curious how you think about this and how it fits into your broader way of thinking about programming?
I see this evolution of sort of AI-assisted programming as not quite the sea change that maybe others have seen it to be. I see it as a big step. It’s not a small step change. But it doesn’t remove the need to communicate precisely. In that, to me, the interesting thing about a programming language is that it’s a mode of communication that is precise. In a sense, that’s almost the definition of what makes a programming language a programming language, as opposed to some other kind of language. There’s a precise semantics to everything that is said in that language. And that language is used as a medium of communication, both from human to computer. That’s how we often think of it. But also from human to human.
With the advent of AI-assisted programming, now we have sort of a new method of communication in that it’s a communication from computer back to human. In that, you might have something like ChatGPT producing the code, but a human still has to read that code and make sure that it does what you think it does. And as a medium of precise communication, it’s still very important to have a programming language that allows that communication to happen.
And so, this new mode of communication is going to put different pressures on language design than what we’ve had in the past. But I don’t think it removes the need for programming language design. But like I said, it does create different pressures and these have been evident for some time in that one little maxim I’ve had in my head for years now about language design is that you wanna optimize for reading, not for writing. Code gets read much more often than it gets written.
And so, when designing a new language feature, maybe you’re thinking, ‘Okay, I could do this very cleverly with, like, a nicely placed twiddle in this spot in the code. And I could use that twiddle to mean something very important about my program. And maybe that’s very concise, but it’s hard to read, it’s hard to search for, it’s hard to train new people to understand. Instead, it tends to be better to write out words in your design and programming language, that’s easier to search for, and easier to learn, and just easier to organize in your brain. If we end up getting to a mode where humans are doing even less and less writing, and computers are doing more and more of it, then those pressures just increase. And we wanna make the language easier to read and maybe even harder to write. Maybe we even get to a point where if computers are doing all of the code generation, it’s just kind of, you know, symbolic and structured. It’s not just text.
So we get these richer programming languages but I just don’t see it as, there was yesterday and then there’s tomorrow and it’s gonna be totally different and let’s just start from scratch. I see this as just one phase in evolution of this precise communication medium.
One of the things that’s really striking about these AI assistants, at least in their current form, is they’re really surprisingly powerful, right? They can really do a kind of stunningly good job of taking a query and understanding it and providing a piece of code that tries to do what it is that you asked for it to do.
But they’re also strikingly fallible. They make lots of mistakes. And, yeah, the reading thing is pretty important because you ask ChatGPT or, you know, whatever your favorite large language model is, to write a piece of code and look at the result. And it’s super easy for the result to just be wrong in all sorts of different ways. And I can sort of see the rising requirement of making things more readable. I’m curious how you think things like dependent types fit into this story?
So, dependent types in their essence are a way of writing precise specifications. So the sorting example that I gave earlier, I described that, you know, you could have a function instead of going from ‘a list of ints to a list of ints’ to something more glorious. Well, before dependent types, or you know, language without dependent types, you could just do that in comments. But probably in your comments, you’re not going to be as precise as you could be if you really wrote it in types, right? You say, you know, return to the input list in ascending order, or something like that. But what does that really mean?
We need a language where we can precisely specify what this function is doing. So as I said earlier, ascending isn’t quite right. I mean, if you write in a comment, ‘This returns the input list in ascending order,’ well, what if the input list has duplicates? Now you can’t even meet that specification. Where actually, that specification doesn’t make any sense at all because the input list isn’t in ascending order. What does that mean to say to put the input list in ascending order? That’s a contradiction in terms, because the input list isn’t in ascending order.
And so, there’s some amount of interpretation that humans can provide, interpretation that a large language model can provide, but in the end, it’s gonna come down to precise communication and specification. And so, where do dependent types fit in? Well, they can become the input to the large language model. Maybe now we have programmers who are well-versed in precise communication of specifications, not implementations, and we can say, ‘ChatGPT, give me a function of this type.’ And then you have your nice type that takes an input list, and it returns an output list that is a permutation of the input list in non-decreasing order and we specify that.
And then now, that gives a nice specification for ChatGPT to go off and say, ‘Okay, here’s an implementation that meets that. Oh, and by the way, it has this asymptotic running time because I’m a nice large language model and I’m gonna tell you that.’ And then, now we actually have a model by which we can check that the result meets its specification if it is in this precise language of dependent types.
Although I have to say, I think I am more pessimistic about the idea of people actually going off and writing down the specifications even. ‘Cause those are actually pretty hard to write.
I wonder if a more plausible model is, you go to your large language model and say, ‘Please write me a specification for a function that sorts a list.’ And then it, like, spits something out. And then you look at it and think, yeah, that seems about right. And then you go from there to next stages. But the role that this kind of concise specification has, it’s like a smaller and maybe more readable thing that you can use as part of verifying whether the later pieces in fact do what you think they’re supposed to do.
Yes, but I think there’s a real danger here. I agree with what you say in that, writing a precise specification is hard. And humans are lazy. So if it’s hard, we’re gonna ask the computer to do it. If we’re asking the computer to write the specification of the function that it’s going to write, now we’re getting into thin ice. Because if we’re working in a system where the human is not expert enough to be able to read the specification and know that it’s the right specification, now we’ve lost. Now the computer can just sort of go off and do whatever it wants and we have no way of knowing whether it’s right or wrong because we’ve lost that ability to sort of have that precise communication.
I think one of the tricky things here is, people are often pretty bad at the reading part of this, right? The idea if something gets written and now you have to read it, especially if it wasn’t written in a really clear way, reading some piece of code that is not really well-structured is actually really challenging. Like, one of the actually most important parts of the process of code review I find, is not so much that, like, one person writes the code and someone else reads it to validate that it’s done the right thing. It’s more like, somebody writes the code and the other person says, ‘Oh, man. This is, like, too messy for me to even read.’ And then there’s some kind of back and forth process where you try and get something that’s actually simple enough to be understood. And I think it’s an interesting open question of, like, as we start using more of these AI assistants, how good they will be at generating things that are actually simple.
Although, if we have precise specifications, the simplicity of the implementation becomes less important.
That’s fair. I mean, in some sense there’s a kind of open technological question of, like, how good will large language models be, acting as tactics for theorem provers?
Right. Yeah. I mean, but if we’re in an environment where there is a tight correspondence between the specification and the implementation, and what I mean by ‘tight correspondence’ is this dependent type’s model where you can—and there’s other models, it’s not just dependent types, there’s refinement types, there’s other ways of checking an implementation against a specification. But if we’re in an environment where we can check an implementation against a specification, the specification is simple enough and communicated precisely whether that’s in very careful natural language or probably not just natural language ‘cause natural language is very, very squishy. But probably in some specification language, you could call that specification language ‘dependent types’ if you want or you could call it something else. But if we can communicate that specification, check the implementation against the specification, now suddenly there’s not all that much incentive to ever read the implementation.
Right. It becomes a little bit like, you know, reading the output of the compiler.
Yeah, that’s right.
You don’t normally bother.
Yeah. It’s interesting. I do wonder whether a whole potential kind of direction for innovation here, which you more or less have pointed in the direction of, is designing languages that work usefully in this kind of intermediate things to generate in the process of working with some kind of AI assistant. The thing that’s generated as an intermedium really affects the overall process of working on the system and generating confidence it’s doing the right thing. It’s not a totally new problem, I guess, in the context of language design. But it does put new kinds of pressure and maybe changes the trade-offs that show up.
Yeah, that’s right. I mean, you know, it’s too early, I think, to start to design a programming language around this kind of interaction. We’ve only been having these interactions with these assistants for a few months. And they’re changing fast. So I think now would be the wrong time. In two years, maybe one could start thinking about designing a language from scratch to do this. That sounds plausible to me. But I think a good starting point would be languages that we have now because they’re designed for precise communication. That’s, in the end, what we need. And, again, maybe there’s less pressure on the writing piece and more pressure on the reading piece. But I don’t think that they’re so divorced from each other that it means that we throw out everything we have and start from scratch. That seems, to me, pretty unlikely.
That makes sense. Well, thanks so much for joining me. This has been a lot of fun.
You’ll find a complete transcript of the episode, along with links to some of the things that we discussed at signalsandthreads.com. Thanks for joining us and see you next time.