S11E10 GenServers and Actor Models with Xiang Ji and Nathan Hessler Intro: Welcome to another episode of Elixir Wizards, a podcast brought to you by SmartLogic, a custom web and mobile development shop. This is Season 11, where we're branching out from Elixir to compare notes with experts from other communities. Sundi: Hey everyone. I'm Sundi Myint, engineering manager at cars. com, Owen: And I'm Owen Bickford, Senior Developer at SmartLogic. Sundi: and we're your hosts for today's episode. For episode 10, we're joined by Nathan Hessler, senior manager at the score and Xiang Ji, engineering team leader at remote. In this episode, we compare Actors and GenServers with Elixir, Clojure, and Ruby. Hi everyone. Nathan: Hey, Sundi: to see you all. It's been a while since I've been on the podcast, I feel like, so it's really nice to get back into the seat. Nathan, where are you from? How are you doing? What's up? Nathan: Let's see. Where am I from? Well, I currently live in Austin. Let's say that I've been all over the country, but then Austin for a few years, really digging it and, uh, doing well. Yeah. Sundi: No DCU shout out. Nathan: Oh yeah. Totally. Sorry. I forgot about that connection. Uh, yeah. I lived in DC for 10 years. I founded the DC Elixir meetup, which you now run, which , was really cool to sync with you on that. It was very fun. Sundi: Yeah, it's always fun when you get to meet somebody who's passed the torch to you, when you weren't expecting it at all. Nathan: Yeah. Sundi: and Xiong, where are you calling us from today? Xiang: so I'm currently based in Berlin. , previously I, I grew up in China and I live in Hong Kong, but I've been in Berlin for six to seven years already. It's quite a long time. Sundi: Cool. Why don't we get started with our paths to, , well, maybe our paths to Elixir. I know Remote is an Elixir heavy company and we've, we've, I think we've had some folks on from Remote before. Yeah, Owen? Owen: Right. Uh, and my mind just went blank with names Sundi: It was Cassidy and Tobias, I believe. I don't remember if they're still there, but, that was a fun conversation. I remember a lot of rabbits for some reason. Tobias, I think, has rabbits. Xiang: both of them have left remote actually. Sometime ago, Sundi: Yeah, it's a changing world. Xiang: we always Miss Toby's rabbits. Sundi: Yeah, the rabbit thing was, real fun, because we gotta, see them on the podcast. I always love when we get to see people's pets. , but, yeah, Xiong, how did you find Elixir, and what has your work been like in Elixir so far? Xiang: Right. So, so I actually started my programming career in Ruby. But I always wanted to do functional programming. And my undergrad research was related to functional programming languages. And at one program meetup, so I went to the local functional program meetup at that time, and somebody introduced Elixir. And I was, like, I felt it was the perfect combination between pragmatism and functional programming. So I was really interested in that. And then I read programming Elixir and programming Phoenix. I think those books only just came out around that time. And then I went to Germany, uh, I did a master's degree in Germany. There was actually a professor who wanted to build a kind of an online system for multi participant experiments. So like, they would talk to each other, it's kind of like a multiplayer game. And Elixir, I thought, uh, Phoenix Channels was actually a perfect fit for that. So I built the whole thing in Phoenix Channels. And it went really well. And after I graduated, I just looked for Elixir jobs, and there was a concentration of Elixir jobs in Berlin. So I just started working at Elixir, and I've been doing Elixir ever since. Owen: Nice. And Nathan, aside from starting Elixir, Washington, DC meetup, I assume you were already doing Elixir and some to some degree at that point, or was that aspirational? Nathan: Yeah, that was totally aspirational. so I've been part of the, I guess I've been part of the community. I've been going to ElixirConf for like eight or nine years now, but, uh, I will be starting my first Elixir job on Monday. So excited about that. Sundi: I think this is another one of those moments where we, like, have to insert the, like, Yay! Yay! Owen: right. So I'm curious, before Elixir, what's your programming history, or even pre programming history? Nathan: let's see. I was, I never know where to start, but, mostly professionally. So I've been out there for 15 years, career wise, and most of it's been in Ruby. , on one came, came by that through a weird situation at work where they wanted a new app and they wanted me to do it in Java. And I was like, eh, I'm okay. You can let somebody else do it. And they're like, well, why not? And I said, well, I just don't like Java. Like I was already doing Java, but it was already in an app that worked. So I didn't have to like start it from scratch. , so they let me use J Ruby. So that was my intro to Ruby. And I've been there most of my career up until Monday. Owen: Awesome. So the topic for today is the actor model, and we're comparing notes between the implementation with Elixir, which really inherits. Directly from Erlang and Erlang virtual machine or the Beam VM. And we're comparing notes with some other languages. I think Clojure, Ruby are also going to come up here. It's great that we've, you guys have more experience across these languages. I can speak more to Elixir, but, uh, I'm kind of curious. We'll go with, Xian. can you give us like a high level description of what an actor is or what the actor model kind of describes in these systems? Xiang: Yeah, sure. So, first, I guess, , a disclaimer that I'm, I'm, I don't think I'm an expert in Clojure. I did this talk at Exeter Conf last year because I was interested in the topic. I guess it's always the case that if you're interested in a topic, you submit a proposal. And if it's accepted, you have to really look into the topic to learn it. So that's just one way for me to share, share my findings. And yeah, I can't guarantee that everything I say will be correct about Clojure, but I'll try my best. . When we talk about the actor model, I guess the fundamental aspect of the actor model is really that actors, they don't share any memory with each other. read a description, quote unquote, actor model is more object than OOP, because the actors really talk to each other by pure message passing, and each actor cannot modify the memory of another actor. Yes, if you come from a more conventional programming language background. This will be pretty new because in all those languages you work by modifying some shared memory and you may spawn up multiple threads, but they can all access the same, area of memory and do work on that memory. But in ActorModel, we have to talk to each other by passing messages. And The Beam VM, what it does is , it enables running a huge amount of actors on the virtual machine and each of them is very lightweight. They have their own memory space and their own heap. And this enables you to maintain a very high level of concurrent connections. So for example, WhatsApp said that they can maybe run a million, connections on one single server. And that's something you can't easily do with the conventional program model. Owen: Right. So what appeals to me about the actor model and everything you said, there was true. What I like about it is , it models the real world human communicatiOn, right?. So it makes it a little bit easier to kind of grok how a system is supposed to respond or act, at least. Xiang: Yeah, exactly. Owen: cause like when we say actor, we're not thinking like Timothee Chalamet, we're talking about like people who perform actions in a system, right? So, if we think of I don't know, a store, right? There's a cashier and there's a customer. The customer brings some goods to the, , counter and then, you know, they're both communicating, but they can't read each other's minds. So that's kind of the memory sharing part of it. You can't like share your brain cells with someone else yet. But you can communicate by talking. We communicate by sending letters, post it notes, Slack messages. We used to do tweets and that's, you know. Just a few examples. Xiang: I guess one theoretical benefit of that is that it makes concurrency much easier to reason about because you only need to look at the messages and how each actor handles the message instead of who first modified this piece of memory and who later and what they did, if there's some kind of race condition, things like that. Owen: right. Sundi: Nathan, did you have anything to add to that definition? Nathan: sure. I actually think the conceptual action of the actor model, like the, the conceptual ness of it, the human aspect of it is very similar to oo in that it doesn't, really lay out the specifics of what's happening under the hood, but it gives you a way of wrapping your brain around it. You can start to build components and, and. Um, reason about how something works rather than trying to build your own reasoning just because you have access to a thread or a process you're, you know, if you have those, like, that's what Ruby usually has, or has had, historically is just direct access to this concurrent thing. And then you've had to figure everything else out past that, , where actor models provide you a conceptual model around how to handle your data, how to handle. Asynchronous code, how to send messages back and forth. Owen: That's a great point. I was introduced to the actor model through Elixir and Erlang. And I think I probably did at least in the beginning, think of it as a functional programming feature, kind of like immutability. But I think it is great to be reminded that , it's a concept, like a way of representing essentially state. in a large system and scalability, and that is not exclusive to functional programming languages. So I think some of the things we're starting to see are some object oriented languages, introducing actor model concepts and features. I think Swift in the past few years, from what I've heard, I've never written a line of Swift, but I've heard that there's an actor model. Something in Swift now. So I don't know if that's like a module or how they represent it there, or if it's threads or processes and that kind of thing, but, , what are some other languages that we've heard of that have added or experimented with actor model outside of elixir and erlang? Nathan: I think Go has one. It's called Proto actor or something like that. I'm more familiar only by name. I just know what exists is Akka and that exists both for Scala and for Java. So it's a JVM library. Xiang: Yes, the more natural way to do concurrency in Go would be to use CSP, but you can definitely implement actor model also using this because they share a lot of similarity and it's kind of two sides of the same coin, actually, which I guess we'll also get to talk a bit later. And from what I remember, Dart probably also has some implementation of the actor model, which is pretty interesting. The language never really took off in popularity. Sundi: Could you define CSP for those who don't know what that is? Xiang: Right. So CSP and actor model, they kind of had a similar origin. And from what I understand, they actually evolved with each other and also took a lot of ideas from each other. And CSP is short for communicating sequential processes. And what actor does is, the processes are first class, and you have to identify the processes by name and pass messages to each other directly. But in CSP, you create channels, and channels are first class inside of the processes. And multiple processes can be readers to the channel and can be writers to the channel. You can configure different topologies. But the important thing is that you create a channel, like in Clojure, you can call async, you can return a channel from a function, and you can hold on to the channel, and write messages to the channel, and then read messages from the channel. Yeah, Owen: So it's kind a pull or a store of or like a message bus almost. It contains the state and then everyone kind of checks in or contributes or gets in set state from the channel. Xiang: definitely you can, that's one way to look at it, like if you draw a graph of the CSP, of the channels, and the readers and the writers. It can sometimes look like Q in RabbitMQ, for example. But one difference I would say is that, as an example in Clojure, the channels cannot have infinite lengths. So, by default, both reading and writing are actually blocking operations. So if you write something to the channel, you have to for somebody to read it, and the channel cannot be operated on by any other process. You can also have other types of channels, like you can specify a certain buffer size, like you say the channel can hold 10 messages, then a writer can write to the channel and forget about it until the channel is full. I guess in a system like RabbitMQ, you don't have this concept of a limit on the number of messages. And another difference I would say, , when you think about a limit and the blocking, Aspects of a, of the channel comparing with actor. In actor you can never block a sender. You always can send messages to a mailbox. And it's a, it's a reader of the message that has to take messages out of his mailbox. And the mailbox actually has no limit and it can overflow. Like one way to bring down a beam system is to overflow mailboxes. Owen: right. So now we're speaking my language, the process and the message box and in queues, right. I wasn't on the episode, but I'm absolutely confident that this is overlapping a little bit of the conversation we had about garbage collection. Because I think Akka came up in that conversation as well. So, in Elixir and, the Beam and Erlang, instead of using threads and sharing memory, we use processes. As long as we're working within the Beam VM. And I'm kind of curious, Nathan, are you, A, am I just wrong? Are we ever using threads? And B, Are there kind of peculiarities about how we implemented ActorModel and Beam compared to using something like Channels? Nathan: I think conceptually you're correct. We're not using threads directly. I do think through the scheduler, you're eventually landing on a thread. so you are using threads underneath the hood. But the beam is doing that for you. It's scheduling it. It's telling you. Which thread to run on at what time, meaning it can bounce between threads. , this is actually something that Ruby's introducing in the 3. 3 that usually gets released around Christmas. They release a new version. So 3. 3 is going to have what they call, many threads, but it's an M to N thread scheduler. And I think it'll be a huge win , for the same reason it is on Elixir and the Beam. Owen: And it's kind of wild to think about. I think this goes back to the eighties, right? I, I wasn't writing code back in 84, you know, as soon as I was born, but, , I think pretty early on was working with processes, concurrency, and building on top of the actor model or using the actor model to represent The solutions in the code base there and, so I know this comes up pretty regularly, but I think just for historical context, the problem they were solving then, was, , writing, a programming language and a virtual machine that could operate hundreds of thousands of phone calls within a large scale phone switch. And also, a network of phone switches throughout Sweden, of all places, that company was Ericsson. Some of the, engineers were Joe Armstrong, and my mind is blanking now on other names. I'm having a bad name day but anyway, so that work on phone switches it might have seemed niche, , back in the day and the throughout the nineties, but it's really starting to shine as, we're living in the internet age and talking about distributed networking and running applications on multiple regions, multiple machines, and even just having multiple cores within a machine to distribute our work. Xiang: Yeah, that definitely has been a trend and JVM is actually introducing something similar. So previously on JVM, you only have real threads that's the same as operating system threads, but they are also now introducing lightweight processes, something called Project Loom. And actually it finally made it into the formal release of JDK 21, which was released a few months ago. And I would say from my understanding, this is something similar to what Erlang is doing essentially, , you can have a lot of virtual processes, which are not real operating system threads, but the scheduler would then know when to, how to stop and continue them. And they're all very lightweight. The problem with traditional threads is that they're really heavy. So they're good for CPU heavy work. But if you want to do some IO heavy work, like having a web server, serving a lot of requests and the blocking and unblocking of those threads are very costly. And that's where Erlang, where Beam shines, and JVM is actually now also bringing it onto JVM. So this is definitely a trend we can see across the languages. Sundi: So Nathan, could you give us a little bit of a rundown on how the actor model is represented in Ruby? I feel like we've touched on it a little bit, but maybe not too in depth. Nathan: Yeah, the actor model is represented by a class in the standard library called Ractor, which is just short for Ruby actor. It's going to look a lot like a process in Elixir. I think Elixir and Erlang very much inspired and, , was heavily used in building this. It has, The same kind of push, , semantics. So like send and receive, and then they also introduced a pull system. So a, process can yield a result and then later somebody can come by and take it, which is, so they have a push semantics and pull semantics, which is interesting. , they had to introduce a shareable. State. It's kind of a definition of whether an object is shareable or not because Ruby wasn't thinking in actors until recently. So most of the decisions they made weren't copacetic with actors and this isolated state by actor. So they've introduced shareable as a, way to know if something can be shared across objects, which mean is usually just like, you know, dot frozen. An object is completely frozen. You can still send via copy, like a deep copy, or if you're passing an object along, you can also just give ownership over so you can just say, I no longer want this. You can have it. , I forget what that's called, but, , you're just transferring ownership of that object to the actor itself, which should be faster, but is an interesting semantic as well. But that's mainly it. I think the other thing to think about here is it's, it looks a lot like if you've used Ruby, it looks a lot like a process or a code block, , ractor dot new, you give it a code block and it will run that code block. Pretty standard stuff again, looks very similar to like an elixir, process, not necessarily gem server. Although I think you could get there. I've gotten pretty close. Owen: All right, before we start talking about the fun toys we have in elixir and gen servers and so on, I'm kind of curious. And it's okay if we haven't dug into all the, the VMs here, but I'm curious if you know, some differences between the beam VM and the Ruby VM. Nathan: , for one, the Ruby VM is very OS centric. So traditionally, , threads have been threads, processes are processes. Your ability to use recursion is based on the number of things you can stack. You can just look at your console and I forget what the command is, but you can look at the number of user space stack, and it'll tell you how far you can go in your recursion before you're going to get like a stack overflow error. Beam I think is much more abstracted from the process. Like it has its own memory management. I'm trying to think specifically like recursion is a great case there like recursion can go on forever because it's not using the OS's stack representation. It's doing its own in its own memory store. So you can run kind of non optimized non tail optimized, Recursion in Elixir for a long time, like it will, you can get away with a lot. Eventually you will hit it, but it takes way longer. We've already talked about like the end to end like that, that scheduler between like an Elixir processor versus an actual thread. So I think Elixir has really stepped away from kind of, they want to manage the stuff all themselves rather than say, well, the OS is going to give it to us. We'll just use theirs. Owen: Yeah, we're kind of spoiled with the Beam and I'm curious, , Xiong, I think you've worked a little bit more with the, JVM, the Java virtual machine. Of course, if you have any, thoughts on the Ruby VM, , throw those in as well, but I'm kind of curious what you know about the JVM too. Xiang: Yeah, sure. I guess first I wanted to ask Nathan a question because I haven't worked with Ruby for a long while. I remember Ruby has a global interpreter lock. But over the several years, I guess they're having a lot of improvements over the concurrency solutions in Ruby. So how does Ruby handle that? And what are the some solutions that overcome this limitation? Nathan: the global interpreter, a lock is still there. Honestly, I, I believe they're working to remove it. I think actor model is very much in that vein of trying to get past the global interpreter lock. , but it's still there for things they can't genuinely know or easily know. What can be run synchronously or asynchronously, you know, so they will, I still, the gill is still there. It's now called something else like the JVL or global VM lock or something like that. But, Xiang: Right. I guess if you, if you want to run tasks on different threads, you can launch the threads, but are they still interpreted by the same interpreter? Or can you run the threads in the same time? Nathan: Right now, Ractors are threads like it's a one to one. When you start a Ractor, you are starting a thread. Uh, the end to end. Xiang: Right, so it's actually a web OS thread. Nathan: Yeah. that'll change with Ruby 3. 3, but, , right now it's just a conceptual overlay of a thread. Xiang: And I guess for each of the threads you start, it can only have one lock for that thread. Like, even though it's called a global interpreter lock, it doesn't mean that you cannot launch multiple threads. It's just that for each thread, you can only do one thing at a time. Right, got it. Owen: Now, have you spent a little bit of time with the Java virtual machine and as I come up in your work with Clojure and, and maybe writing a little bit of Java. Xiang: Yeah, sure. And so I guess first a disclaimer is that I haven't really worked that much professionally with JVM. I just found CSP to be really cool and I wanted to look into that and do a talk on that topic. And I guess the best way to learn something is to submit a talk and then you be forced to actually learn the subject and share it once it's accepted. Yeah, I'll try my best to present it. So. On the JVM, traditionally you also have threads which are tied to OS threads, so they are not lightweight processors like what you have on the Beam. And I've looked into Akka, which is a popular actor model implementation on the JVM, and it works on Scala with Java and also with Clojure. The way Akka works is it cannot really Go around the restriction that you have to run on real threads. But what I do is basically they will pause the thread and swap this virtual actor onto the thread. And swap it off again if it's blocked. So in that sense, I guess it's kind of similar to what Nathan mentioned, what Ruby is doing. And, but it's just, it just cannot be as effective as, what the beam does, and it also supports message passing. But the thing is that because JVM is not built from ground up to have no shared memory. I think if you pass something to another actor, it can still modify this. Memory, just like what you can do in Ruby while in on the beam is just impossible. You always make a copy of that scene when you pass it over. Owen: What could go wrong? Xiang: Yeah, that's like not really the actor model, right? When you can actually modify that scene. Nathan: I actually think, so totally agree with all that, but part of what makes BEAM powerful, even if you can isolate objects, which Ruby is trying to do, you have these semantics around whether it's shareable or not, or who owns it. And that's not anything that pops up in Erlang or Elixir because it's all immutable. So once you have it, it's never going to change. For the thing you're pointing at, right? The thing you have access to, you're going to get a new variable with a new value if you quote, unquote, change it, which I think totally helps with that, like shareable conversation around who can see what. Xiang: Yeah, exactly. I feel very spoiled actually being able to program with Elixir for so long because the concurrency model is just so straightforward. I mean, actually reading this book, Seven Concurrency Models in Seven Weeks, which is super interesting. I totally recommend it. And I see, for example, how locks, how mutex is used in a relatively large program. I actually haven't, have never needed to deal with that in, in my professional career. I've done that in university, but I've been doing Elixir most of the time, and it seems like it could be a real nightmare if you're not careful. But with Elixir, you don't have to worry about any of that. Sundi: I'm a little distracted because I've never heard of this book, and I wasn't sure if you said seven programming languages in seven weeks, but is this a familiar, or is this a common concept, in seven weeks do something? Xiang: It's a series by Pragmatic Bookshelf, I think. So, first they did Seven programming languages, which probably include Elixir. And they thought it's also a good idea to do concurrency models. Why not? So they also came up with this book, and I think also there's some, there's a book about seven DB models or something, and there's also a book about several more languages in seven weeks. I really enjoy the concurrency model book, and, it introduced seven different concurrency models. There are still quite more, but yeah, if you, if you don't actually read such books, and you work with one language, it's hard to Get perspectives on different concurrency models in different languages Sundi: Yeah. We'll have to check that out. Coming to the practical side of things, how would you say that we implement these features in Elixir using the actor model? what are some of the tools that we have at our disposal? Xiong, if you could take us into that a little bit. Xiang: Yeah, I guess the actor model is so essential to the beam you don't really Implement the actor model because everything you do is based on the actor model But what beam offers what Aaron offers is OTP which is kind of a set of abstraction on Top of the beam which makes your job much easier. While reading this book, I was just reminded how you can manually spawn processes manually, link them together, manually monitor processes, but this is not something we do in your day, in your day-to-day job because in OTP you have all the obstructions, like gen server agents, and you would just write a nicely encapsulated gen server with all the behaviors, all the callbacks that should define for the gen server and the the runtime, the OTP will handle the rest. Sundi: So, what's interesting is, this is top of mind to me because, , I'm actually interviewing right now and, what is a genserver? It's like a pretty common question that people like to use as a gotcha. Not that we do gotchas, but, um, can you, for us, define what a genserver is? For those who are listening. Xiang: Oh, that can be a difficult question. Um, Owen: It's the best kind. Xiang: Yeah. One funny anecdote is that at remote, our interview, our coding challenge used to involve GenServer and somebody decided to remove it because they said we never use GenServer. Why, why do you test people on the GenServer? But yeah, I think it's a mark of whether you're an experienced X developer or not, of GenServer. So I would say GenServer encapsulates a set of behavior for an actor. Like, you would usually, there are some defined, functions. There are some functions you can define for GenServer, like handleCall, handleCast. A cast is a case where another process calls into this GenServer, but they do not expect a response. And a call is where a process calls into a GenServer and they expect a response. And would also write certain functions such as how to initialize a GenServer, what are the initial arguments, and how upon each call or cast you would get the argument and update the internal state. So each actor, as we mentioned, has this internal memory space, and it's responsible for maintaining its own internal state. And to update internal state, there needs to be some messages. And basically, in the handleCore and handleCast functions, you define how you would handle each type of incoming message, and update the state and response to the request. And you would also define a set of so called API functions, I guess that's one way to describe it. It's basically how other processes can easily call into this GenServer. So one simple example of a GenServer is something to keep a running score, for example. And you may have an API function called setScore. And some, let's say, maybe a referee for a match or something, it gets a score and it tries to Keep it in the store. It will call the setScore function. And somebody else, when he tries to fetch the score, it will call the getScore function. And the genserver will then return its internal state, which stores the score to the scorer. Owen: Awesome. So, that's a great explanation of Genserver to kind of recap. I think of Genserver is kind of like a, like you could do everything that Genserver does with the raw process. But it's essentially an abstraction on top of a process. It gives you those callbacks, like you said, for handle info, handle call, handle cast, and maybe a few others and like how this, how the process should start. It kind of helps you define that as well. And so it can be run under a supervisor. All that I think is pretty, pretty well streamlined. and also a gen server maintains its own state. Like you said, you can send messages to the gen server to update its state or to fetch. Some or all of its current state as well. And then Nathan, I'm kind of curious whenever we talk about shared memory and other languages, there are some escape hatches, like even though we don't have quote unquote shared memory in Elixir, your threads. Under the hood are not necessarily sharing memory, but if we have multiple processes that want to share some state and not necessarily duplicate the state in each process, I know there's one, one particular tool that comes to mind, does anything come to mind whenever you think about, uh, kind of sharing state between processes? Nathan: I'm thinking of like agents, but that's just another process holding state. There might be something I'm not familiar, I want to say like a, what is that? ETS table, stuff like that? Owen: ETS! Bingo. Yes. Agents are a similar abstraction on top of processes. They're similar to gen servers. They've got slightly different limitations., there's actually a great book I want to call out. So, elixir in action, past friend of the pod, uh, wow. Names today. I don't know what's happening. Uh, Sa?a. Sa?a Juric. Sasha Shrek. Yes. Oh wow. Okay. Like faces are all I've got, but yeah, so great book by Sa?a. I think even during that book, like he can't, he walks into the process of like building something with the process that looks a lot like a gen server, then using the actual gen server module and that kind of thing. And then part of that was also. Using things like ets tables. So Erlang term storage is what that means. For anyone who's coming to this podcast outside of the Elixir space, what's great about ets is it's like an alternative to something like Redis, or a fast memory kind of cache store for your application. So typically that would be something you have to pay extra for or have a separate machine that does that. Then you've got to send messages back and forth between your nodes. , it's really nice to have all of that on the same machine, right? So, A, you don't have to go over the network, you don't have to deal with network faults. And also, you can store all the data in its raw Erlang form. So you don't have to serialize things into JSON, out of JSON or XML if you were going to be. Very peculiar. And so that's, is great at kind of giving us a tool that all of our processes that our machine can get that data very quickly and at a very low cost. Sundi: Nathan, I was also wondering, , when we first chatted about bringing you on to have this conversation, you mentioned, , building out, , Elixir like behavior in Ruby, which is funny when you think about, , the history of, Ruby, of course, inspired a lot of Elixir, Elixir reads like Ruby, and so on and so forth. , could you speak to what that was, within the space? And I think you, you had just come from giving a talk about it as well. So I guess if you could go into what that was about. Nathan: I've been trying to basically build Gen Server in Ruby. It's been a fun process. I'm not done with it. It's still very beta. I did get it working, , to some extent. So in that it felt very, to, Owen's point, right? Gen servers are just abstractions on processes. You can totally do that here. And even though they're calling their process an actor, you could build something on top of it that acted very much like a gen server. Which is pretty interesting. There are some caveats to that. Like. Supervision is very different in Ruby. They technically have it, but I haven't yet figured out how to run supervisors, because of the way they have supervision working in actors. I mean, you can do it and it can be done like, , you can start a loop, you can create kind of cast and calls or, , as Akka calls them, asks and tells, um, very similar to your casting calls or exactly like just different names. And yeah, you can totally loop on that and have your kind of loop catcher that just sits there and knows what to do based on, on the message that it receives. And then, goes and does the thing and then it comes back and looks at the next message. I think, I tried too much to use Elixir semantics. So some other things that came into Ruby around the same time are, they actually have pattern matching in Ruby now in a, Specific way, it's not built all the way through in the language, but you can use it in case statements, or switch statements. You can switch on a pattern match rather than on like a comparison or a true false. and you can do assignment via pattern matching, but it looks very awkward. I think they call it like arrow assignment. Anyway, I, I was using a lot of that because that's what I knew what to do with gen server. , and I showed it in the talk I gave at the Austin Ruby meetup and Rubyists were like, WTF, what is this? I have no idea how to read this. And I think , I need to go back and rewrite that because one of Ruby's powers is metaprogramming kind of stuff. And so you can, define a method and then call that later. So just being given a method, you can then put the method name together and use that to call. So rather than using pattern matching, we can use some meta, capture stuff. Xiang: Talking about readability, I realized when I was looking at Akka's implementation, I saw that I actually quite like Akka's syntax. So the syntax in Elixir and Erlang really takes getting used to if you're not familiar with that. And one thing you have to also remember is how the API functions to define a GenServer, actually meant to be run in another process. So the self, for example, if you call self in that kind of API function, you're actually referring to the caller process. And things like that, it takes a while to get used to. And I realized Akka actually also offers some additional functionality, like it offers something called Router, which is also an actor, but it can define different kinds of topology, like round robin routing, to route the message to some actors down the line, almost like something you have in RabbitMQ, while I guess in Elixir and Erlang you have to kind of build it yourself. Like you can build everything, but there's some niceties there. Sundi: Yeah, it's, it's so hard to try to cram these conversations into an hour because I think We could have a whole series on this to really dig in and understand these kinds of concepts. I, I know, Owen we've had gen server conversations over the past few years, a few times, and every time we talk about it, it's still like. Not quite fresh, but there's always something a little bit more to add to the conversation. So you both for giving us a chance to dig in a little bit more. I'm wondering if you have any takeaways that you hope listeners will gain from this discussion about GenServers, Beam Actors, and concurrency just in general, through different programming languages. why don't we start with you? Xiang: first, I would say, if you want to understand GenServer, understand the actor model really well, , Francesco Cesalini, , founder of Erlang Solutions. He also has a great book, uh, Designing for Scalability with Erlang OTP, where you actually get to build a lot of the things like, , GenServer from the primitives. So you get to see exactly how that works under the hood. As for the takeaway, I think concurrency sometimes can feel like something that's removed from your day-to-Day work. Like when you directly use a framework to, write your program. You might not think about the concurrency model underneath, but sometimes it's actually relevant to the problem you're solving. And if you have some certain knowledge, it can greatly help, choosing the right solution for the right problem. I just talked with somebody who. was tasked with building the whole system with Node. js, but there's actually a payment component. And he realized that Node. js probably is not a good idea, and he chose Elixir to build that part. And three years later, it's still running perfectly well. So if you had the understanding of how, what are the strengths of the actor model, and what are the strengths of some of the other models. I read a blog post by Discord a few years ago. About how they actually encounter some difficulties running their servers with hundreds of thousands of participants. There's some heavy tasks they have to run, CPU heavy tasks. And they were writing that in Elixir, but Elixir is not such a good fit if you want to run a CPU heavy task. So they actually combined that with Rust. So they would delegate such tasks to Rust while they still run the majority of the program in Elixir. I guess we understand the properties of those different languages and different concurrency patterns. You can make the better choice when you're faced with a relevant problem. There's a difference between the JVM and Beam, one characteristic of the beam is it tries to ensure that every actor, every process gets a similar amount of CPU share. And the way it does that is when every process reaches 4, 000 in its reduction count, and you can think about reduction as something similar to one function call. So once a process reaches 4, 000 in reduction, it will get put back to the scheduler queue and another process will take its place. , in this way, every process really gets a similar amount of CPU time as possible. And as you can imagine, this is great for a high concurrency situation, but this is not so great if you have a CPU intensive task, like number crunching, and you want to run the task for a sustained period of time on one single process. And this is where the thread mode of JVM, which is closer to how operating system threads operate can come in useful. And this also ties into the excellent point raised by Owen earlier, that when he first started with Elixir, he thought the no shared memory model property of the active model is a natural consequence of functional programming, which later turns out to actually not be the case. And this is also my journey as well. So, here we didn't get much time to discuss the more conventional concurrency models of Clojure. , but actually a CSP, a core async, that was a later addition to the Clojure language. The more, let's say, native way of Clojure to deal with concurrency, it's still shared memory concurrency. And Clojure has this whole set of tools to do that, like ref, atom, agent, var, part of which is based on the idea of circular transactional memory, which we will not have time to discuss here. And they are also not unique to Clojure. And a lot of other functional languages like Haskell have it. They are supposed to make a shared memory concurrency quite easier and quite more pleasant than the conventional imperative ways of like locks and mutexes. So I would say that I would encourage you to just read a book Seven Concurrency Models in Seven Weeks if you're interested in this topic. There's so much to learn that cannot be crammed into one episode. And even if you don't get to work with all those different languages at your work, it's intellectually very satisfying. And it will most likely aid you in the future to become a better programmer. Sundi: Yeah, cool! And Nathan, what about you? Any takeaways that you hope folks gather from this conversation? Nathan: I think from the Ruby side, one thing I'd like to say is let's get on 3. 0. And 3. 3 once it's out, just doing some back of the napkin math on Ruby gems. There's still like half the. Rubyists are still on 2. 7, so it's really hard to do anything with actors until, it's going to be useful for most people. , so, update those Ruby apps, update those Rails apps. Would love to use this. I think it's a great concurrency model, that will help all Rubyists be able to wrap their heads around. Asynchronous code, for elixir. , it's fun to think about how they came here and how they got to this. And what Owen said, it's very pragmatic. It's one of the things I love about elixir. , it's one of the only functional languages that I know that wasn't built on academics, but was built on solving an actual real world problem. And so they use the parts that were necessary to solve that problem, which was really interesting. And I thought a good balance of not getting too weird or academic, Owen: Right on. Well, it's nice working in a non academic language as a non academic thinker, right? Xiang: there was just a post about how somebody gave up programming Haskell after some years because of all the, way too academic, way too formal aspects of the language. I guess the language was never designed to be used in industrial settings. Design is a research language to push the extremes and, have ideas that can be then later used in other languages. But Elixir really impressed me with a focus on, with a combination of pragmatism and functional aspects. Like, it, it's not, , statically typed, but you also don't get the same issues as you have in JavaScript. And it actually just all works out with strokes and pattern matching and all that. Owen: All right. Of course. Coming soon to Elixir, probably. Types, types, Types, types, Nathan: boo, Owen: Alright. Xiang: Remote is actually sponsoring that project, but, uh, I don't think we should put so much hope on that. I mean, if we're a nice addition, but Elixir already works super well with, even without it. In the Clojure world, they don't use types at all. They, they're super dynamic and they're kind of proud of it. Nathan: amen. 2. 7 to Owen: Right. Proudly dynamic. That's what we think. That's what we're saying here. Awesome. Well, you've heard it here. Go upgrade your Ruby 1. 0 projects Nathan: 3. Owen: to 3. 3. That'll be real easy. So you can get on this actor model so you can get your Viola Davis and your Timothee Chalamet's and your Matt Damon's into your Ruby code. All right. Well, I really do think we could, we could talk about liveview and an actor model and like JavaScript and whether that'll ever have actor model, there's so much more we could talk about here, but we have maxed out our time. Our queue is overloaded. So I think we're going to go ahead and wrap it up. any final plugs or requests for the audience? We'll start with you, Xiang Sheehan Xiang: I, have, I have my personal website at, uh, xiangji. me, that's X I A N G J I dot me. but I haven't updated that in ages and I probably should have post, uh, put up the blog post summarizing this talk I gave about, concurrency model between action model and, uh, CSP. Maybe I'll finish the post by the time this episode is up. Uh, I'll try to do that. some plugs, uh, I would say. Something interesting I've been reading recently, actually some, some books about, so they're actually having some classic books about Conti behavioral therapy. Um, I heard of this idea before, but I never looked into it. And I just read a book feeling better by David Burns. And it's super interesting because I guess when I grew up, I was, I never got any like much knowledge on this front. But I realized when I read a book, like there are a lot of practical tools, let's say, a kind of, ways to think about certain things that can be super helpful. It's not just for the clinically depressed, even though cognitive behavioral therapy is supposed to be a therapy for depression. Everybody would have ups and downs in their life, and a few of the methods presented in this book can be helpful to everybody to manage their mood better and live a happier life. And so I definitely recommend it. Owen: Awesome. We'll make sure we get a link to that in their show notes as well. Cause I, I know we, as much as we like to think we're walking on cloud, sometimes, you know, ups and downs are real. And then Nathan, any, final plugs or a request for the audience? Nathan: you know, I'm just starting on Monday, so I don't have too much to say, but I do know the score is hiring. So, I think that's a rather unique situation at the moment. So if you're looking, come and come and talk. Xiang: same for remote or hiring all kinds of positions. Owen: Awesome. That is great news because, you know, this year was a little bit bumpy. Speaking of ups and downs. So. All right. Well, that wraps it up for this week's episode of Elixir Wizards. Xiang has a blog article to write. Nathan's got to get started with his new job and Sunday Pokemon Go. All right. We will be back next week for more. Thank you. Xiang: Thank you. Outro: Elixir Wizards is a production of SmartLogic. You can find us online at smartlogic. io, and we're at SmartLogic on Twitter. Don't forget to like, subscribe, and leave a review. This episode was produced and edited by Paloma Pechenik for SmartLogic. We'll see you next week for more as we branch out from Elixir,