Erik Meijer discusses programming language design, effects and some web programming problems
00:41:57 video length
Bio Erik Meijer is an architect in the Microsoft SQL server division where he currently works together with the Microsoft Visual C# and the Microsoft Visual Basic language design teams on data integration in programming languages. He was an associate professor at Utrecht University and adjunct professor at the Oregon Graduate Institute.Erik is one of the designers of Haskell98 and the Cw language.
GOTO Aarhus is the enterprise software development conference designed for team leads, architects, and project management and is organized by developers, for developers. Important topics are presented by the leaders in our community. The conference concept is to present the latest developments as they become relevant and interesting for the software development community.
Long time no see.
I think last time was in San Francisco, I think at QCon.
I think I have a pretty clear definition. I think when you look at languages, people say object oriented languages for example then you can ask yourself “what does that mean?”, that means programming with objects, but then what are objects, then you get something really vague, like object is something that represents a real world bla bla bla. So that is always kind of vague, so if you look at functional programming, what does that mean? It means program with functions. You open up any book from high school or elementary school, you look up what is a function and you know what a function is. So functional programming is programming with functions. Now unfortunately people kind of confuse the term, so they use functional programming for things that are not programming with pure functions, so the only pure functional language that I know is Haskell, because that is programming with mathematical functions. All the other ones either they either mean, they don’t use assignments or use closures, something like that. So for me the world is divided into two: there’s pure functional programming and then there’s all the rest. And I’m not saying that one is better than the other, but I think there’s a very large distinction between pure functional programming, like in Haskell, and regular imperative programming. And again, I am not saying one is better than the other. They both have each their strengths and weaknesses, we can go down into the details but that’s kind of my distinction.
3. But is it a problem if I have some language which has functions, which uses functions but it still does a lot of, for instance IO or whatever other non-pure operations interleaved with functional code?
I think that’s a huge problem actually and the problem is that once you’ve introduced closures, let’s call them closures because I don’t want to call them functions if I’m really kind of a purist, because they are not functions, they are kind of higher order things, they are like objects, special objects that capture variables in their context. And that’s exactly the problem because when you use a closure the evaluation of that body of the closure gets delayed, it doesn’t happen at that point but it might capture all kind of state or context and it’s not only local variables, it can be other things that it captures, then you pass that closure happily around and at some point you call it, but now when you call it it’s out of the lexical context where it was created and then really bad things can happen. So I think it’s a very powerful tool but you’re giving people an enormous gun and they can shoot their legs off, their heads off, bad things can happen if you don’t know what you’re doing.
That’s a very good point, in some sense what you get is, the difference between dynamic and lexical scope is that I can see which variable was captured because that is lexical scope but you still get the dynamic effect that when you call the closure it happens in a dynamically very different spot. Maybe you could argue that dynamic scope is better because at that point it finds the kind of variable binding as late as possible. But I don’t know, now I am working up ten eyes, people will be saying “oh, Eric Meijer likes dynamic scope”, I don’t know, I do like dynamic scope actually because in Haskell we added this thing implicit parameters which is really what dynamic scoping gives you. When you have a dynamically scope variable it is like an implicit parameter and then when you use it, it will go and find where the binding is. Scala has something similar with implicits; in Haskell you have that, so I think there is usage for that as well.
Sadek: So is it like in a way dynamic scoping is back? also Clojure has something like implicit parameters, like sort of type classes.
I think these things, I don’t think there is any bad, when people talk about programming constructs that are bad I don’t know how you can measure why it’s bad. Is go-to bad? I don’t know, go-to is maybe bad because your programs, you cannot reason about your programs or something, but that holds for any advanced construct that you use in a language. In that sense I think closures are as bad as go-to, using closures in convoluted ways or your program becomes unreadable, you cannot reason about it anymore. So I don’t think we should blame, it’s easy to blame the programming language, go-tos are bad, I think that’s kind of denial that really the programmer is bad, but this is human, we always attribute something, we try to attribute it to something outside ourselves. But I think you can write bad code in any language even if the language only has “good constructs”.
Sadek: Right, so it seems like closures are the new go-to.
Closures are the new go-to, that would be, maybe we can say that, continuations are the kind of ultimate go-to but yes, I think closures are very useful but you have to be very careful just like with any power tool, they are powerful and therefor dangerous.
Sadek's full question: Back to the question, if I have a language and it’s not pure, and for a reason, it’s a pure functional programming languages tend to be completely different from other languages that people have to learn everything from scratch in a way almost, reasoning about programs is completely different and that’s why people prefer to stay with languages that are not completely pure but they still use functions and they have to do IO. What is the best way for programming language to help programmers do IO but in a safe way, not to do the wrong thing?
So this is something I wondered a lot about. You say people don’t use pure languages because they say they then have to learn everything from scratch again but the funny thing, if you look at most education, the first thing you learn is mathematical functions. When you get math at school you learn mathematical functions, you don’t learn assignments and wide loops and things like that. So I would say, and if you look at other disciplines like biology or physics, they continue to use functions. Physicist use functions for everything, that’s how they model the world. Only computer scientists for some reason say “hundreds, centuries of mathematics, we know something better”, so then they move to this different formalism which is operationally more attractive but I think from a mathematical point much more complicated. So that was a long story to come to your answer.
So for me the beauty of pure functional programming is that you have to be explicit about everything, so you are saying if you have to do IO in a language like Haskell it becomes apparent in the type system, so you look at the type of the function, even in Haskell you can cheat, but if you look at the type of the function you can see that it does IO, that it has effects, and then when you compose these functions you have to be careful about in what order to apply these effects and now when you are doing computations in an imperative language they’re usually the types, if you look at the method declaration either there is no type or the type doesn’t even say that there’s IO. In Java it might throw an exception, which is kind of an effect, but usually it doesn’t.
So I would say you have to be careful about the effects that you care about and I think the struggle for me, what I would like to find is a language where I can care about effects when I want to and don’t care about them when I don’t need to. But the thing is there seems to be this divide between them because once you stop caring about the effects then in some sense the whole system becomes unsound. I haven’t found the middle ground but what I would say if you want to do IO, say in an imperative language, and it really doesn’t matter, you don’t care about it, then it’s fine, you just do it. But if you do care about what you can do is use techniques from pure languages and express it in a type, you can define some kind of fake phantom type or something that says IO or, is not the phantom type I should be careful with my wording, but you can define your own let’s say IO of T which had just only a value of type T but in the type system you can now track that.
Sadek: And for other IO versions I would say secretly doing IO, I don’t care.
When you don’t care, yes. But the problem with that once this becomes apparent in the type and this is why people don’t like checked exceptions is that the type now pollutes your whole program, because everywhere, let’s say you have a function, or I should be super careful, say you have a closure that returns an integer but it does IO, its type now becomes IO of Int and then I cannot add it anymore because now I have to decide in what order I do the effects, so you get the same thing people don’t like about monads in Haskell, but I don’t know whether there’s a middle ground.
Sadek: This kind of forces you into separating your system into two things: IO things and pure code. That’s the main argument.
Yes, and in some sense it’s like Simon Peyton-Jones also if you look at the way they define the semantics of Haskell they also kind of separated into these two worlds: the imperative world of effects that in some sense you script with a pure language. It’s interesting that the scripting side, the pure sides and the operations are the imperative sides.
Sadek: Right. And then you have some other languages, for instance Erlang, it’s kind of weird, it has functions, maybe you call them closures in that sense, and they do IO but not in a pure way, so the thing is you don’t have multi execution, given closure you don’t have multi execution, multi threads and multi processes and that’s why it takes off a lot of the bad things IO can do with this kind of environment.
Ok, so let me prefix it here, I love all languages and you ask me a question to kind of look at different languages and critique them. I don’t want to give the impression that I’m bashing any language but with Erlang it’s an interesting question because one of the most powerful effects that you have is multi-threading or concurrency. And say that you have a language that has no assignments or no other effects except concurrency and you can easily do this in Erlang, you can define a cell, a process that has two messages, get and set, that with concurrency now behaves like a mutable variable, except now I’ve kind of modeled my effect of mutable state on top of the effect of concurrency. So in some sense you didn’t gain anything by leaving out mutable state because I can implement it at the next level. Even if you look at recursion, is also a dangerous effect, if you look at circuits, gates are pure functions, AND gates, OR gates, but once I allow feedback, now I can define a flip flop and now I’ve created mutable state. That’s the thing you add a little bit of effect you think “oh, this is ok” because everything is contained, it’s like computability, once you have power you can program anything and it’s really hard to contain that, it’s really hard to come up with a language that is not Turing complete and immediately you add just substitution or something, you see this with Cellular or Automata, they look like trivial but then suddenly you see they have full power. The same with effects, don’t think that by saying “oh, I only have good effects” that you’re ok.
Sadek: And you think they can surface accidently, this kind of?
Accidently or if I’m a bad guy I can say “look…”
Sadek: But the bad guy gets back to go-to and not using ….. Accidently is dangerous.
Yes. And the other thing I think what sometimes Erlang people say is that Erlang, for example, doesn’t have objects or something they have actors or you send messages, but I would say, I like to look at languages and look at what is common instead of what is different, instead of saying “Erlang is different”, I look at Erlang and say “it’s really very similar to objects because if I send you a message, you’re an actor, you’re doing a pattern match on that message and you have some internal state when you have a tail recursion that you can wait for the next message”. That’s really just like virtual dispatch, where the pattern match is just looking what is my dynamic type. And the parameter that you pass around, the internal state that you pass around in that tail recursive loop is like your instance variables. So I would say there is more commonality than differences, so I would say Erlang has a lot of great properties, for example that’s very cheap threads then you can build amazing things because now you can use concurrency as a control structure but I would say it is really, for me and I think that’s a really positive thing, very close to object oriented programming and dynamic dispatch.
Sadek's full question: So one of the things you did in the last years is Reactive Extensions, which is I guess myself, was a bit ahead of its time because now, today we start to hear a lot about streams, Web Sockets and Server Sent Events and these kind of things where you can have an open socket with a client and with a browser and with other things, like Twitter has an API for get live, sending twits, searches and so on. And it seems like just now we are trying to find the tools for doing this. How does Reactive Extension, Reactive framework respond to this kind of needs?
What you notice when you write code for the web, is that suddenly these things now, because there was another thing that we did, we wanted to take code, you put some annotations on it and then we would split it to run on the client and the server. Now, to make a long story short, what we discovered is that really when you split your program to run across client and server, now you cannot make synchronous calls anymore, because you don’t want your browser to block because that call now can take a long time relative to the number of instructions that a computer can execute in that time. So the first attempt to us was, say you have a function that takes a Bool and returns an Int and now I run that function on a server, you don’t want to expose that as a function, as a closure, a routine, I’m getting caught up in my own names here, that takes an integer and returns a server, a Bool returns an integer, because it’s asynchronous, it can fail the network can fail. So the first attempt that we did was to say “oh, we’ll give this function a callback so when it returns an Int that function now takes an extra argument that takes a callback that takes an Int, and then when that thing is ready the callback will be invoked and everything returns void”. And then the next thing you notice “oh, but now I can have a callback that takes an Int, but what if it fails?” so then you have to have a second callback that takes an exception because really if something returns an Int because of the effects, that thing can return also an exception.
Now if this thing really has side effects and you call it multiple times, then it returns multiple results and really implicitly a function with side effects returns a sequence. If I call the increment function, and when I say function I am not going to just use function, so if I call that every time I call it it gives me the next value so I call it it gives me one, I call again it gives me two, so really if I would make the effect explicit just like I did with the exception, that thing returns a stream of values. Then we said but we need three continuations because the thing should also notice that it’s done. But then you cannot compose them, if you pass the continuations as arguments it becomes hard to compose, because it gets void back, how can I, for example, cancel or do anything? I pass the continuations as arguments and I get void back? Getting void back is really nasty, you cannot do anything. So instead what we said is that the function itself returns an object to which I can supply these callbacks.
And then you can compose the result of that function, so your function, you call it, it gives you back an object that I can compose and then when I am ready I can pass the callback and then everything happens. And really what that is, is the continuation monad, so if you look at what the Reactive framework is, it’s just the continuation monad. And what is also nice is that the way we discovered it is the observer observable is the dual of enumerable enumerator. So there is a nice mathematical connection between them, and then the operational view is that when you have an enumerable, an enumerator that is pull based, you ask for the next value on your block whereas the other one is push based. So that’s where we came from and then the other thing is that observer observable is very much compatible with .NET events, so like the mouse moves and so on, it fits nicely in there, but the long story, and I am making this a very long story.
Sadek: It’s interesting though, the whole story of reactive, now we’re getting the whole story of where Reactive came from.
Sadek: You said something very interesting, well everything was interesting but there was a particular thing, you said we made effects explicit, this was the first thing. The second thing you said Reactive is all about effects, it’s full of effects in a way, I mean subscribing something is an effect and pushing callbacks on something is an effect too. So to me it’s like mixing functional programming with effects in a way.
That’s a very good question. So I am giving a talk now that I do several times which I kind of like and I don’t have my tie dye shirt on today and I don’t have long hair but really I’m a hippie and there’s this kind Chinese philosophy that things come in five things, wood, fire, earth, metal and they are all in circles and I really think that’s kind of an interesting way of looking at the world, in Chinese cooking this comes back with a kind of five spices and everything. Now if you look at programming, I think there are also five fundamental effects in programming, so let’s look at that. If you look at normal computation, in say C# or Java or any imperative language, a normal computation that is the default effect is a single value, if you call a method it returns you a single value and it does that synchronously and it can have all kind of effects that are defined by the language designer and machinery, for example in the effects there are things like it can throw exceptions, it can create threads, it can do reflection, but there is this one effect that we can say this is the default effect of something that returns a single value and usually it’s synchronously, you’re blocked, you call in a method, you’re blocked and then once it comes back you can do all kinds of effects. Then the other one is when you get multiple values, but they are also in a blocking way and that’s what enumerable is.
So enumerable is the little brother or sister of the default, but then it’s a stream. Now you can move to the asynchronous world and that’s a dual in a mathematical sense, so if you go from enumerable you get observable which is a collection of asynchronous values and each time you get this thing an effect can happen but they come asynchronously at you and then the fourth one is when you get an asynchronous computation that returns a single value because that’s the dual of this thing, of the default effect and in .NET that’s called Task
Sadek: Very interesting the duality between the value and the task of value, and the observable and the stream of values and a collection of values, but the lazy value it doesn’t have a collection of things, right?
The lazy value is like a single value but operationally it behaves different from a regular value, so the Task
Sadek: Still blocking, yes. It’s mostly what Haskell has for instance.
Yes, Haskell has that kind of implicitly. Every Haskell value is kind of lazy.
Sadek's full question: And for that reason it seems like you don’t need tasks of anything in Haskell, I heard, I don’t know who was telling this but in Haskell you don’t even need any reactive programming or anything because you have lazy values and you have IO that is taken care of by the compiler in a way, right? So, does it replace all the reactive things?
Laziness? I wouldn’t say, because laziness is still blocking. Say that I have an integer that’s really the Ackermann’s function applied to huge numbers. Computing that can take millions of years, if I take my arguments from Ackermann’s function or some other kind of function. So once I touch that I get blocked, so even though it’s lazy it’s still pull based, so I think for reactivity you still want to be push based and still have to reverse the control.
Sadek: It’s kind of interesting because if you’re not reactive but you’re still using CPU and you are on the same machine, you are not distributing, it’s not a problem, right, you are not wasting resources because you’re consuming CPU and the other function as waiting for it, it needs to have value to compute anything, that’s why you didn’t block anything, you didn’t block any resources that you don’t need, you don’t have to block it. Of course, when you are distributed then you can give signals because the other machine doesn’t care about what you are doing.
I’m not sure it has to do with distribution, even on a single machine, you don’t have internet, you’re in the middle of nowhere and you have a single machine, you only have only a one core machine, so you got it from a thrift store, is a very old machine that has only one core, there’s still simulated concurrency going on so, in that case things do not block because your mouse is still something you have to react to, so you don’t want this one computation of Ackermann to make your, that you cannot play. So another interesting thing is that I laid out these five principle effects but you can also start to combine them, if you decompose the effects themselves, if you look at for example IEnumerable, you can look at that thing like an atom, this IEnumerable of t that’s the effect, the effect of a pull based multiple value computation. But if you cannot look at that thing itself it’s composed out of smaller things and for example the move next method there is something that returns Bool and it’s kind of blocking, but what you can do is now you can make that move next method return a Task because it returns a single value, so you can let the other effects push them into the other ones and then you can get hybrids you can have a very fine grained notion of where the effects happen and then in the end you only I think need the effects for single values, because everything that returns multiple value is composed out of single values. Now my theory may fall down.
Sadek: That’s really interesting, if I don’t do Observable, I just do that innumerable that is returning a task of something when I move next, then it’s completely reactive now, right?
That is, yes. And you can show that these things are isomorphic.
Sadek: Still pull but very reactive.
It’s pull, but asynchronous pull, you pull but normally when you pull you get blocked, in this case it returns a task of the Bool, yes.
Sadek: You can pull the value in the stream.
Or you can react to it and the value comes back. There’s all kind of hybrids and you can show design space of that and I am pretty sure that you will find many of the existing Reactive frameworks, in the broader sense, as points in that design space. But my claim would be, I don’t want to sound too cocky here, but I would say the design space is spanned by those five effects, just like a vector space where you can get all the other points by a suitable combination of these elementary things.
Sadek: Very interesting. So five principles of combining effects. Well, thank you, Eric. It was very interesting.
Thank you so much.
Any talk of Erik's is always very interesting
I think that was primarily a limitation of the C# language version at that time. With the new C# 5.0 async keyword this should not be an issue.
I recently did a PoC with F# and WebSharper and async calls to server were never an issue. I just wrapped the server sides functions in async monads and used F# computation expressions on the client side to compose the asychronous worklows. See this blog post for details: fwaris.wordpress.com/2012/12/15/rich-mobile-web...
Re: Any talk of Erik's is always very interesting
Also, as I explain, there are 5 fundamental effects, Task<T> + async await are great for asynchronous computations that returns a single value. In order to deal with asynchronous (event) streams of 0,1, ... values you need IObservable<T>, just like for synchronous sequences of 0,1,... elements you use IEnumerable<T>. You may watch Jafar Husain's interview to see how Netflix leverages asynchronous *streaming* to scale channel9.msdn.com/posts/YOW-2012-Jafar-Husain-R....
Happy holidays, and keep on hacking!
Re: Any talk of Erik's is always very interesting
Fortunately, WebSharper integrates with a long list of JS frameworks (see: websharper.com/downloads) – including JS Rx! Here is a nice sample that combines async with Rx: www.intellifactory.com/blogs/joel.bjornson/2010....