The Peterman Pod - Co-Creator of Haskell: Functional Programming, Thinking in Types, Useless Languages | Simon Jones
Episode Date: June 8, 2026Simon Peyton Jones is the co-creator of Haskell (pure functional programming language) and I interviewed him about functional programming, why it matters, and his thoughts on other programming languag...es.• My ergonomic keyboard project I mentioned, you can follow along here: https://read.compose.llc/Podcast links:• YouTube: https://youtu.be/xcB_LF3cdqw• Apple: https://podcasts.apple.com/us/podcast/the-peterman-pod/id1777363835• Transcript: https://www.developing.dev/p/co-creator-of-haskell-functionalThank you to this episode's sponsor for supporting my work:• WorkOS: makes your app Enterprise Ready with easy to use APIs to add SSO, SCIM, RBAC, and more in just a few lines of code, check them out at https://workos.com/Timestamps:(00:00) Intro(00:39) What functional programming is(09:18) Downsides of functional programming(10:53) Specialized hardware for functional programming(21:47) Haskell is useless(25:59) Rust vs C(28:26) Haskell vs OCaml(35:26) Side effects in Haskell(44:26) Type systems(57:30) How the Haskell compiler works(01:04:35) Why Haskell is talked about more than used(01:09:07) Avoiding success at all costs(01:11:12) LLMs and programming languages(01:13:57) New programming language design(01:15:59) Should students continue to learn programming(01:22:33) Why Excel is his 2nd favorite programming language(01:25:04) Advice for his younger selfWhere to find Simon:• LinkedIn: https://www.linkedin.com/in/simonpj/• Wikipedia: https://en.wikipedia.org/wiki/Simon_Peyton_Jones• Personal Website: https://simon.peytonjones.org/Where to find Ryan:• Newsletter: https://www.developing.dev/• X/Twitter: https://x.com/ryanlpeterman• LinkedIn: https://www.linkedin.com/in/ryanlpeterman/• Threads: https://www.threads.com/@ryanlpeterman• Instagram: https://www.instagram.com/ryanlpeterman• TikTok: https://www.tiktok.com/@ryanlpetermanReferenced in this episode:• Haskell is useless: https://www.youtube.com/watch?v=iSmkqocn0oQ• John Backus Turing Award lecture: https://worrydream.com/refs/Backus_1978_-_Can_Programming_Be_Liberated_from_the_von_Neumann_Style.pdf• Why functional programming matters: https://www.cs.kent.ac.uk/people/staff/dat/miranda/whyfp90.pdf• Excel is his 2nd favorite programming language: https://www.youtube.com/watch?v=_M4P5M85KO8
Transcript
Discussion (0)
Why is the internet so insecure?
Primarily, because all of our software infrastructure is written in unsafe languages.
This is Simon Peyton Jones, co-creator of the functional programming language, Haskell,
and I asked them all about programming language design.
If we rewrote all of our software infrastructure and rust, things would be way, way better.
I think that statically typed languages are a huge boon for LLMs.
I do large-scale, systematic refactories, fearlessly, because the type system keeps me safe.
In fact, would you recommend people learn how to program today?
I think people would like to be worried in the sense that I think there's going to be
considerable dislocation.
Here's the full episode.
I wanted to start by asking you, what is functional programming in your words and why does
it matter to people who might use the other languages, the imperative languages?
Functional programming is about programming with values instead of mutation.
So in a conventional imperative programming, computation proceeds by, like if I want to add up the numbers between 1 and 100, I have a mutable cell containing a running total.
And then as computation proceeds, I keep adding to my running total.
So there's a cell that changes its value over time.
Computation pretty step by step, there's a program counter.
Now, that's very unlike mathematics.
If I say 3 plus 4 times 7, you don't think of a mutable cell that changes its value over time.
you think, well, 3 plus 4 is 7, 7 times 7 is 49.
Just the answer is 49.
So in a sense, it's more declarative than imperative.
Another way to think of it is it's more like what a spreadsheet does.
In a spreadsheet formula, if you put in a cell, you'd say in A1,
you put the formula equals A2 times A3 plus 7.
Well, then that's just the formula that is evaluated,
and you expect to compute the values of A2 and A3 automatically,
before computing A1, it wouldn't make any sense to compute A1 from the old values of A2 and A3.
And there's no notion of a program counter.
There's no notion of do this and then do that.
It wouldn't be sensible to say equals print X plus three.
Because who knows when or even whether that cell will ever, that formula will ever be evaluated.
Now the surprise is that it is possible to do useful things in such a limited language.
when I would learn programming,
I learned it by writing machine code
in which the fundamental mode of computation is registers
and program counters, right,
and mutation of registers as you go along and memory.
Like computation and mutation seem to be inextricably interwoven.
That's how computation gets done.
So it was a surprise to me when I learned that,
well, it is possible to do useful things with functional programs.
I still remember that amazing revelation.
Now, in fact, one way to look at it is this.
Right back in the late 1920s, early 1930s,
Alan Turing was doing a PhD in Princeton
under the supervision of Alonzo Church.
Now, Alan Turing, as we all know, invented the Turing machine,
which is fundamentally about mutation.
It has a little tape, and you can read things from the tape,
and then you write back new things onto the tape,
and it has an immutable program.
Now, Alonzo Church, at the very same time,
was working on something he called the Lambda calculus,
in which there's no motion of mutation.
This is, the lambda calculus is like the essence of functional programming.
Programs execute just by reduction.
Now, so these were two different models of computation.
You might ask, what can you compute with the Turing machine,
and what can compute with a lambda term?
And the astonishing thing that turned out,
which is completely non-obvious,
is that anything you can compute with a lambda term,
you can compute with a Turing machine,
and anything you compute with a Turing machine,
you can compute with Lanta calculus.
So that means that the two are identical
from the point of view of their computational power.
Big surprise to me.
Okay, so they're equally powerful,
and then the question is,
which might you want to choose?
As I say, I started life from my baseline of imperative programming,
of course that's the way you write programs,
And functional programming is a kind of weird, anomalous, rather nerdy, academic thing.
But I became hooked on the idea that by excluding side effects by default,
that is, imperative programming has side effects by default,
by excluding side effects by default,
we could make programs that are easier to write, easier to maintain, easier to reason about.
And then the question is, what are the practical obstacles?
Is it slower?
Is it less convenient and so forth?
in effect, I've devoted my research life to exploring that hypothesis.
The wonderful thing about being a researcher is you can take something, which at the time,
this is back in 1980, back in 1980, a functional program was very nerdy, geeky academic,
but I was allowed to spend my professional life just saying what would happen if you took
that one idea programming with values and just ran with it. Where would you get?
and I think we've got
somebody somewhere pretty interesting.
I don't want to say that it's better
fundamentally because after all
Haskell on that journey
started with functional programming
then we learned how to mix
functional imperative programming together
in a way that we might talk about later
so it's not really an either-or
it's more of a both-hand in the end
but my
position now is
as I often say
when the limestone
of imperative programming has worn away
the granite of functional programming
will be revealed underneath.
And why do you say that?
Well, functional programming is the language of mathematics.
That's how we fundamentally think about things
when we want to be formal.
Indeed, if we want to reason about
and say what does this imperative program do,
we have to wheel out a lot of mathematics
and, you know, hoar triples and so forth.
But that's all in the world of maths,
which is just functional programming.
Functional programming is just mathematics brought to life
and made executable.
So you might wonder, I suppose you may be saying,
well, isn't imperative programming just enough?
And I think that there you have to look at the broad sweep of things.
I think what's happened is that from the world of functional programming,
lots of ideas have got adopted into the imperative arena.
So they grew up over here in functional programming.
and then they have been adopted by the imperative programming things.
So you might think garbage collection, you might think lambdas,
you might think language integrated query,
you might think about lots about type systems, polymorphism and static typing, tons of that.
So at least it has been an intellectually interesting experiment
that has been a very fertile laboratory,
which to grow up ideas that can infect the mainstream.
Now, my instinct now is that we should,
should start from functional programming and do imperative programming where necessary.
I think probably the mainstream is we should start with imperative programming
and sort of add bits of functional programming where possible.
And I'm not going to say that they're wrong to do that,
just to say that I think there's a pretty interesting dialogue.
Now, you do have to think very differently about the act of programming, right?
You do have to rewire your brain quite a bit.
But just let me, this is something we might want to come back to.
I said easier to maintain.
As programs get old, think of a 10-year-old program.
If you have a piece of 10-year-old code
written by somebody who's long since left your company
that mutates some more or less global variables,
then you might be a little bit afraid about,
oh, and there's other bits of code scattered about
that also read or write those global variables,
then you might be a little cautious about making modifications,
less, oh, if I put these two things, if I do B and then A instead of A and then B,
maybe the global variable that I read somewhere deep inside the code
is now not have the value that it was expected to.
So reading and writing shared variables that are visible outside the scope,
somewhat global variables, is a form of coupling between bits of code that is very invisible.
Very invisible.
So functional programming forces,
that to become visible.
It forces it into the open.
And by the act of forcing it into the open,
that strongly encourages a style of programming
which you don't have such interactions
because they are painful to manage.
So it encourages
a style in which programs
execute in modular box without interacting.
Now, every imperative programmer would want to do that,
but I want them to do that
in a provably secure way.
So you can know that these programs.
I'm not interested to act rather than just say, I've done my best.
Then I guess on the flip side then, what are the downsides of functional programming language?
Oh, well, it can be very tiresome and inconvenient, right?
So you're somewhere deep in the, you know, a subroutine of a subroutine of a software stack,
and you want to reach out and, I don't know, grab the time of day.
Oh, dear.
That's a side effect.
But we're in a pure functional language, the time of, and indeed,
there's a good reason because everybody else in the stack is assuming that if I call this function applied to, you know, seven, it will give me the same answer if I call it, call it again applied to seven. But if it could interrogate the time of day, suddenly it might give a different answer. Right. So as simple a thing as I was asking for the time of day, invalidates a whole set of assumptions and so is not by default allowed at all. And that can be a convenient because you always say, look, guys, all I want to do is get hold of the time of day. Don't give me grief.
And so every functional programming language, including Haskell, provides you with a little trapdoor.
Haskell's is called unsafe performio.
And it says, trust me, I'm going to do something that has side effects, but you can pretend there weren't any.
All right.
And it's called, it is literally spelled, UN, S, A, F, E, Perform I-O, the long name that has unsafe in the title so that you, the programmer, taking on the obligation to make sure that what you're doing,
is okay. Whereas in imperative language, everything you do could be unsafe before my ohish.
I was watching one of your lectures and you did go over a little bit the history of functional
programming languages and you mentioned somewhere that there was this idea of people building
hardware for functional programming languages. It just made me curious because I didn't, I only know
the Von Neumann architecture.
Do you have any idea what that might look like
and some high-level thoughts?
Well, I did.
I now think it's probably a bad idea.
But back in 19, when was Backus's Tewing Award lecture?
I think it was 1978 or about.
What was it called?
Can programming be liberated
from the Von Neumann style?
That's it.
Now, he was saying two things.
Firstly, he, the designer
Now, of Fortran, a quintessentially imperative language,
the designer of Fortran was saying,
guys, we should do functional programming.
That was what the talk was about.
And he introduced a language called FP
for functional programming.
It was a very particular, somewhat idiosyncratic,
functional programming language, but there it was.
So that was rather amazing, right?
This was 1977.
He was saying, guys, give up on imperative programming.
just do functional programming.
Here's how you do it.
But he also said in the same lecture,
we might imagine designing new hardware
to execute this new kind of language.
So if you like, we've got Turing machines,
they give rise to register machines
and microprocessors and so forth,
maybe Lambda calculus,
perhaps if we designed a machine
from the ground up to do Lambda calculus,
it would look different.
So that was, of course, very exciting.
and a similar kind of thinking
led to an actual commercial machine
called a LISP machine
that was a piece of hardware
that was designed by symbolics
to execute LISP programs
LISP is a mostly functional programming language
and so at the time I was
in my early 20s
we were thinking, oh, crumbs,
what would a piece of hardware look like
that was primarily designed
to execute functional programs?
We'd have a different kind of instruction set
and architecture.
So the newest we got to it, actually,
was, well, there was a big strand of work then
that was the called the MIT Dataflow project.
So data flow languages, functional languages,
they're definitely the same kind of category, right?
And so the MIT had this great group led by Arvind,
called the MIT Data Flow Group,
and they designed, in the end, a machine called Monsoon,
that was specifically designed to execute data flow programs,
that is functional programs.
It was based on, you know, a token store and matching and tokens flowing around
and getting matched up and executing.
You might imagine data flowing through a graph.
When three arrives at a plus node and four arrives at the other input of the plus node,
bam, you could fire that plus node, right?
And the hardware did all of that.
At a similar kind of time in England, my colleagues, Thomas Clark, Joe Stoy and others,
built something called the Skii machine, skim.
There's a paper about this in the FPC.
conference and Skim was designed
to execute SK combinators
so at the time
David Turner has written lovely paper
in which he described how to take
Lambda calculus programs and translate
them into SK combinations
so you can read about that in my book
it's a very simple translation
that you can give the whole translation in half a dozen lines
but then you have this messes of
this mess of S's and K's
and S and K reduction is very very easy
so
like the machine code of functional program.
That one way to think of it.
What is a SK combinator just for context?
There are three combinators, SK and I.
So, and they have simple reduction rules.
Here they are.
I of X equals X, KX, Y, equals X.
SX, Y, Z is X of Z applied to Y of Z.
End of story.
That's all.
So very simple, right?
So now all I've got to do is,
take my lump to translate it into a giant tree of SK combinators, right?
S applied to K, apply to I of 3, and S of Z, just a huge tree of S's and K's.
Right?
Now simply apply the rewrite rules I gave you.
And that's, that is program execution.
It's rather astonishing that that can execute arbitrarily complicated programs, but it can.
Do you think that's rather amazing?
Now, then I can take an arbitrary Haskell program.
I can translate it into Lambda terms, and I can translate there's Lambda terms.
into S&K, very simple transformation,
and I can just run those three rules,
and it'll produce the output of the Haskell program.
That's pretty amazing.
And indeed, there is an implementation of Haskell
that uses exactly this.
It's called MicroHS, and my colleague, Leonard Augusten,
has built it,
and its execution mechanism is SK-commodator reduction.
And if you sit in micro-HS,
you can ask you to show you the S&Ks that it produces.
If we had a shared screen, I could probably show you.
So, long story short, then.
the Skii machine was designed to take SK trees,
big trees of S&K combinators,
and just execute them directly.
So it was built in hardware,
and it ran perfectly well, reasonably fast.
Now, why didn't this catch on?
Why don't we have functional programming machines?
I mean, at the time, it was a big idea.
We even had a conference called
Functional Programming Computer Architecture, FPCA,
It was right there in the title of the conference.
But slowly we became aware that what was really happening is we were doing at runtime what we could do instead at compile time.
And that is always a bad idea.
Imagine an interpreter for, I don't know, Pascal or something.
Or for Java.
We can compile Java to bytecode and we could interpret the bytecode.
right?
So then we're doing something at runtime.
At runtime, the interpreter is looking at the bytecode
saying, dispatching to say which instruction, blah, blah, blah, blah.
Now what does EJIT do?
It takes a sequence of bytecode and says, oh, no, no.
I'm going to take that sequence of bytecode to compile it to a sequence of machine instructions
that, when executed, will do the same thing, right?
Much faster.
Because it can take advantage of bytecode sequences, for example,
to say, you know, if you swap and then swap, it's a no-op or something like that.
So what, in fact, it turned out is this whole S-K-I business, and any other,
and indeed, data-flow machines also, were simply doing at runtime what you could do better at compile time.
We were building an interpreter in hardware.
So don't build the interpreter in hardware.
Instead, build a compiler that translates your Lambda term into a sequence of machine instructions
that when executed will behave as if you had done this reduction business.
And then you might think, well, machine instructions for what machine?
Well, from a practical point of view, it was very hard to compete with Intel.
Right.
They were just spending billions of many years on making X-86s go faster
and arm likewise for arms.
So we can leverage all of that work by compiling into their instruction set.
But even if you say you are God, you are the chief executive in Intel and can tell them to add new instructions or computation mechanisms to support functional programming, what would you add?
Not very much.
Little bits to do with garbage collection barriers, I think, and such like, but nothing major.
So, in other words, I think the whole build hardware to execute functional programming directly.
turned out to be a mistake.
Inspiring mistake, fun mistake,
I had a great time, but a mistake.
It's better to build a compiler.
I would have thought there might be some unique opportunities
for parallelism,
given the immutability in these functional programming languages.
So, yeah, you described that graph
or that SK combinator graph.
I imagine you could, you know,
execute that all in parallel,
assuming there's no connections across,
the graph.
And indeed, that was the data flow machine,
the MIT data flow machine
was all based on that idea.
So throw the whole graph into the token store
and just run all the nodes that are runable.
Right.
But that's incredibly fine grade.
You've got to imagine this big tree
of a million nodes
and your processor is sort of wandering around over
at doing little things in parallel.
There's a lot of memory traffic going on there.
And these little threads
are operating on the same bit of tree
then there's synchronization costs.
So very, very fine-grained parallel computation like this turns out to be impractical.
Or not impractical.
You could do it, but it's very slow.
So the data flow people, their history, if you look at the history of the MIT Dataflow project,
they started with micro-parallelism.
Every individual instruction was a separate parallel computation that might take place
and the token sort would match it up.
And then they built more and more compiler technology.
that group these things together into larger units.
So they expanded from micro threads of single instruction threads
into 10 instruction threads or 100 instruction threads, right?
But even that never really caught on.
So you're right, though, that if I say in a Haskell program, E1 plus E2,
then I can do E1 and E2 in parallel.
And GHC does support you in doing that.
But you nowadays, rather than expecting the compiler to do that
automatically for every sub-expression, you can spark a computation.
You say E1 par E2, and that says do E1 and E2 in parallel.
There's a project at Carnegie Mellon for a strict language called ML
that has a very good variant of parallel ML going in a similar way.
So essentially we've moved away from the dream of automatically getting parallel
very, very fine grain to programmer,
we give programmer clues about words,
good idea to do parallelism,
but still the compiler will try not to fork two tiny grains.
I was searching on YouTube,
and I've searched your name,
and this video came up,
and it said,
Haskell is useless, Simon, Baten Jones.
And it's a video from a long time ago.
Someone's got a low-quality,
handheld camera,
There's a group of researchers that are all talking.
Butler Lambson is across the table, and it's a fun little video.
And in the video, you lay out this two-dimensional graph, where on one dimension, there is the
useful versus useless axis, and then on the other dimension, there is the safe versus dangerous,
and then you're placing programming languages on this two-dimensional graph.
And I think you started by putting C as it's very useful, but it's incredibly dangerous.
And then you put Haskell on the polar opposite side.
You said it's useless, but it's very safe.
What are the least safe and most useful languages and why do you place them there, like C, for instance?
Well, yeah.
So let's see, in C, you program my mutation.
So it's unsafe in the sense that any function can mutate any variable,
at any time. It has a lot, you pass pointers
around a lot and functions mutate
the memory pointed to by those pointers
and moreover, typically they can mutate
it anywhere, there's no array bounce checks
or anything. So it's kind of like
super unsafe and in fact, and this is demonstrated by
fact, can you imagine
all of these exploits that we get every
day, right?
That, you know, mythos is discovering
in huge numbers, but we've had, you know,
why is the internet so
insecure? Primarily,
because all of our software infrastructure is written in
safe languages. If we
I mean, if we'd written all
of our, you know, internet software
and operating systems in Haskell
or maybe in O'Cammel or ML,
99% of
all these exploits would be removed by
construction.
It's like we've built a
boat out of paperclips
and we're surprised that it's leaky.
I mean, you shouldn't build boats out of
paper clips, right, because they have holes in them.
You should build it out of a secure
substance, right? But then it's
too late. So we spend
incredible amounts of human ingenuity
and effort patching the holes
in our boat built a paper clips.
It's tragic. It's
tragic. How much effort
and ingenuity and money has been
lost and waste of resources
just because we wrote our
computational
infrastructure for the world
in an insecure language. That's what I mean by
unsafe. I mean, are there
not vulnerabilities? Like if I wrote something
in Haskell, I mean,
There's got to be some new type of...
Of course there are vulnerabilities.
I just said 99%.
I didn't say 100.
If I write a Haskell program that says,
receive message, if the message says,
tell me everything, then spit out my entire database in reply.
No language can stop you doing that, right?
But if you look at the program and it doesn't have any such things, right?
So, I mean, so, you know, nothing can prevent you against high-level attack.
to insecure programs.
Or, I mean, another example might be deadlock, right?
Two services, no matter how securely written,
if A waits for B and B waits for A, deadlock.
Sorry, no, and no language is going to stop you doing that.
You might hope for some high-level verification tools.
But it's like, surely if you're trying to do something hard,
like prove that that doesn't happen,
you want to have a foundation that is, you know,
in which you've got some bedrock to stay.
stand on. If you're standing on sand and trying to prove some advanced property, it's very,
very difficult. But good point. I'm not talking about 100% security. Absolutely not.
But how many exploits are based on buffer overruns or, you know, point of manipulation
that's gone wrong? If you couldn't have a buffer overrun, you couldn't do point of
manipulation that got wrong, those those exploits just wouldn't exist. So, I mean, C's maybe half a
century old now. What about more modern versions of those lower level languages like Rust?
Much, much, much, much better, right? If we rewrote all of our software infrastructure and Rust,
things would be way, way better. I'm not actually even sure whether Rust has a raybounds
checks built in, but it must have the ability to promise that you're not indexing out about.
I don't quite now, quite now. But if you compile a load code with that switched on,
you are way better situation, way better.
So, yes, this is not just functional programming,
but you did ask about why I thought C was in insecure, unsafe language.
If we imagine that graph, there's that the upper right quadrant,
which is useful and safe.
And you described that as Nirvana in the video.
And I guess maybe over time, I mean, C's somewhat really unsafe, really useful.
Yes, so C, I mean, Rust has moved a lot.
along the axis from useful but very unsafe to stay useful and become safer.
Now Haskell started life as being very safe but useless.
So it's worth just rehearsing that because in the first version of Haskell, there was no I.
The only that a Haskell program was a function of type string to string.
It's functional programming after all.
It could do NIO.
All it could do was take a string and produce a string.
So obviously that's not very useful.
it's a little bit more useful.
A language it is very, very safe
and completely useless is
no-ob. There does nothing ever.
Very safe,
but very useful. Haskell was a bit better,
right? At least it lets you
apply a function to a string
and get back another string.
So of course we then worked on
how could we do I.O. in Haskell in a safe way.
That will lead to, you know,
monads and stuff. But, but, so
just as the, you know,
we're moving from C,
horizontally to go safer and safer, but staying useful.
So from Haskell, we're moving sort of vertically to get more and more useful without stopping being safe.
And Nirvana is when we sort of meet up, right?
But the point is not to say one is better than the other, but just to say that we both seek
things that are useful and safe.
When I think of functional languages, I guess in the mainstream or what people are
kind of thinking about, I hear Haskell.
I also hear OCamill.
How do these two programming languages compare
if someone was trying to pick a functional language to work with?
So OCamill is a strict language that it's called by value.
So when you say F applied to 3 plus 4, it'll add 3 plus 4 and then call F.
Forget something right.
In Askul, if you say F of 3 plus 4, it'll build a little suspension
that says, well, if you ever need this 3 plus 4, you can evaluate it,
but maybe you'll never need it, and then it calls F.
That's a lazy language.
Now, so because O'Call is strict, it has a defined order of evaluation.
So if you say F of 3 plus 4 and a second parameter 8 plus 9, it'll evaluate 3 plus 4 and then 8 plus 9 in that order.
So it makes sense to say, you can say F of print hello, comma, print goodbye, right?
Because you get hello and then goodbye printed.
So once you have a defined order of evaluation, then it's pretty easy.
you to incorporate I.O.
Even though it quotes
a functional language,
O'Camel allows you to
do IO without
saying unsafe perform IO or anything.
In fact, it has a defined
order of evaluation and it's perfectly kosher
to do that.
As a sort of unintended
consequence of being called by value, it was
also, by default, impure.
Now, good O'Camel programmers
won't use many side effects, but
O'Camel doesn't prevent you.
Haskell prevents you.
Why does it prevent you?
Because if we'd allowed you to say F of print hello, comma, print goodbye,
those would be thunks, like, not yet evaluated.
Right?
So if F happened to evaluate its first argument and then it's second,
we'd print hello and then goodbye.
If it evaluated its second and then it's first,
we'd print goodbye and then hello.
If it didn't evaluate either, we wouldn't print either of them.
That doesn't sound very good if you want to control what I always happen.
right, laziness forced Haskell to stay pure, strictness allowed O'Camel, which grew out of the ML tradition, by the way.
ML is another functional language that predated Haskell.
It was always strict and it always had I.O. by default.
It wasn't a goal, but that's just the way it worked out.
At the time, nobody thought about it.
It was just obvious.
So that's a big difference between O'Camel and Haskell, right?
is that
now then,
since then,
they've both grown up.
Haskell's gained
sort of monads.
O'Cammels gained
lots of fancy type systems.
Some of them,
you know Haskell and how
O'Chamel have lurked from each other.
O'Chamel has grown
an interesting effect system recently
and a whole lot of new extensions.
It's an absolute hotbed
of innovation at the moment,
Ocammel.
So I view O'Cammel
as kind of siblings,
brothers and sisters.
We love each other.
We learn from each other.
We compete with each other, all of the things that siblings do.
But they're not the same, right?
So siblings don't say, I'm just better than you.
You shouldn't exist.
We say, let's enjoy our life together.
And that's the way it is.
You describe the laziness and the strict.
And as a programmer, my initial thought is I would like to control the order of execution.
And when I say print X and then print Y, I wanted to have that order.
otherwise it would be a little unintuitive.
What is the main benefit of laziness?
Why is laziness good?
Well, John Hughes did this rather well.
Way ago, 1980-something, he wrote a program called
Why Functional Programming Matters.
And one of its main thesis was that lazy evaluation
lets you compose programs in a particularly modular way.
So imagine a program that is,
I mean, his classic example,
is a program that plays chess.
Right? So one thing you could do is you could imagine building a tree of all possible moves starting from the position we're at.
It would be a very, very big tree.
In O'Camel, it would be too big because first of all I compute the tree and then I'd start deciding what move to do.
It couldn't possibly do that because the tree of all possible moves, you know, has more nodes in it than the number of protons in the universe.
So let's not do that.
Now, if instead you could build the tree, oh, having got this tree,
supposing you had it, you could then walk over the tree saying,
oh, let me do some minimaxing and saying,
oh, this looks like a good position, let me go this way.
I could explore the tree, right?
Now then, so in O'Kammel, then,
you would have to put the tree generation
and the tree exploration in one function.
In Askell, with lazy evaluation,
you can generate an infinite tree,
and then the explorer that prunes the tree
and explores just the bits of it that is necessary,
is completely modularly separated.
I can build a different generator and a different pruna.
They're just completely separate programs.
So lazy evaluation is a very powerful glue
that lets you glue together
to programs that you'd like to be distinct.
Strict evaluation forces you to merge them together.
Yeah, this reminds me.
I mean, so in Python, for instance,
they have the idea of a generator where you could...
lazily retrieve things.
Every strict language, every, you know, serious strict language, has lazy evaluation in it.
They're called iterators or generators.
And so does O'Camel.
But in Haskell, that's the default.
Now, instead, in Haskell, so this is a bit like, again, the two are converging on the middle, right?
Haskell, lazy by default, but you can make things strict.
You can put in exclamation marks to say, please evaluate this before the call.
or the IOMO now to say value
but that's a more brutal strict
strict amortation so in Ascle you can make things stricter
in O'Connell you can make things lazier
right so it really boils down to
is what's the default
we both want to have a mixture of the two
and then it becomes a bit cultural as for which you prefer
I don't know
some people sometimes ask me they say
well if you were designing Haskell again
would you make it strict
by default
with really good support
for laziness
and I often say
well I might
yeah it does seem attractive
because frequently
I found myself
cursing laziness
as an implementer
but I strongly suspect
that 10 years after that
I'd be thinking
man if only it was lazy by default
oh when I say strict by default
I would all definitely mean
strict but pure
right no side effects
a few times
in our conversation
you've mentioned the word monad,
and it seems like it allows us to do the side effects.
What is a monad?
And how does it help us do side effects and preserve ordering?
One way to think about it that's very easy to understand and use
is just to imagine that the do notation is somehow built into Haskell.
So you can say, do print X, semicolon, print Y.
and that has type i.o unit.
So a value of type io unit means I do some input output and return a value of type unit.
An expression of type i.o-int is an expression that when you run it, we'll do some i.O.
and return an int.
Okay?
So do lets you combine i.o performing computations together.
So, print 3 has type I.O. unit.
It does some I.O., namely printing 3.
Get char has type I.O. Char.
It does some I.O., namely, reading a character from standard input and returns a character.
All right?
Notice that's different from just char.
So, you know, quotes X, quote, that has type char.
It's just the character pure, right?
Get char, the I.
The I.O. Performing operation has type I.O. chart does some input output and returns a character.
Okay. The do notation lets you combine together I.O. performing computations.
So if you could say do, and then you say X left arrow get char, semicolon, put char X.
then that X left arrow get char that says run the get char computation
and get me the character call it x
the put char x says run the put char x
and we combine them together with the do notation
that combines two computations to make one i.o performing computation
right so but these things are completely first class
that's what's new about monads compared to just make it into into c
X left arrow get char semicolon put char x.
That's a computation which type is iow unit.
Let me give it a name.
So I can say let foo with type arrow unit equals that do.
Now I can pass foo as an argument to something.
I could put foo in a data structure.
I could return foo as a result.
It's a value just as much as three or plus.
In particular, for example, I could say do,
Fou, semicolon, foe.
That takes foo, uses it twice.
Each time, do fu semicolon, foe says, do foo, and then do foo again.
Right?
So I'll read a character and print it, and then read a character and put it again.
So these values of type IOT, for some type T, are first class values.
All right.
in C, I can't take X-colon equals 3 semicolon, you know, Y plus 4, and pass that as an argument to something and expect it to happen wherever it's used, right?
It's not a first class value.
When I first was learning this monad idea, it seems like you've said in a few places, it's a way to do the side effects but keeps the language pure.
But when I saw it, my first thought was it's almost like this little.
you know, place where we segregate the dirty things we want to do, I guess.
But then how does that keep the language pure?
Oh, because, because.
Well, yeah, we are lying it.
But look, if your function has type int arrow int, can it do any I.O.?
No.
No.
If it has typed int to I.O.int, it could do arbitrary I.O.
So yes.
So it's nice, pure int to int-to-int function or dirty into-ink function.
So you may say, perhaps you're about to say, that's a bit of a blunt instrument.
Either completely pure or completely dirty, right?
It gets you an awfully long way.
But nevertheless, it would be cool if you could say,
oh, I am an int to effective doing reading of files only, int.
So you'd like to, in the type, you'd like to say,
what kind of effects can it have?
Can it throw exceptions?
Can it, you know, spawn new threads?
So you'd like to enumerate the effects this computation could have, right?
That will be cool.
That's called an effect system.
And there's, you know, bazillion programming language papers about effect systems.
And it turns out that you can indeed, in the type system of Haskell,
and indeed, O'Connell is rapidly becoming safe.
You can express not just all or nothing, does it do IO game over,
but rather which particular effects does it do,
including no effects at all. That's pure.
A good place to start is a library called Bluffin, which my colleague Tom Ellis has designed.
It's a very nice take on how to do effect systems in Haskell.
I guess the purity comes from that this,
this dirty stuff is signaled through the type system.
Correct.
Correct.
So we can do it, but here it is and be aware.
Yep.
And because you have to thread, you know,
because the type system gets in your face,
you know, you were trying to, like Map, say.
No.
Map says, I apply a function to every element of the list.
Well, so it has type A to B to list of A to list to B.
if you want to apply an I.O. performing function,
so the function you're applying has type like it to I.O. of char.
You could map that over a list, but you just get a list of I.O. of chars.
That hasn't done any I.O. yet, right?
You want something that says, take a list of I.O. Chars and perform those actions one at a time.
So I want a function that goes from type list of I.O. char to I.O. of list of char.
right and you might want to perform all those actions
top to bottom or maybe bottom to top
who knows that's what this
function of type you know so yes so you could do it in various ways
so sometimes it gets in the way right you say oh shit
you know can't I just use map
well in Askell no sorry
you're going to have to do a little bit more work to tell me
in what sequence you want your effects to happen
because by default Haskell does not specify sequence
So, you know, adding monads to control effects does get in your face a bit.
And that's a tax we pay.
In effect, that's part of the big experiment that Askell is doing is to say,
suppose we up front say we're willing to pay that tax.
You know, how many followers can we get?
And the more, but I will note that monads have infected quite a lot of other languages.
like F-sharp is definitely called by value in pure language,
and yet F-sharp had these workflows that were definitely monads, right?
And monads have appeared in Scalo and in many other languages.
So something that's very monad-like has appeared lots elsewhere.
It's been a very unifying concept.
Open AI, Anthropic, Cursor, and Versal all use this product to make their lives better.
And the problem it solves is when you're building SaaS or an AI product
and you want to sell to other companies,
there's all these requirements you need to meet.
There's SSO, there's SCM, there's RBAC, there's audit logs.
These are all things that take time to integrate,
but aren't the main focus of your app.
WorkOS is an API layer that lets you meet all of these requirements
in just a few lines of code.
So let's say you have a new SaaS product
and you want to sell to other companies.
WorkOS will solve all of these critical feature gaps for you.
You can check them out at WorkOS.com
learn more and get started. And I appreciate them for supporting my work and sponsoring this podcast.
When I read about Haskell and people's perception of Haskell, one thing that keeps coming up is
that the type system is really powerful. And so I kind of want to ask you, maybe just even on the
highest level, what is a type system in your words? Okay. So what does a type system do? It lets you
reject silly programs up front.
So if I have a function that adds one to things
and I apply it to a character
or to an I.O. computation,
I'd like to, that's going to fail at runtime.
Because I can't add one to a character.
Or maybe you overload plus, maybe I reverse a list or something.
If I give, if I go to a list reverse function,
I give it something that just isn't a list.
then it's a bit silly to allow that and only fail at runtime.
So fundamentally, type systems are about rejecting at compile time,
programs that you do not want to run because they will fail at runtime.
Okay.
Now, we've had static type systems for a long time,
like going back to Pascal and earlier.
But simple type systems are annoying.
because they get in the way.
Imagine a function that reverses a list.
In Pascal, you could write a function
that reverses a list of integers,
but if you wanted to reverse a list of characters,
sorry, you can't,
that function that reverses a list of integers,
it has type list of int to list of int,
so you can't apply it to a list of characters.
Game over.
You have to write another copy of the code
with a different type.
That's a bit annoying.
So, what are we going to do?
We need polymorphism.
We need a more powerful type system.
We want to give reverse the type for all A,
list of A to list of A.
So that up front says, I work for any type A.
You give me a list of integers?
Fine.
I'll produce a list of integers.
You give me a list of characters.
Fine.
I'll produce a list of characters.
Notice that's much better than just list to list.
I want to keep the fact there's a list of integers.
So I know if I apply, you know, when I look inside the list,
I know I've got an integer.
But also, if I just list the list, I might get back a list of some completely different type,
but I know that reverse returns a list with the same type of things.
So, parametric polymorphism, super valuable.
If you want a psychic type system, you must have parametric polymorphism.
The message is, if your type system is too simple, it gets in the way.
Very important lesson, because it means that we cannot really say is a type system.
system useful or not, unless we say which one?
Because we know that weak type systems are very inconvenient.
It must at least have parametric polymorphism.
And that is another idea that was born in the world of functional programming.
It was born in ML, incidentally, Robin Milner's famous dictum,
World Type Programs, don't go wrong.
ML was the first, I think, the first parametarily polymorphic functional programming language.
But generics in Java and object-oriented programming more generally is exactly the same idea.
Right. So there's an idea that was born in functional programming and made its way into the mainstream.
And you mentioned polymorphism. So there's this parametric polymorphism in the type system.
But in the example you mentioned where maybe you want to do an operation on a list and then the type of the input changes.
I was just thinking, what about when people do polymorphism in the classes?
Okay. So now you're into a whole more complicated world, right?
So as soon as you say class, you're talking static type system again, right?
So object-oriented programming is another approach to polymorphism.
So in an object-oriented language, we say if we have a Ford that is a car and a car is a vehicle,
then anything that works on vehicles should also work on cars and should also work on fords.
So the code that we write for cars works unchanged.
for vehicles and for for for for for.
Okay.
Now, that's a form of polymorphism,
not parametric polymorphism.
That's what you might call object-oriented problem,
or subtype polymorphism.
Okay, so now we, but polymorphism in the sense that,
the same code, the same executable code,
actually the same machine instructions,
work on values of different types.
Okay, in both cases.
Both reverse a list, same machine instructions.
Code that works on vehicles, works on things on Ford, same machine instructions, right?
Okay.
That's what polymorphism in general means.
Parametric polymorphism means this for all A, list of A to list of A stuff.
Subtype polymorphism means, if it works on vehicles, it works on any subtype of vehicles.
Okay?
Now, the interaction of the two, which you get by adding generics to an object-oriented language,
is pretty complicated.
Oh, and by the way, adding side effects as well.
And that's why you're...
And so your question involving classes and superclasses and so forth
is smack in that complicated world.
And I'm not sure it'll be very fruitful for us to...
We'd have to get a lot more details of the type system sorted out
to know what to say.
But at this very high-level overview,
the big point I'm trying to make is
weak type systems
get in the way.
Type systems are meant to reject programs
that will go wrong.
But my function
that reverses a list
of integers,
it will also reverse
a list of characters.
So it's tiresome to be told
no, that is a bad program.
I want to be able to write
that it has that for lay,
list of A to list of A.
So the idea
of making the type system
more complicated
is to say
programs that you want to run,
you can still write
in your static type system.
Now, you might say
well, blimey, if that's all, why don't we just get rid of the static type system altogether?
Now we could run all of those programs.
But the trouble is you can run too many programs now.
You can run programs that will crash at runtime.
And that's very, very, very bad.
And we all know the costs of programs that crash in deployment that you could have crashed
before you even started to run them, before you even ran your first test.
Right?
Before you even linked it into an executable, that's really.
good. But the biggest benefit
of a static type system, in my humble opinion,
is maintainability.
If you have a program written in,
I don't know, Pearl or Ruby
or LISP in its
basic form,
and it was written 15 years ago
and the original author has left
and all of the people who were involved at the time
it was written have left,
then that program is very difficult to maintain.
And it becomes almost immutable
Nobody dares change it anymore.
What they do is it's an immutable piece of software
and you do that stuff around the edges
to impedance match what you really want to do
to this now immutable blob.
So of course, you have lots of tests.
So test your development,
I'm totally with it, I've done development, all of that.
But still, GHC, for example,
is itself written in Ascot.
It's 35 years old.
And yet I do large scale, systematic refactings of it,
you know, fearlessly.
because the type system keeps me safe.
In fact, often what I'll do is I'll change a few types
and then start compiling.
And then a sort of wave of changes propagate through
forced by, you know, I just get type errors,
so I know what to do.
Whereas the thought that I've changed the representation
of this data structure a little bit,
but I've added a field to this data structure,
where in the entire compiler
might that field be read, written, or freshly allocated?
I can't imagine how anybody maintains 30-year-old software
and makes large-scale changes like that
without a type system.
It's unimaginable.
So for me,
the benefit of type systems
is maintainability.
Oh, and designability, right?
So a type,
I often write the types
of my programs up front.
I write the type,
you know,
the data types are super perspicuous.
You know, if I say,
it's a bit like writing
the classes of an orbit,
of an orbit,
obviously language, right?
But if you have no types,
no classes, nothing,
just, I don't know,
S expressions.
Types are my design language.
They're the way that I start writing my designs.
I'm trying to understand other mindset.
Like, let's say C, for instance,
where I remember a lot of stuff
when I was learning in in college, for instance,
many times where I'd add a character to a pointer or something,
and it just works because it interprets the character as a number.
So is there any value to having that,
kind of type system or is that just strictly unredeemable?
Just you're stronger.
I think there's no benefit to weaker.
Just off the top of my head, one thing I think of is there is a set of programs that
will work but don't satisfy the type system.
Yeah, that's why.
But those are, I mean, I don't know if they're good.
That's subjective.
Oh, no, they may be good.
So, so, so, so that like us, like we start.
If you have Pascal and you write a function to reverse a list of integers, then if you apply it to a list of characters, the same machine instructions would work, but it is rejected.
So we have rejected a perfectly decent program, bad.
Our goal is to expand the collection of the programs that satisfy the type system, to include as many as possible of the programs we want to run without including any of the bad programs.
that we don't want to run.
Okay?
Parametric polymorphism is a big step in that direction.
Other type system innovations are a big step in that direction.
But there will always be some programs that would run perfectly well
that the type system rejects.
Imagine a tree that contains integers at every node.
But if you are 17 deep in the tree or 34 or 51, if you're in a multiple or 17 deep, the integers can be characters instead.
Or the integers all turn out to be characters, right?
Now, you could write a program that generated such trees and you could write a program that consumed such trees, knowing that every 17th layer we switch to characters.
But most static type systems would make it pretty hard for you to accept that program.
And yet it will run.
Now, you might say, oh, but I really want to write that program, guys, you know, don't get in my way.
Well, then we should provide a way for you to bail out into dynamic typing.
Right?
What I'd like to do is to say, okay, so if all else fails, then at least you can, as it were, pair up a value with its type representation.
was one reason we don't want to interpret an integer as a double-position float, for example,
is that they don't even have the same representation, which is just nonsense.
One possible way, which in untyped languages let you do,
is to tag every integer and every double-position float with the fact,
I'm an integer, I'm a double-position float, but that has a lot of overhead.
So one merit, I think it's not the biggest single-barat,
but it is a major merit of static systems, is you have no tags.
you know that if it says it's an integer, it's going to be an integer.
You know that if it's a double position float, it's going to be double position float.
But if you're not sure, maybe we could make a way to make a pair of a type representation and this value.
The type representation is now like a runtime tag.
It's like a little runtime data structure that describes the type.
And then in your program, you could say, now I want to say, oh, I've got this type dynamic.
We'll call this pair a value of type dynamic.
Now, when I want to take a value of type dynamic
and treat it as a character,
I want to say, ah, look, look at the type dynamic,
see if it says it's a character,
if it is, return the character.
If not, crash, like runtime failure.
That's fine, you can do that.
And Haskell has good support for dynamic typing where necessary.
So my story would be,
static typing should expand to carry as much as possible
and where you absolutely cannot do it,
then use dynamic typing
and we provide facilities to support that.
One thing I wanted to talk with you about
is the compiler.
How does the GHC work on a high level?
So GHC takes a string like any other compiler,
the source code of the program,
parses it,
and then it type checks it,
because is this a type correct program?
Then it converts,
converts it to LAMPT calculus.
Now, the HUSCOL, the original source tree,
has 50 different data types,
50 different kinds of nodes,
some of which have 30 or 40 different variants.
So it's a really big, diverse, complicated data structure,
the AST.
Lambda calculus has this variant of the Lambda calculus has eight.
One of the data, or maybe two or three data taps
with eight constructors.
So it's like taking a gigantic language and squeezing it down into a tiny one.
And that tiny one we can then optimize.
That's the optimizer works on that.
So the front end does pars, rename, type check, D sugar into Lambda calculus.
That's the front end.
The Lambda calculus, particular language, is called GHC's core language.
I'm quite proud of it, because it's been very, very stable.
it's 35 years old and it has barely changed since birth.
That's amazing, right?
Because Haskell has changed a lot, a lot.
So almost all of the innovation in Haskell has been in the front end.
Very little in core.
Now, the back end, the core optimizer, has changed a lot too.
But all of the changes that were useful there would have been useful 30 years ago, right?
So there are two completely separable things.
So I'm quite proud about that.
Core, then we do a lot of core to core passes.
That simply take core programs, we'll use core programs, right?
Lots and lots, long pipeline.
Then we convert it to C-minus-minus,
which is a prototypical imperative language.
Think of it as a portable assembly code.
Right?
So that bit is meant to be platform-independent.
So it's simply, that's the compiler that take...
Do you remember I said if you take Lambda calculator?
and compile it, that's that step, right?
I want to compile the Lambda calculus
into machine instructions, really,
but I don't really mean machine,
I mean portable machine instructions.
That's called C minus minus.
Then I want to convert C minus minus
into actual machine instructions for various platforms.
And there we could either do that directly
with an native code backend or go via LLVM.
Interesting.
I've never heard of C minus minus.
Why not just go directly to,
I would have thought, maybe assembly or something like that?
Well, because what happens, assembly for which processor, please.
Oh, I see.
I guess it's the, I would have thought the Lambda calculus part was already portable.
It is, yeah.
You could go straight, but, but, so there's work to go from Lambda calculus.
You could go all the way to X-86.
Then throw all that away and now go from Lambda calculus to PowerPC.
Oh, dear, I've just duplicated a lot of work.
by going from Lambda calculus to C minus minus
and then from C minus minus to X80s, C minus minus,
we've avoided duplicating
the work that went from Lambda calculus to C minus minus, right?
When you see that, that is pretty obvious, isn't it?
You want to make the platform-specific bit
as small as possible.
You would like to have, as it were, like a generic architecture,
one that can do addition and as a program counter
and a stack and so forth.
That's all C minus minuses.
It's just a portable assembly language.
And then you say,
oh, the nitty gritty of, you know,
whether you have double precision add
and set the floating point bid here and there.
That, well, that's platform specific.
Core is itself statically typed.
You know, but in some ways it's surprising
because no other compiler has this property,
no other production compiler.
By core is statically typed.
I don't just mean that the initial program
was typed correct.
I mean that a core program,
you can run a type checker on that.
I might say, why do you need to?
But after all, if GHC is correct, it started with a type correct call program,
because it came from a type correct Haskell program, assuming the D Sugarer was right,
and all the optimizations, if they're right, will generate a type correct call program.
So why do you need to type check her?
Answer, to discover bugs in GHC.
Now, these are serious bugs, right?
If you ever take a type correct call program and an optimization pass produces a type incorrect call program,
what will happen if we don't have the type checker for call
we'll generate machine code and we'll run it and we'll get a segfault
now we have to backtrack from a particular test program that now crashes
all the way to back up the pipeline up the pipeline up the pipeline up the pipeline up the pipeline
oh it was this pass of ghc that was faulty
that's super hard to do because you know you're getting out gdb on some runtime failure
that is an indirect and perhaps distant consequence
of the fact you just generated a type
you just made a bug in the optimizer.
So it is amazing to have a type checker for call.
Now, why does nobody else do this?
Well, it's because their intermediate language typically,
and this I really am talking typically,
because I know of no other compiler that has this property,
none, production compiler.
Typically then, you know, complex syntax.
trees decorated with all sorts of pragma information and things are hanging on it onto it here and there and
you know there's no there's no type checker for it at all and there's no hope of one so i'm very proud of the
fact that core is statically typed and i'm also also think it's a the most delightful thing is that the
way that it is statically typed it is an implementation of something called system f
So System F, when I said Lambda calculus,
Lambda calculus as Alonzo Church had it,
was untyped, had no typed system at all.
But Girard defines something called System F,
which is a statically typed Lambda calculus,
a rather powerful one.
And Core is essentially System F.
So we literally adopted something from the nerdy,
theoretical computer science,
you know, logic community,
logic of mathematics community,
and adopted it directly in a production,
implementation.
So I'm very proud of that.
I think core is, and the fact that we can do, we have 35 years worth of development
that has been not just not impeded, but actively aided by statically tapped into
medium language, which is really a big marker in the ground.
Watching all your talks, reading everything, one of the interesting data points that you
brought up was that Haskell is talked about more than used when you compared stackover
flow volume and actually GitHub volume, like who's actually using the programming language.
Why do you think that is?
So Haskell embodies one key idea.
Immutability changes everything.
There's a quote from Pat Helen's talk that I think you also paper, which I think you also
looked at already.
So it says programming with values changes everything about the way you think about programming.
It's just mind changing.
It's not necessarily better, but it's not necessarily better.
It is different.
And so Haskell takes that idea and runs with it.
Everything is driven by that one idea.
Everything else is incidental.
In the early days, that meant we just said, well, guys, suck it up.
We'll keep changing the language.
And if it breaks your program, it's too bad.
So it is a bit peculiar.
And therefore, also it felt a bit academic,
but initially it was really not very powerful.
It was taking the key idea.
But you couldn't do very much with it.
We talked about that, right?
So over time
GHC and Haskill in general
has become more and more powerful.
The type system has become less and less in your way
and more and more useful.
All the obstacles that make functional programming harder
become better. The compiler generates faster
code, it allows faster and so forth.
So
it has become
less, if you like, peculiar.
So we've become more and more
taking into account the
needs of our users.
But in a way, the sort of
cultural heritage is we never
give up on the one core principle.
We're just not going to give you unrestricted
side effects. Sorry.
You want to say unvoiced before my own
and put up with the consequences. So
we're going to stick to one core principle
and then we'll do lots of work around the evidence
to make that better. Right. So
that does limit our community somewhat.
It does mean you have to
you really have to think in a different way.
Immutability changes everything. That means you'd have to think
a different way about programming.
Maybe you don't want to think in a different way. That's fine.
then don't use Haskell, right?
So in a way, we've started from a very small user community,
a very sort of pure and nerdy one,
and growing larger and larger, but all slowly,
but all the time maintaining faithfulness to this core principle.
I thought that was very unique about Haskell
because I feel a lot of the other programming languages are user-centric.
I mean, if people want something, they work on it, they add it,
Whereas Haskell feels more principled, or it's all starting from these ideas.
And if you don't satisfy these ideas as a user, that's fine.
For instance, in one of your talks you mentioned somewhere that there was a release of the compiler
where if a file wasn't type correct, then the compiler would delete the file.
Oh, it reported the error message first.
Yeah, I reports there
but I thought that was
absurd. I mean, very
hostile I guess to the
I mean, well, it's
if you're type safe, no problems
but. Oh, it was a mistake
right, it was a bug. It wasn't
deliberate.
And it only happened, you know, how did the bug
get out? Because of course, if it always
did that, we'd have noticed.
How did it get into a release? Well, it was
only on Windows and only when you compile
a module that was not in the current
directory. But in that stage
our users were very forgiving
and somebody wrote to us and said,
well, by the way, Simon, you might like to know that
GHC does this, but hey, don't worry about it.
You know, I just copy all my files somewhere else
before I compile and then I copy them back.
So, of course, those days are long gone.
We pay a lot more attention to our users
and have much more rigorous CI testing than therefore we did.
Right. So that's a story from a long time ago,
but it's a good cultural story, but it suggests that
we care about our users very much,
but we care about users who in their hearts want to be principled.
We're trying to appeal to.
One of the things I like best about Haskell is people often say,
I just enjoy writing Askell.
It's fun, right?
My boss doesn't allow me to because, you know,
somehow doesn't fit with my production shop.
But for me, I would go for, I love writing this stuff every time, every time.
That's very rewarding to me.
There's also this interesting, I don't know if it's a cultural value, but it's a statement that you say often in the context of these older talks, that you say that you avoid success at all costs.
Could you explain what you mean by that phrase?
Oh, yeah, this was just a little play on words.
It was in a retrospector on Haskell.
I gave an invited talk at Popple in a long time ago, probably 20 years ago.
So it's a little play on words because you can read it as either avoid success at all costs.
And that's what we've been discussing.
Success at all costs means compromise your principles in order to satisfy your users or think that you're satisfying users.
Give them what they say they want.
Or if we build it, they will come kind of deal.
So avoid success at all costs.
Or if you parenthesis it the other way, it says avoid success at all costs.
and all costs avoid success.
And that's saying, that's a little joke, but it says,
if you're too successful and have too many users,
it becomes more difficult to make changes.
And we experience that right now.
So I devote many, many more of my personal cycles
to backward compatibility issues than ever I did.
I have devoted hundreds of hours and hours,
well, days and days, weeks and weeks in the last year or two,
to the following
what seems to be a very simple
property. If
you can compile a program,
a package in a whole program
with GHC 10.0 and we released
GHC 10.2, you should be able to
compile that same package unchanged
with GHC 10.2.
Seems reasonable, right?
After all, 10.2
should just be better.
But no, JTC has never
had that property.
And making it have that property is
turned out to be very, very time-consuming.
Previously, we just never cared.
Then we started to care but thought it was a lot of work,
and now we're investing the work.
I guess that was from a long time ago.
I think nowadays, software engineering,
there's been a major shift in the last year
where a lot of code is being generated by these models or these LLMs.
How do you see programming language design shifting to accommodate a world
where a lot of the code is no longer written by humans?
I think it may be the best thing that's happened to statically type languages for a long time.
Because, as we've been discussing, with a static type system, you cut down the space of programs that the LLLM can generate.
Because it is perfectly capable of running the compiler and saying, oh, darn, that was a bad program, better fix it.
So a zillion iterations get done behind the scenes.
was in an untyped language
the first one it coughed up
you'd have had to run or run against its test suite
or who knows what but it's
it drastically tightens up that cycle
right
so I think that
statically typed languages are a huge boon
for LLMs
because it's too easy to
programs are just strings right
we could
they could just generate
the next plausible word
and you want you want to make any
implausible programs programs that really shouldn't run
You want to make them not run right away.
If there's a slider on, I guess, the strength of a type system
and the other sides of the week,
what do you see is the absolute strongest type systems
among programming languages?
Oh, Askel, I think.
Askell is exploring the bleeding edge.
There is an exception, which is that module systems
are a, and in particular sort of functor-style module systems,
have explored much more detailed.
in the ML O'Camel world.
And in the Haskell world, we've essentially never gone there.
But otherwise, I think Haskell's right up there.
Now, of course, a language like Scala is also,
as though Scala has almost everything Askell has,
I think not quite,
but it also has subtyping and object orientation.
So that's a lot more complicated,
a lot more complicated, I think.
and they pay a price for it.
I think Martin Odescu would agree that they pay a price for it.
So in complexity, it's probably poor complicated than Haskell's.
And maybe in terms of power, so maybe I should have said to Haskell and Scala are the two leading.
Haskell, Scalo, O'Cabell.
Perhaps I'll just put them in an equivalence class for now.
They're not strictly comparable.
They all have things which they're more powerful than the other probably.
What do you think are the important problem?
to solve in the future of programming languages today?
Maybe, you know, what are the unsolved problems in the domain that are top of mind for you?
I think it's actually hard to identify, you know, to say, here's a problem we want to solve.
Let's try to solve it.
But I think another way to tackle is to say, what are interesting, you know, new languages out there that are exploring very different parts of the design space?
And there, I think I do have a candidate.
So the language that is my day job, right, I work for Epic, and we're designing a language.
a programming language called Verse.
Now, verse is a very exotic language.
It's really a functional logic language.
So it's yet more expressive than Haskell.
It has an athletic type system,
but a very different one to Haskell's.
So the way I think of it is like this,
if you look at C and Fortran,
they look pretty different if you're an imperative programmer.
But if you look at them, if you zoom out,
so you can see functional languages,
then C and Fortran are pretty close together.
you know, along with object-oriented languages,
they're all in a clump, right?
And then there's some functional languages,
you know, Haskell and ML, I know Camel and Scala out here.
If you zoom out still further,
then the imperative languages and functional languages
are all together and verse is way out here.
Right.
So versus exploring a very new point in the design space.
But just like functional programming back in 1980,
it seems sufficiently interesting and cool and unusual and weird
that it's worth exploring, right?
So back in 1980, nobody was said, we're definitely going to do functional programming and it's going to be useful practical applications.
They said, that's pretty weird.
You know, by all means, give it a try, guys.
And that's kind of what I am with verse.
One difference is that back in 1980, we were purely academics.
And now but verse is being developed by, well, epic games.
So we've got some, you know, much more muscle behind it than, you know, was behind functional programming to begin with.
So we'll see.
It's a very interesting intellectual endeavor.
adventure, I should say.
Yeah, I think a lot of people, like students,
they may be worried about AI or, you know,
studying computer science.
Would you recommend people learn how to program today
given that AI is starting to write reasonable code now?
Oh, yeah. Yeah.
So I think people have liked to be worried
in the sense that I think there's going to be
considerable dislocation.
Right.
If you were in the Industrial Revolution,
then lots of people lost their jobs
as spinners and weavers
and it wasn't easy for them
to get a new job in the new economy.
Now, the new economy had in the end
had more jobs,
but if you were one of the people
who just lost their job,
that was not a happy place to be.
From our perspective,
Olympian perspective of a few hundred years later,
we think, well, it's just a blip, right?
If you're part of the blip, problem.
So I think the right to be worried,
we don't know how things will shake out.
I'm actually optimistic that in the medium term,
if we don't destroy ourselves with some truly existential thing,
but from an employment market point of view,
I'm optimistic that in the end, you know,
we'll just be in a higher place,
that AIs will just be a bigger power tool.
I mean, everyone, we like using compilers, right?
We don't like machine code anymore.
Compilers make us more productive.
Maybe LLMs can make us more productive.
That's what I hope.
I sort of believe modulo dislocation effects.
Now, should we teach children or even undergraduates how to program?
So I think still yes.
The reason is because one way to say it is co-pilots and need pilots.
I think co-pilot was quite a good title that Microsoft gave their tools,
because it encourages you to believe it's your partner, not your boss.
if LLM spit out a pile of goop
and we literally do not understand what it does
but we just try it and it kind of works.
That might be okay if we're just throwing up a quick visualization.
It might be less okay if the quick visualization
is going to drive our policy choices
about as a nation whether to go into lockdown because of COVID
or if this program is going to run my airplane
or train signaling system.
So now those are extreme ends of the spectrum.
from quick and dirty things,
it really doesn't matter if it doesn't work
when it kind of does a lot of the time.
Absolutely fine.
This is a 30-year code base
that's going to last a long time.
I really want to make sure that it's...
Like, putting new stuff into J.C,
if somebody sends me a pile of AI-generated code
to put into J-HC, I'm not going to put it in
unless I've reviewed it
or somebody's reviewed it
because in 10 years' time,
I'm going to want to change that code.
How do I even know what it does?
If it's simply a magic incantation
that somebody's done
that kind of worked on,
the test they did, but maybe won't work in deployment. That's no good. So I really want long-lived
maintainable code to be well reviewed. Sorry. And to do that, I need reviewers who can write code.
Let me mention one other perspective. If you think about what every child should know,
when I think about whatever you like a child should know about computing, I would include
binary and bits.
Just as for physics, I would include
atoms and molecules.
Now, it's not that in real life anybody
manipulates atoms or molecules, or takes
decisions which are based directly
on knowledge of atoms, but somehow
knowledge that all matter is made up
of atoms.
You know, constitutive a finite number of elements at
Atman's group, but that knowledge
underpins everything we understand about
the natural world. If you literally have
never been told that,
you are sort of inusculated, even as a citizen, let alone as a scientist.
So, if you literally do not know that everything has composed the bits,
that words and music and text and LLMs and everything, it's all just bits,
I think you're crippled.
So I want every child to learn, you know, it's like I want you to learn the bottom.
It's all bits, nothing else.
It's all just bits.
I even have a talk.
The talk is called Bits with Soul.
So easy to
Gret for. It's a talk I gave to an audience
that was not computer science audience at all.
It was a completely lay audience
ranging from 14-year-olds to professors of quantum mechanics.
Pretty difficult audience to address.
It was meant to be all about
codes and coding and bits.
So it tries to get at the essence of
why do I think it's important that every child,
every person, every human being
should understand something about
the computational universe that surrounds them
That is founded in bits.
Now, just to develop the analogy a bit further, I would then say,
and it also, I think I want them to also know about programming, programs.
I want them to know that computers fundamentally execute by following machine instructions blindly, right?
That is not magic, it's not hocus, focus, it's just remorseless and very dumb, right?
It's incredibly empowering then.
And also to learn the basics about how neural networks work.
In the same talk, I explain how a one neuron neural network works.
And that's enough.
Then it's actually true to say, not distorting the answer to say, chat GPT is just a trillion of those wired together.
And astonishingly, that very simple.
So all chat GPT is is a trillion floating point numbers and a lot of floating point arithmetic.
That helps you to make sense of a question like, can chat be chat.
HEDT have feelings.
Well, it gives you,
of course that's a philosophical question,
but informs your, you know, your discussion about it.
If you know that all it is,
is trillion floats and a lot of floating point arithmetic.
That's all.
Nothing more.
Anyway, sorry, long answered your question.
So, yes, basic programming, absolutely.
Yeah.
Becoming very skilled in how to use, you know,
web Django framework 2.7,
maybe not so much.
Maybe.
I think one thing that LMs are very good at
is knowing the arcane and complicated APIs
that many of these frameworks present.
There's just 10,000 functions you've got to know.
And if an LLM is really good at knowing that,
I don't want to learn.
You mentioned somewhere, someone asked you,
what's your favorite programming language?
And obviously Haskell's got to be number one.
Yeah.
But for your second favorite programming language,
you said it was Excel.
At that time, why was Excel your favorite programming language?
Oh, because it's the world's most widely used functional programming language.
The formula language is a functional language, isn't it?
Doesn't have any side effects?
You program entirely with values.
So the formula language of Excel is a functional programming language, a very weak one.
It doesn't even let you define new functions.
And it has a very limited collection of data types, namely just, you know,
flat arrays and numbers and strings.
So, when I was working for Microsoft,
I took it as my, what's the word,
Walcry, to say, let's take that idea.
Excel is the world's most widely used functional language
by three orders of magnitude,
and, oh, is the word most widely used programming language
by three orders of magnitude?
Not just functional language.
Excel is used by many, many more programmers, users,
domain experts, than any imperative language.
Right?
Imperative language is a few million users.
Excel, hundreds of millions of users, right?
Even if you just restricted users who are using formulae.
So how can we delight those users?
Answer, take ideas from functional programming
and use them to make Excel's formula language more powerful.
It took me 20 years, but it
Excel did finally add Lambda to Excel.
Look it up.
There are blog posts about it and many YouTube videos about it.
So you can now program in Excel using full Lambda as a Lodzo Church originally defined it.
You can write anything like because Lambda is computationally complete.
You can write any computation in Excel now.
It would be a bit slow, but you can.
And much more practically, you can take formulae that previously,
that previously you just copy-pasted here and there,
and you wanted to make reusable,
wrap them up in the Lambda,
and now you can just call the Lambda.
And now it is a proper grown-up functional language
that is too incomplete.
Just search for Lambda Excel.
Those two keywords will get you lots of raw material.
Last question for you is,
knowing everything that you know now from your career,
if you could go back to when you just graduated from college
and give yourself some advice, what would you say?
all of these people who you see very successful wandering around looking as if they've made it
I guess you might class me among them now they are all of them just making it up as they go along
they feel insecure uncertain not sure what to do next not sure of what their next steps are
not sure of what next big problem are going to tackle unsure about whether they're what doing is
going to be successful or not and so all of their confidence is I mean they project confidence
maybe. That's partly a life skill
but often they're not.
And so the fact
that in those days
of course I felt very not confident, I would
say, you know,
since all of these successful
people are making it up as they go along,
it's fine for you to be as well.
And they've been
lucky, moreover. They've been lucky.
They've had
but if you want to be lucky,
you do need to put yourself
in a position where
accidents can happen to you.
And that means taking risks.
So if you want to be lucky, you need to be in a put yourself in positions where lucky things
could happen.
And that means taking some kind of risk.
So if you're very conservative, never take any risk.
It's very unlikely that the accident that is life transforming will happen.
That's a balance, of course.
But it means accidents are not so bad.
And of course, I'm dying as bad, but lots of action.
accidents may change your life in a way that might be surprising to you, but turns out to be not so bad in
retrospect.
Awesome.
Well, yeah, thank you so much for your time.
I really appreciate it, Professor Jones.
We will and last talking to you.
Thanks, Ryan.
Hey, thank you for watching this podcast.
If you liked it and you want to see the show grow, please support with a comment or a like.
Also, if you have any recommendations for people you want me to bring on, please drop a comment.
And guests like Barbara Liskov, Mike Stonebreaker, Mark Brooker, these were all people that I brought on because someone left a comment.
On another note, aside from the podcast, I'm working on building the ergonomic keyboard that I wish existed.
Here's a glance at the prototype.
It's a split keyboard.
So there's two sides.
This is in the case.
But yeah, we launched on Kickstarter and we hit our goal within eight hours of launching.
I really appreciate it if you were one of the people who grabbed one of the early units.
We're now working on the long journey of building the tooling now.
And so if you still want to pick one up, I've left the late pledges open on Kickstarter.
So you can grab one there.
I'll put a link in the description.
Thank you again for watching the podcast.
And I'll see you in the next episode.
