CORECURSIVE #027

Abstraction and Learning

With Runar Bjarnason

Abstraction and Learning

Rúnar Bjarnason

What is abstraction?  Can we have a precise definition of abstraction that, once understood, makes writing software simpler?

Runar has thought a lot about abstraction and how we can choose the proper level for the software we write.

In this interview, he explains these concepts using examples from the real world. Examples include SQL, effectful computing and several others areas.

We also talk about how to learn and acquire the skills necessary to understand complex concepts.  Concepts like highly polymorphic code and category theory.

Runar also explains his latest project unison computing and how it uses the correct level of abstraction to rethink several foundation ideas in software development.

Transcript

Note: This podcast is designed to be heard. If you are able, we strongly encourage you to listen to the audio, which includes emphasis that’s not on the page

Introduction

Adam: Hello, this is Adam Gordon Bell. Join me as I learn about building software. This is called CoRecursive.

Runar: Yes, it’s a sort of mind-body integration thing. I think it’s just like learning to dance, or play the guitar or whatever, you can’t really know how to play the guitar abstractly, and no one can really just tell you how to play the guitar. You got to do it.

Adam: That was Runar Bjarnason. He co-wrote Functional Programming in Scala, a book that was very influential for me, and for a number of people I know. But we aren’t talking about that today. Actually go back to episode five for that interview. Today, we talk about abstruction and precision, and also the relationship between constraints and freedom. My take on Runer’s ideas is that when writing software, if you constrain yourself to the least powerful abstraction that will work, then you will have more freedom to compose things at a higher level.

Runer, I interviewed you probably more than a year ago for the podcast, and we talked a lot about the basics of functional programming, from your book Functional Programming in Scala, that I guess you co-wrote. And a little bit at the end, we started talking about abstraction. So thanks for coming back. And I was hoping we could dig in more on that topic.

Runar: Yeah, my pleasure. Absolutely.

What is Abstraction

Adam: So in broad sense, what is abstraction?

Runar: What is abstraction? Abstraction is a sort of mental process that you go through all the time, like it’s the essential function of the human mind. And it’s just like taking a bunch of things that we have observed or encountered or experienced in some way, and then grouping them by their similarities, but in a way that they’re different. Like grouping them by like a specific way in which they differ.

Adam: Do you have an example?

Runar: Do you have an example? Well, like for instance, size. We can have an idea of size, that’s an abstract idea, so we’ll have a bunch of things and we’ll note that they’re all different in some particular way, and we’ll call that way size. But with regard to this idea, we’re going to regard them as the same in every other respect. So is this way of constructing sort of a new entity in our minds, that we can manipulate and sort of have, and that we can give it a name, and that refers to this particular difference among things. And then we’re going to then basically regard those things as being the same in every other respect.

Adam: So it’s like, if I have like an apple and a car, when I talk about their size abstractly, I’m not worrying about their caloric value, but merely that an apple is smaller than a car?

Runar: Right? So they have different size, but in every other respect with regard to this particular idea of their size, they are incomparable, or the same. And you can start with a really imprecise idea. Like, you can just say, “Well, the Apple is small, and the car is big.” So they’re only two sizes, big and small. But then as we get more and more abstract ideas, we can start to be very precise about how we compare, for instance, sizes, or colors or shapes, or programs.

Adam: Yeah. You used the word precision there. Actually, maybe before we get to precision, so how does this apply to the world of actual software development or code.

Runar: So the way I’m sort of looking at this in my mind, is that we’re viewing the things that we observe out there in the real world. We’re sort of looking at them and sort of lining them all up in some way, in such a way that we sort of punch a hole through the whole stack of things that we’re integrating under some abstraction. And then we’re going to sort of fill that hole with some quantity or quality, like for instance, with size, we’re going to just line everything up so it has a size shaped hole in it, and then we’re going to fill that with either big, small, or like an integer or square meters, or whatever it is. And sort of viewing it as like a stack of things that with like a hole punched through it that we can then fill with some other idea.

And then the same kind of thing happens in programming when we abstract. That is we take a program, and we want the program to vary in some way. Like for instance, we might want to take the sum of the numbers one, two, and three, but that’s not very abstract. So we might want to be able to take the sum of any numbers, and so we sort of punch a hole in our program, and say, “Well, we’re going to actually take those things as an argument. We’re going to call the numbers like X, Y, and Z.” And so we have number shaped holes in our program that we’re then later going to fill with some values.

Variables

Adam: So like variables are a form of obstruction. Is that the example?

Runar: Yeah, exactly. So a variable, you’ll note, can’t be absent, you can’t ignore the fact that it needs to have some value. But it could be any value, it stands for absolutely any value of a particular type of value that fits that sort of hole in the shape.

Adam: It’s an interesting example. So like 1+2+3, or let’s just say 1+2 is like a concrete addition, but like X+Y is abstract.

Runar: Mm-hmm (affirmative).

Adam: And I think you said before that somehow that’s more precise? How is X+Y more precise?

Runar: Is that more precise? I think that it’s certainly more abstract.

Adam: Yes.

Runar: 1+2 is already pretty abstract because, like even the idea of like one and two, these are abstract ideas. Like we’re not specifying one of what, or two of what. So these are ideas that work universally for anything that we might want to consider one or two of, right?

Adam: Yeah, like just counting. We’re not counting a specific thing, but just the number itself.

Runar: Right. So that’s an example of where… So just at that level, is an example of when like an abstraction buys us precision. Like out in the world, when we’re like looking at the actual universe, it’s sort of undifferentiated, and it’s just this one big interconnected mess. But we can abstract out like two of something, like we can notice that there’s a pattern. Like, everyone has some configuration of eyes, or legs, or like there might be like a pair of sheep in the field and two stars in the sky or something. And they all have this same relationship, is that there’s like there’s two of them. So then we have this notion of two, and then from there we get all the natural numbers and arithmetic. And now we can start to be very precisely about adding things together and subdividing groups of things. So we can do very precise arithmetic now with these abstract quantities. Whereas, like with actual stars and sheep and eyes, we can’t really.

Adam: Yeah, and if we talking precisely about eyes, it’s sort of a symbol, like there is no universal eye, right? We’re referring to a whole bunch of different people’s eyes as like a combined concept?

Runar: Right. Yeah, exactly. So even there, that’s abstract.

Adam: So to make it more specific, I feel like I’m going to end up otherwise in a really philosophical place.

Runar: Sure.

Adam: I mean, maybe we will regardless, you had this example of actually writing printer code, and how abstraction was lacking or something. I was wondering if you could share that?

On Printing things

Runar: Oh, yeah. So in the talk I was talking about this time when, this is during the ’90s sometime, when somebody wanted to hire… Somebody hired me to write a program to make sales reports, and they had to be printed on this dot matrix printer that they had in their office, and they had their data in an SQL table. So I just took their paradox tables, or whatever it was, and I essentially just dumped them out onto the printer using very specialized code, using like the printer control codes. I wasn’t using any kind of abstraction, or anything like that. So talking very concretely to this particular printer. And then when the client comes back and says like, “Okay. Well, I want to manipulate these reports. Can I move this thing to over here?” For instance, or like, “Can we take two reports and put them side by side on a page or something?”

That becomes basically impossible without rewriting my whole program. Because the program isn’t able to talk about the location of things on the page, or whatever, it can only talk about like how the printer is being controlled in this particular instance. So I was missing the sort of abstract layer, it should have been talking about the reports, sort of in the abstract layout and other things like that, like even if I had known that PostScript existed, which I didn’t. So that would have afforded me the ability to talk precisely about where things are on the page and things like that.

Adam: So yeah, PostScript or PDF is like an abstraction that lets you specify like where things would go on a page without being specific to a printer?

Runar: Right. And so yeah, the point I was trying to make with that one was one of compositionality. So I didn’t have a compositional program, so it wasn’t able to deconstruct the program that I had written into smaller elements that I could then rearrange. It was all just one monolithic block of code.

Adam: So how does abstraction relate to composition? If you had written a more abstractly to PostScript, how is that more compositional? Or is it not?

Runar: Yeah. It certainly would have been more compositional, or like any kind of sort of algebraic abstraction, like where you can take things apart and then recombine them would have been better. I mean, will allow me to do what I wanted to do. And I mean, the way that relates is that you can, once things are very abstract, you can sort of manipulate them symbolically, according to some rules. So there will be some rules of composition or combination. For instance like if you have some kind of layout language, you might be able to say, like this thing appears above this other thing, or these two things appear side by side. Like that might be a rule of composition, but then the actual rendering of one element and the rendering of another element, if you combine those renderings together, you should get the same thing as if you are rendering the combined elements. So that makes sense?

Adam: Yeah. It’s almost like an intermediate representation, and then some sort of printer-specific interpreter or something?

Runar: Yeah. The thing that the abstraction buys you is to allow, again, this sort of sum, but any idea, that you can talk about. You have some printer, but it could be any printer, as long as it has a mapping from your abstraction down to how you actually control the printer.

Adam: Yeah. I have this quote from you, it says, “We should work symbolically and only go to a final representation at the latest possible time.” So this, I suppose, is an example of that. We would map things to this small language for representing layout, and then at the latest, like right when we, before we send it to the printer, we kind of interpret that into the printer codes?

Representing SQL as Strings

Runar: Right. Yeah. Because if you kind of think about it, there will be lots of different ways that you can combine things that are sort of in a very large language. And a favorite example of mine is like SQL and strings, and combining two strings together, even if both of those strings represent some SQL statement, there are going to be lots of different ways of combining strings, and most of those ways are not going to be valid SQL. So if you want to ensure that you have valid SQL at the end, you have to sort of recover the structure of the SQL and then sort of manipulate it in some way, and then recreate or sort of generate the combined string.

But if let’s say you had a symbolic representation of like the SQL syntax as a library, you could then just work using that library and combine your SQL in a way that’s legal, like in a way that is still legal SQL and then generate the string as the sort of last possible step. So you’re not like trying to do some kind of string matching or hacks.

Adam: Yeah. The interesting thing about that is almost necessarily there will be some SQL. If I’m using some intermediate form, that’s much more compositional, like almost necessarily there will be things like types of SQL I can’t do. So I’m limiting myself in using this intermediate form, would you say?

Runar: I’m not sure. I mean, if your intermediate form can represent… If that is like the syntactic specification for SQL, then no, there won’t be any legal SQL that you can’t generate. Maybe I’m misunderstanding your question.

Adam: No, I think that makes sense. Like SQL is large, so in practice, there’s things I may miss. But yeah, if I were able to specify… Like the perfect case is that I have this intermediate representation that lets me compositionally form any possible SQL that I want to do, where I think what you’re saying is that if you have a string representation, that is true, right? You can have any valid SQL, will fit into that. But so will like an infinite amount of things that are not SQL.

Runar: Yeah. Exactly. So it is true that like you can have any combination of valid SQL in your string, you can have lots and lots of junk. But if you have a precise specification, if you have an abstract representation of SQL, then you can see where precision comes in here. You can have precisely SQL and nothing else.

Adam: Yeah.

Runar: So in a way, precision separates things that are sort of distinct, so that we prevent confusion. So it separates the things that we don’t want to have confused. So it avoids additional noise so that there are no unnecessary elements.

Adam: Yeah, so it is. To use some of your language from before, like it is a constraint on what you can do, right? A string can hold more things but the representation that can only represent SQL is much more precise.

Precision of natural numbers

Runar: Right. And this is the same with like every day concept formation, like with the idea of the natural numbers, for instance, with regard to like arithmetic on the natural numbers, the numbers are very precise. And there’s no additional noise, like the fact that those numbers represent guitars or something. Like that’s just additional noise that you don’t want to consider, you don’t need to consider. Like the fact that two guitars are different from some other guitars doesn’t matter, because if you have two have something and then two have something else, you still have four. So with regard to their cardinality, we want to regard them as being the same.

Adam: The place where… This is funny because this is talk about obstruction, I feel like it’s very abstract. I suppose that makes sense.

Polymorphism

Runar: Sure.

Adam: The place where I really found a lot of value in this concept, I’m trying to think about how to communicate it well. The place that I found most interesting was like in terms of polymorphism. So if I have a function that takes in an integer, and returns an integer, then that is concrete as compared to if I have one that takes in anything and returns something of the same type. Right?

Runar: Right. So that’s now more abstract.

Adam: So what is that abstraction buy us?

Runar: Well, so it buys you that the… So the caller of the function can now select which type the function should operate on. And because the caller can pick absolutely any type, that means that you have no hope of writing a specialized program that works for every type. So you have to write a general purpose one, and there’s only one that could possibly work. You have to return the element of the type that you’re given. Because you have no idea upfront, when you’re writing that program, you don’t know where you could possibly be getting.

Adam: Which is interesting because it means that the generic version is actually more precise, even though it’s more abstract. Like the possible implementations are just one.

Runar: Right. So it’s a more abstract type, but it’s a more precise specification for a program.

Adam: And because the definition is so limited, the amount of potential callers of it increase. Like there seems to be some relationship there?

Runar: Yeah. There’s definitely a relationship there. That relationship is called adjunction.

Adjunctions

Adam: Okay. What’s an adjunction? I might regret asking that.

Runar: So adjunction is an idea from category theory. So it’s a relationship between two categories, so it’s a pair of factors, one going from, if you’re going to category C and D, one of them goes from C to D, and the other one goes from D to C. And they stand in this particular relationship such that they are what’s the sort of a layman’s way, there are sort of approximate inverses, or one of them finds the most efficient solution to the other, and the other finds the most difficult problem that the other one can solve. That makes sense?

Adam: They’re inverses. They’re sort of like a [qualities 00:17:16] but they’re in the opposite direction?

Runar: Yeah. So they’re in opposite directions, but they’re not exactly inverses. They’re sort of approximate inverses. That is, if you go across the adjunction in one direction and then back again, you don’t end up where you started. You end up either sort of below where you started or above where you started in some kind of way in the category, like in a hierarchy. Like you’ll be able to either get to where you ended up from where you started, or you’ll be able to get to where you started from where you ended up.

Adam: So in my world of writing software… So I’m constructing a function, so I think what you’re saying is like, the more abstract and precise I write the definition of my function, that allows for more reuse or more potential callers of it?

Runar: Yeah. So you can think of this in terms of variance, for instance. Like if you’re familiar with the way variance works in Scala, like in class hierarchies?

Variance

Adam: Mm-hmm (affirmative).

Runar: So like a function is contravariant in its argument. And so as you grow as type the things that you could possibly receive, you make it easier to use. Like you can pass more things to it.

Adam: Yeah. And at the same time, that limits what you can do, because you have to cover the most general case of all.

Runar: Right. Yeah, and conversely, if you shrink the type, you make it more difficult to use. This particular function that we’re talking about, like the identity function, like for all A takes an A and goes to A. You can see that it is giving a lot of freedom to the caller, and basically makes it really easy to call, you can call it with anything, but you can only set that in turn shrinks the things that you can return, you can only return one thing.

Adam: So it can be called by anything, but in fact, it does nothing?

Runar: Right.

Adam: So it is the most extreme example, I guess, right?

Runar: Right, exactly.

Abstraction and Functions

Adam: A less extreme example would be like functions in general. So if I am constraining myself to not do side effects, that obviously limits my function, right? Like what I can do to make something a function, if there’s no side effects. However, that means, like more things could call it, I would say.

Runar: Yeah, that’s right. Because if you do have side effects, those side effects may be inappropriate, or impossible in lots of contexts. Like for instance, if you access the network, your function can be called anywhere where the network is unavailable, or where it’s inappropriate or dangerous to access the network.

Adam: Yeah, I’ve actually had this problem with, I think it’s like the Java URL class, where it actually does like a namespace lookup or something.

Runar: That’s a great example. Yeah.

Adam: I hate this problem, right? So you’re like, “What’s going on here? Like, it’s making a network call, why is it doing that?” The fact that it does a network call makes it not usable in a whole bunch of circumstances.

Runar: Exactly.

Adam: So the thing that you really, I guess, opened my eyes to is this back and forth relationship. And I think that like, as I write code, I want it to be reusable, I want composition. And that means that in this relationship, basically, I always want to go as abstract and precise as possible, right?

Runar: Well, I think that depends on your goals, really. Because sometimes going very abstract and very precise might be costly in terms of labor, or computation, or something like that. So I think that you got to weigh it against your other goals as well. So it’s not necessarily always correct to, and by correct I mean it doesn’t always align with your values to go completely abstract and absolutely precise.

general recursion vs folds

Adam: Yeah. So like, here’s an example. Should I use explicit recursion, or does it make more sense to use like a float? To me, it seems like there’s less power in a float.

Runar: I agree with that. And I think that, therefore, you should choose a float. I mean, whenever you can. But often, the float might be very convoluted to do. Like in principle, it should be possible to write any function over a list using a float, but it might be very convoluted, you might have to do some sort of intermediate steps and things like that. And there are lots of algorithms on lists that are just easier to write using recursion. And which one to do, I think, just depends on what your goals are the time.

Adam: Yeah. It depends on the case. But you could be as abstract and precise as you possibly can in that case, I guess. This is the way I’m interpreting what you say like, “If I have a function that… Like the values of it are only going to be whole numbers, like I shouldn’t be returning like a double or a float?”

Runar: Right. Yeah, exactly.

Adam: All right. What do you think is more precise? A like actor based system or like some sort of I/O effect types?

IO vs Actors

Runar: That’s a hard thing to answer. I think that probably some kind of I/O effect, like a monadic I/O?

Adam: Yeah.

Runar: Yeah. I mean, things that you can do with one you can definitely do with the other. But sort of an I/O monad has an algebra, like it has some laws that you can use to sort of reason about your code. It doesn’t have a lot of laws, but you can for instance, always know that if you break out some part of your program into a sub routine, and call it from your main program, it’s going to execute in the order that you expect, for instance, and other things like that. Whereas, like an actor system has no algebra at all, there are no laws of composition for actors, at least to my knowledge. I mean, there have been systems to formalize the sort of mobile processes, I’m looking at my book here on pi-calculus for instance.

So yeah, there are lots of sort of formalisms for talking about processes that communicate using messages. But we don’t tend to talk about like, when we’re writing those systems, we don’t tend to write them sort of symbolically, in the abstract, like those formalisms do.

Adam: That makes sense. I have a, in a piece of software I worked on, there is like a an actor that does some sort of job execution. Is very basic, but it will take any type of input into its message queue, and then do something with it. So if I were to replace it with some sort of function that takes a specific input, and then returns like I/O unit or something, it seems to me that that is more precise, because I’ve limited its input.

Runar: Oh, yeah. Limiting the input I think is a good first step. But I think that you can go even more abstract than that, maybe not for that particular application. But you could, if you have some kind of network that is exchanging messages over lots of different actors, you could maybe write that symbolically. Like you could have some kind of library that allows you to describe networks of actors talking to each other. And then as the sort of last step, compile that to actual actors.

Adam: Very cool. If I have something that is like of type… Like I just described, like type like I/O unit, or is that actually a function?

Adam struggles with IO[Unit]

Runar: Is I/O unit an actual function?

Adam: Yeah. If something returns that.

Runar: Yeah. So it’s constructing a value of type I/O unit. So then it depends on what language you’re using or whatever. But assuming it’s something like Haskell or Scala, what you’ve constructed is sort of a script, which will then be passed to the runtime in Haskell’s case, will be passed to the Haskell I/O subsystem, or in Scala case it will be passed to like the interpreter of the I/O monad. So yeah, if you have a function from X to I/O unit, it is a function, constructs… It returns an actual value, which is first class, and you can manipulate it.

Adam: I see what you’re saying. So yeah, it does work.

Runar: Yeah.

Adam: Okay. That’s awesome. I guess what I was thinking is that a function… Yeah. Okay. This is interesting. I’m thinking out loud about.

Runar: Yeah, no problem.

Adam: Because I’m thinking a function that takes in the same inputs and returns the same results should be equivalent. Like those two functions should be equal, right?

Runar: Yeah.

Adam: But I can write a function called not put string on that takes in a string and just returns I/O unit, and it would not be equivalent to put string on.

Runar: Right. Yeah, because I mean, it’s a different function. Yeah. But in a language where you don’t have monadic I/O, your point is good. Where like if you call read line twice, you’ll get two different strings, because you’ll prompt the user or read from standard input twice. And so they’re not interchangeable at all. Whereas in something like Haskell, read line just be a value of type I/O unit, and every read line is the same as every other, it’s just the value and you can pass it around or do whatever you want with it. Because to the programmer it’s completely opaque, right? The only thing, well, if we ignore unsafe perform I/O, the only thing that you can do with it, is to return it back to the runtime.

Adam: Yeah. Because the distinction has to do with that actual stage at the end where everything gets run. Like where you don’t see, off the end of Maine somewhere, this thing is being evaluated.

Runar: Right.

Adam: So, I think that if you explained this to yourself when you were writing printer programs in Delphi, would it have made sense?

90’s Runar

Runar: I don’t think so. I think I was just unequipped to learn this at the time. I mean, I was thinking in terms of, a program is a sequential thing. I understood pretty well like how a PC worked on the inside, you know like there’s a program counter, and it’s like loading into registers and moving stuff in memory. And like that’s how I thought about programming. And so when I thought, “Oh, I got to print.” To me that meant, “Oh, I got to send instructions to the parallel port.”

Adam: Personally, I found your book very inspiring. I love your talks. You seem to have a vast knowledge, so I saw this talk you’re talking about printing poetry, and then somehow you connected it to like junctions? How did you get where you are? How should we follow in your footsteps?

Runar: Oh, don’t follow my footsteps. There’ll be dragons? No, I’ve just always been really interested in software and software development, and always found that there is like something else to learn, something new on the horizon. And I guess I was looking for the sort of governing dynamics, or the abstraction that captures in general, the thing that I’m doing.

Learning through examples

Adam: I watched like several of your talks, and one thing I noticed that maybe I’ve failed to do here, is like you hammer examples. I saw you give a talk about category theory at the [inaudible 00:28:03] conference that was in San Francisco, and you didn’t even really explain what it was, but just hit with examples, one after another. Is this like how you learn or how you teach?

Runar: Yeah. That’s sort of my philosophy of teaching, and also how I tend to learn. I think that this is the nature of abstraction, because we were started off talking about abstraction. The nature of figuring out an idea is to see lots of examples, and then take what is the same in those examples, and what is different, and sort of stamping that out as a concept. So if you line all of those things up, you can see in what way you can regard them as the same, and in what way they vary. And then the ways in which they vary is the thing that you want to abstract out. So yeah, I definitely think that the way that we come to understand anything at all, is to connect it with our existing experience. So seeing lots of examples, and then forming the concept or abstraction ourselves. I don’t think that an idea can really be conveyed in any other way.

Like trying to convey just the abstract idea, I think is sort of doomed to failure. You may see the structure of the abstract idea, like for instance, if you don’t know what a monad is, I can just show you the Mona interface. I can say, “It’s this.” And you will know exactly what a monad is. But yet, you will have no idea what a monad is, right?

Adam: Yeah, exactly.

Runar: Like you will have the syntax for monads, but you will have no semantics in which to interpret what you have learned.

Adam:

Examples and applicative functors

Yeah. A colleague of mine, he was talking about applicative functors, and he was going over the definition of like [maptun 00:29:48] like combining two things. And, like I understand it because I’ve used maptun, and map whatever. I don’t know if it was all renamed. Like if the names were gone, and I was just looking at the pure definition with the symbols, then I would have understood it. I’m only relying on my ability to connect it to a specific case where I had used it.

Runar: Yeah. This is one reason why often people find mathematics difficult, is because lots of times they’ll just invent symbols. And so like the structure of the argument is all there, right in front of you, but it’s really difficult maybe to figure out what is being said because there’s a lack of examples. And all the symbols are, they don’t refer to anything that connects with your existing experience. There might be like, just like, “Oh, gamma and sigma, and x and y.” And that’s it. Was like, “Yes, structurally, that is correct.” And like I can use this paper or whatever it is to calculate something, but I don’t know what it means.

Adam: So like, you talked about having a book on pi-calculus there. So how do you overcome this? Obviously, you’re able to take up these concepts? How are you putting meat on them?

Runar: Probably by just conjuring up examples. So trying to connect what I’m looking at with something that I know already. Yeah, I remember learning about applicative functors. And the way that I learned about applicative functors, was just to experiment with lots of things that I knew fit that shape, or that I thought might fit that shape.

Adam: Do you think that there is a… Maybe I’ll tell a story. So I tried to learn Haskell in like 2012, I had like my real world Haskell book, and a worker had some slack and I would just build things here and there with it. I mean, I built some things but I don’t think the skills really set in. But now I work on a team of Scala developers who are really keen on functional programming. And we had somebody recently join the team, didn’t know Scala, not that interested in FP, and I feel like just through osmosis, he’s learned like so much stuff, without even really caring, compared to me struggling back in the day. So I don’t know if there’s an aspect of learning through like… It’s just contagious. Like just working in an environment where people understand concepts, somehow there’s a there’s a transfer. I don’t know what you think about that.

Learning through osmosis

Runar: Yeah. No, I totally agree. I think that you learn a lot more by doing, by experiencing the thing yourself and through your own work, particularly through work that you actually care about. I mean, I think the reason why lots of people come out of University, not really having learnt anything, is that they don’t care about the subject material, and it doesn’t connect with anything that they do care about. And so I think that that’s really important to connect the science or the abstract idea, with the everyday work that you’re actually doing, and to sort of like chew on it gradually until it starts to fall into place.

Adam: And do you recommend like, you yourself, given many talks, written books, is that a method of learning or method of teaching?

Runar: I think both. I mean, learning is just teaching yourself, right?

Adam: Yeah.

Runar: So I think that this is just a practical way of both learning and teaching basically anything. So I didn’t really go to school to learn any of this. It’s mostly just been through work but I’ve learned all this stuff.

Adam: So is it possible to become the future Runar via just like working at your job where you’re doing PHP development, and then playing around with things on the weekend, or do you need to be fully immersed?

Runar: I mean, yeah. I don’t know what it means to be the next Runar. I think that you can absolutely become an expert in anything that interests you enough, and that you have enough exposure to in your day-to-day life.

Adam: So it’s not necessary to write books and to teach and to work somewhere where they apply these principles. You can just overcome that via self interest?

Runar: Well, you ask the question whether it’s necessary to write and teach? I think that that’s absolutely part of it. I think you learn a lot by teaching an idea to someone else, and you also learn a lot by writing your thoughts down. So it kind of forces you to sort of face where your gaps are, or where you might not have a very precise idea. Like not precise enough to explain it to someone else. So yeah, I think that’s definitely part of it. It’s a sort of mind-body integration thing. Like I think it’s just like learning to dance, or play the guitar or whatever. You can’t really know how to play the guitar abstractly, no one can really just tell you how to play the guitar. You got to do it.

Learning via teaching

Adam: Yeah, that’s a good point. So that makes me think of like open source, right. So there’s certain things I’ve learned about functional programming or abstraction or about types, just by having somebody tear into me in a PR. I can’t get that from reading a book. But I can like try to contribute to some sort of open source work and then and have somebody tear into me there.

Runar: Yeah, for sure.

Adam: Well, I think that I used up all the time. One thing I wanted to ask you about was this Unison Computing that you’re working on.

Unison Computing

Runar: Yeah.

Adam: Do we have time to talk about that a little bit?

Runar: Absolutely.

Adam: So what is Unison Computing?

Runar: Yeah, so Unison Computing is a small company that I co-founded with a couple of friends in here in Boston. And so we’re working on this language called Unison, which is a purely functional programming language. And we hope to have it make it easier to write particularly distributed software. But we want to sort of revisit the entire developer experience, and like the entire experience of caring for and owning software, starting with just like how you interact with the compiler, and then all the way to how you build large scale distributed systems.

Adam: One thing I noticed, when I looked at it, it seems like you don’t represent code as text. Is that right?

Runar: Yeah, that’s right. So the code base is a persistent data structure. So essentially, the code base is a purely functional data structure that you can manipulate as a minute. It’s an object that you can manipulate. So it’s not a jumble of mutable text files on your disk, it’s like an actual object that you can programmatically interact with.

Adam: I feel like this ties into your idea earlier about SQL and representing SQL as strings.

Runar: Exactly. This is exactly like that. So we represent programs symbolically, so that we can manipulate them very easily programmatically. And we can also communicate programs over the network, and so we can write distributed systems where to make a program run on a large network is no harder than just writing a program that runs on a single machine. So you write a program that describes your entire system, a distributed system, and then when you execute that program, that’s the same thing as deploying that distributed system.

Representing programs symbolically

Adam: So it’s almost like, this probably isn’t how you would describe it, but it’s almost like if your entire program was in the config for Kubernetes, then all you would need to do to deploy it as give that config hands it out to all the nodes and then execute it.

Runar: Yeah, it’s basically like that. Except that, imagine if you could talk about like Kubernetes locations as a first class entity in your programming language, then you’re getting closer to sort of where we’re trying to go. But communicating programs over the network, and representing them symbolically is not new, like small talk that this Lisp does this, but we are doing this as a typed, purely functional programming language. And when we communicate our identifiers, we don’t start by like sending programs over the wire. So Unison has a unique approach to names, which allows us to do this sort of unambiguously.

Adam: What’s the approach?

Runar: Well, okay. So sort of as a little bit of a background. Let’s say I have a program that like calculates the 24th Fibonacci number or something, and then I send you that program, you’re on another node on the network. I send you the program, and I say, “Execute this.” And then you see this program, let’s say it’s a Lisp program or something, and it has this expression in it, which is like Fibonacci of 24. Like, how do you know what Fibonacci is? And how do we know that we agree on what that is, right? So there has to be some kind of like resolution of free variables in the program that works across the network across both space and also across time, because the definition of what Fibonacci as well, that’s probably a bad example.

But like, the definition of some function may change over time, like what a name means. So in Unison, a name is a cryptographic hash of the definition. So the Fibonacci function has a unique eternal identifier that we can unambiguously communicate. If I send you that hash apply to the number 24, you either know exactly what I’m talking about, or you can unambiguously know that you don’t know what I’m talking about. And then you can say, “Please tell me what this hash represents.” I will send you the code, and then you can hash it, you verify that it matches that hash, and then away you go.

Adam: It makes me think of well, two things, right? One is like content addressable storage, right? So the key for a value is like the hash of the contents.

Content addressable functions

Runar: Yeah, it’s exactly that idea.

Adam: And then the other one is, are you familiar with this Little Typer book, and the PI programming language, or dependent types in general, I guess?

Runar: I am familiar with dependent types, but I haven’t taken a look at Little Typer.

Adam: So in Little Typer because the type system allows for like functions that will evaluate some arguments to produce the type right so that the type ends up being an actual function. To figure out if two types are the same actually means figuring out if any two functions are equivalent, right, which means that they kind of do the same process, because they need to be able to say, the type of this function is returned is actually this is the type can actually be determined by evaluating the function. So to see if two types are the same, they may need to determine if two functions are the same. And so they have to do this process.

Runar: Yeah. So that gets into notions of like extensional versus intentional equality, the kind of thing that we have is Like unintentional equality, like two values are the same, if they have the same implementation, or rather, they have the same name, if they have the same implementation.

Adam: Make sense, which is sufficient for distributed. But if two functions return the same inputs for all outputs, they’re not necessarily the same by your concept of equality?

Runar: Right. Yeah, you could, for instance, insert an identity function to identity on the outside of your function, like you take the source code from my function, add identity of that, and you’d have a different function as far as Unison is concerned.

Adam: Which is fine, because then it just distributes it somehow.

Distributed small talk

Runar: Yeah, the idea is that you should be able to distribute implementations unambiguously, and that once somebody has discovered a function, it’s sort of like always there, it’s like discovering a new star in the sky or something like it has like an unambiguous place in this space. It’s rather like it was always there. And it was just waiting for you to discover it.

Adam: Isn’t this like Smalltalk, you mentioned earlier? Like, it has some sort of, you can save data and definitions into a workbook or something? Is that kind of a similar concept?

Runar: Yeah. So in Smalltalk the code base, or I don’t remember what it’s actually called. But yeah, like all of your Smalltalk definitions are saved to a database, like in your local storage. And there are tools to migrate between to migrate code between people without necessarily sharing just the text. Yeah. So I mean, it’s a similar idea to that kind of thing.

Adam: Well, it sounds like the problem is that what about like version control, and my favorite text editor, and things like that?

Runar: Yeah, so Smalltalk is very sort of like a closed environment, but we very much want to make sure that you can still use your favorite text editor, and that you still use Git, or whatever, to diversion things. So we’ve made sure that the repository format of the codebase is very amenable to being versioned by Git. So then you don’t version the text of the code, where you version or the binaries of the codebase.

Adam: Oh, are they actually binary?

Runar: Yeah, the code is actually stored in a binary format.

Adam: So how come binary and not like when you described it, to me, it sounded like some sort of abstract syntax tree or something would be the?

Runar: Yeah, it is. It is an abstract syntax tree, but it’s historic in a very compact format. So instead of having text that represents the things syntactically like for instance, a block of code will start with some byte, which represents open block. So it’s rather like a bytecode, it’s like a Java bytecode, for instance. And so what your version is the bytecode and you can always recover source code using your code base. And your code base has mappings from like the hashes to the names that you originally used. And so the rendering of the code will look very similar to what you originally wrote. Or you can keep the source code around if you want as well. But that’s not like the source code is not like the source of truth. That’s just sort of the peanut shell. And then once you’ve cracked up open the peanut, you can keep the shells or you can throw them away.

Adam: Yeah, I feel like we should have started with this as an example, because it definitely seems to fit your abstraction idea, right. So the binary can only represent like valid programs, or some it’s more restricted than in text form.

Runar: Yes, yeah. It’s definitely more restricted than a text form. I mean, it certainly can represent invalid programs, like the algebra of sorry, the abstract syntax tree datatype, the algebraic data type for the syntax tree is not absolutely precise, right? So like, you could, for instance, apply a value that isn’t the function to a value for instance, but that’s why we have a type system so that we can prevent you doing things like that.

Adam: So how did you implement this? You’re building a compiler and stuff?

Implementing the unison compiler

Runar: Yeah, so the compiler is built in Haskell. And we originally had a runtime that was written in Scala. And so we were running Unison programs on the JVM. And that’s one of the current implementation. We’re migrating to a Haskell-based implementation of the runtime it’s a lot simpler.

Adam: What was the like, I assume there was some disadvantage to the JVM?

Runar: Well, we just weren’t really very happy with our implementation of it, we were trying to be sort of too clever. And so the JVM-based implementation turned out to not buy us very much speed, which is the reason why we were trying to write it for the JVM in the first place. But the Haskell-based implementation is a lot easier to understand than the Scala based one.

Adam: So what kind of crazy distributed programs can I make with this language?

Runar: Oh, well, I mean, right now, none. I mean, it’s still very much in development. And so we’re, I mean, we’re sort of engaging the community of enthusiasts for this, but it’s not really ready for sort of everyday programmers to get their hands on it. And, I mean, you can, it’s open source, it’s on GitHub. But you can’t really write any practical programs in this at the moment, but we’re hoping to have an initial release of this sometime this year.

Adam: That’s awesome.

Runar: We can actually write some programs.

Adam: So if people are interested they should, what should they do?

Runar: Go to unisonweb.org. It’s all one word unisonweb.org.

Adam: And then like, Can we, I guess we can’t use it yet. Can we contribute? Can we learn things? or?

Runar: Yeah. So GitHub has a number of outstanding issues, bugs and like feature requests and things like that. So like, there are things that are easy to pick up. So people want to contribute. They definitely can. And like, one of the ways in which you can contribute is like porting libraries to Unison. I mean, it might be kind of difficult at the moment, because like, the compiler has bugs and, and it’s all very, it’s a lot in flux at the moment as of right now. But very soon, we’ll be able to accept contributions to things like I know, something simple, like a text manipulation library, or something.

Rethinking computing

Adam: Seems like a crazy, ambitious project Runar, like you’re rethinking through rethinking distributed computing, but also just representing code as text. What else are you rethinking?

Runar: We’re rethinking sort of how to interact with compilers. But we want to ultimately rethink how code is edited, make that a more sort of fluid experience? Yeah, there are lots of ways that we can go with this. But the fundamental thing that allows us to rethink all that is to think of the code as being a data structure and having this idea of the sort of content addressable namespace. So there’s those two things together, I think, will enable a lot of cool technology that, I don’t know, it seems pretty space age at the moment.

Adam: Well, I think it’s awesome. Yeah, I mean, it just really hits home to me with your concept earlier, because like a, an AST, with a text file of my code can represent a lot of things that are valid. And, like just code formatting, I guess we love a lot of nice code formatting tools, right now.

Runar: Yeah.

Adam: But still.

Runar: That’s just something that nobody should ever do.

Adam: Yeah

Runar: Think of all the time this is wasted on like, formatting code, like every time I just like, make a new line and hit Tab a couple of times, or Backspace to like, make something a line up like, I die a little bit on the inside.

Adam: Yeah, I agree. And like, there’s like weird things like if I was doing like scheme programming, I really would like to have a way to tell my editor to just like, treat brackets as indents, like Python style, and like, not actually see this, like ending line with like, 1000 ending brackets?

Runar: Yeah, absolutely. And that’s another thing that I think that we’ll be able to do is to have lots of different surface syntaxes for the language. So if you want to know a lot of brackets, and you want to use expressions, you can. I mean, we’re not there yet. We only have one syntax, which is very Haskell like, but that’s only because that’s the aesthetic that we prefer. But there’s no reason that we couldn’t have any number of surface syntaxes for this abstract language very much like Microsoft is doing with C-Sharp and F-Sharp and all of those languages, they’re all under the hood are the same CLR.

Adam: But like even more so because you’re just Scala and Java both run on the JVM, but they’re not the same language. This is much, talking about the presentation could vary, right, but like, the actual semantics would be the same.

Runar: Right? Exactly.

Adam: That is a cool concept. You could get adoption by just having a mode that fits everybody’s weird preferences.

Runar: Yeah. Maybe we could make it easy to have sort of pluggable syntaxes.

Adam: That’s awesome. Well, thank you for your time, Runar. This has been a lot of fun.

Runar: Yeah, likewise. It’s been a blast. Thanks for having me.

Adam: Thank you very much. That’s the show. I hope you liked it. If I’m totally honest, the interview with Runar went in a slightly different direction in the second half than I had planned for, but it ended up super interesting to hear about his new project, to hear about how he likes to learn and approach topics. Just a super interesting person to talk to. If you like the show, tell a friend about it and help spread the word and also maybe join our Slack channel. After the last episode about Scala Native, we had some interesting talks about Big O notation and complexity in the channel. And also user Joe shared a file server open source project he made in F-Sharp, where you can upload files but you get to set kind of a time to live on. Kind of an interesting project exploring functional programming answering files. So yeah, until next time,

Join the Newsletter

Did you know CoRecursive has a newsletter?

Sign up to get insights, takeaways, and exclusive content from each new episode.

    We won't send you spam. Unsubscribe at any time.
    Audio Player
    back 15
    forward 60s
    00:00
    00:00
    49:00

    Abstraction and Learning