Paul Smith of GitHub discusses the crystal programming language and the lucky web framework with Jeremy Jung. They cover the tradeoffs of object oriented vs functional programming; creating and debugging macros; detecting nulls; building a web framework with strict compile time guarantees; conversational elm-like error messages; the benefits of fast rendering times; writing templates with crystal instead of HTML; choosing what to include in a web framework; and crystal’s ecosystem and community.
Show Notes
Related Links
Transcript
Transcript brought to you by IEEE Software magazine and IEEE Computer Society. This transcript was automatically generated. To suggest improvements in the text, please contact [email protected] and include the episode number.
Jeremy Jung 00:00:52 This is Jeremy Jung for Software Engineering Radio. Today Iím talking with Paul Smith. Paul is the creator of the Lucky Web Framework and he currently works at GitHub. Today we’re going to talk about the Crystal programming language and the Lucky web framework. Paul, welcome to Software Engineering Radio.
Paul Smith 00:01:07 Thank you so much. Happy to be here.
Jeremy Jung 00:01:09 There are a lot of languages for software developers to choose from. What excited you about Crystal?
Paul Smith 00:01:14 Yeah, that’s really interesting because when I first saw Crystal, I actually was not interested at all. It basically looked like Ruby to me. And so I just think, okay, so it’s a faster Ruby. And typically if I want to learn a new language, I want something that feels really different that pushes the boundaries on things. I started getting more interested in compile time guarantees. I worked at Thoughtbot previous to GitHub and previous to Roku and people were starting to get really into typed languages. Some people were starting to get into Haskell, which is like, you know, the big one that I guess is probably one of the more type safe but also hard to use languages. But also Elm, which has a good focus on developer happiness and productivity and explaining what’s going on. And as they were talking about how they were writing fewer tests and it was easier to refactor, it started becoming clear to me that that’s something I want.
Paul Smith 00:02:15 One of the things somebody said was, if the computer can check the code for you, let the computer do that rather than you, or rather than a test. So I started to get really interested in that. I was also interested in Elixir which is another fantastic language. I did a lot of work with Elixir. I built a library called Bamboo, which is an email library, and another called Ex Machina, which is what a lot of people use for creating test data. So I was really into it for a while and at first, I’m like, wow, I love functional. And then I realize like I can do a lot of, lot of the stuff I like about this I can do with objects. I just need to rethink things so that it uses objects rather than whatever random DSL.
Jeremy Jung 00:03:03 Because I mean, when you think about functions, right? Like you’ve got this big bucket of functions and you got to pass in all the parameters, right? Whereas, you know, in a lot of cases, I feel like if you have those instance variables available in the object, then the actual functions can be a lot simpler in in some ways.
Paul Smith 00:03:22 Totally. That’s like a huge focus and making the objects small so that it doesn’t have too much. But that’s how I began to feel with Elixir is that I’m like, I just have 50 ARDS and most of them I don’t care about. Like I want to look at what’s important to this method. It’s, you know, this argent, but with functions you’re like, which thing’s important is the first thing? Probably not. That’s probably just the thing I’m passing everywhere. And so I like that ability to kind of focus in and know like this object has these two instance variables everywhere.
Jeremy Jung 00:03:57 Yeah. It’s kind of interesting to get your perspective because it seemed like you were pretty deep into Elixir if you had created Bamboo and Ex Machina and stuff like that. So it’s kind of interesting.
Paul Smith 00:04:08 Yeah, I was like way gung ho. And then I started missing objects and luckily with Crystal and Ruby you still get a lot of the functional stuff. Like you can pass blocks around as functions. You can use functions, but it’s not the other way in Elixir, you can’t use objects.
Jeremy Jung 00:04:24 Yeah.
Paul Smith 00:04:24 It doesn’t exist.
Paul Smith 00:04:26 And then the type of safety. Yeah, I’m just like, I still run into so many errors, you know? So frustrating, I don’t want to ever do that. The main benefit I got out of Elixir compared to Rails which is what I had been using and still use a lot of, was speed. That was really big in terms of bugs caught about the same, mostly because it’s still a, for the most part dynamically typed language with very few compiled times guarantees. So I’d still get the nil errors, I’d still mess up calls to different functions and things like that. And so that’s where I ran into Crystal. It has the nice syntax I like from Elixir and Ruby. It’s also very very fast, faster than Go in some benchmarks. So it’s quick, plenty fast for what I need, and it has those compiled time guarantees like checking for nils. That’s a huge one. And it also makes the type system very friendly. So it does a lot of type inference and very powerful macros so you can reduce some of the boiler plate. And so that’s when I kind of started getting into Crystal was seeing Elixir. I still got a lot of these bugs that I was running into with Rails, but I like the speed, but I don’t want to use Haskell and Elm doesn’t exist on the backend. So I started looking at Crystal.
Jeremy Jung 00:05:51 And so it sort of sounds like there’s this spectrum, right? You have Ruby and you have Elixir where you don’t necessarily specify your types. So the compiler can’t help you as much. And then you’ve got Haskell, which is very strict, right? You have a compiler that helps you a lot and then there’s kind of languages in between, like for example Java and C# and things like that. They’ve been around for quite some time. How does Crystal sort of compare to languages like those?
Paul Smith 00:06:22 Yeah, that’s a great question because I did look at some of those other ones. TypeScript for examples is huge. Kotlin was another one that I had looked at because it’s Java but better. Basically that’s the way it’s pitched and so far, everyone that’s used it has basically said that. And also looking at Rust, what it came down to was how powerful was the type system. So Crystal has union types, which can be extremely helpful, and it catches nil. Java does not have a good way to do that. Kotlin does, but also Boilerplate and the macro system Crystals is extremely powerful. Elixir also has a very powerful macro system, but Crystal is type safe, which is even more fantastic. So basically what that let me do with Lucky was built even more powerful type safe programs. And we can kind of get into that once we talk about Lucky and how that was designed. But basically with these other languages, a lot of what we do in Lucky just simply wouldn’t be possible or wouldn’t be possible without a significant amount of work and duplication.
Jeremy Jung 00:07:34 You covered a few things there. One of the things was macros. What are macros?
Paul Smith 00:07:40 Yeah, this is like a confusing thing. It took me a while to get what it is. But in Ruby for example, they have ways of meta programming that are not done at compile time for most compiled time languages, compiled languages I should say. You need macros to deduplicate thing. And basically what a macro does is it generates code for you. The way I think about it is basically you’ve got a method or it’s a macro, but it looks like a method. It has code inside of it and it’s like you’re copy pasting whatever’s inside of that macro into wherever you called it from. So in other words, Rails has many like has many users, has many tasks that’s generating a ton of code for you. So that’s how Ruby does it. And Crystal has many would be a macro and it would literally generate a ton of code and copy paste that into wherever you called her. So it’s just a way to reduce boilerplate.
Jeremy Jung 00:08:49 So in the case of dynamic languages like Ruby, when you talk about meta programming, that’s having I guess a function that is generating code at runtime, right? And the macro is sort of doing something similar except it’s generating that code at compile time. Is that kind of the distinction?
Paul Smith 00:09:11 Thatís the way I look at it. There are people much smarter than me that probably have a more specific answer about what the differences are. But in my mind and in practical usage, that’s what it comes down to in my mind.
Jeremy Jung 00:09:25 Let’s say there’s a problem in that code. What do you get shown in the debugger?
Paul Smith 00:09:31 Debugging macros is definitely harder than debugging your regular code for that exact reason. It is generating a code. So what Crystal does, there’s different ways of doing this, but I like Crystal’s approach. It’ll show you the final result of the code and it’ll point to the line in the generated code that caused the issue and tell you which macro generated it. Now it’s still not ideal because that code isn’t code you wrote, it’s code that the macro generated. But it does allow you to see what the macro generated and why it might be an issue. Part of that can be solved by writing error messages and error handling as part of the macro. So in other words, making sure if you’re expecting a string literal, you can have a check at the top that checks for it to be a string literal. I wouldn’t use them by default, but it’s great for I think a framework where you have a lot of boiler platey things that you’re literally typing in every single model or every single controller. And that people kind of get used to, that’s well tested, that has nice error messages. In my own personal code though, I pretty much never use macros. They’re only in the libraries that I write.
Jeremy Jung 00:10:49 Another thing you mentioned is how Crystal helps you detect nils or knolls. How does the language do that?
Paul Smith 00:10:58 It actually uses union types for that. Some languages that have this, they’ll have an optional type, which is basically a wrapper around whatever real type, like an optional string or an optional INT and you have to unwrap it. The way Crystal does it is, you would say string or nil. And there’s a little bit of syntactic sugar. So you can just say string with a question mark at the end, but that gets expanded to string or a nil type. So then within that method the compiler knows that this could be a string, could be a nil, and there’s a little bit of sugar there where the compiler, if you say if whatever variable you have, it’s going to know that within that if, it is not a nil and in the ELTS it is, so there’s a little bit of sugar there as well. But that’s basically how they handle it. And there are ways to force the compiler. Just say, hey, this thing is not nil you can call not nil on it. That’s a little, I would avoid that because maybe the compiler’s, right? And it really is nil. Or maybe you change the method later and then it can become nil and you’re going to get a runtime error there. But it does have those escape patches because sometimes you just need the quick and dirty and you can if you need to.
Jeremy Jung 00:12:15 As long as you don’t tell the compiler that, then you will actually have a compiler error. If you have a method that takes in, let’s say some type of object or a nil and then you don’t account for the fact that like it could be nil, then the compiler actually won’t let you compile. Is that correct?
Paul Smith 00:12:35 That is correct. So for example, if you just had a method that’s like print email and it accepts a user or nil, I’m not saying I would do that, but let’s say that it does and you just tried within that method to do user dot email to print the user’s email it’s going to fail and tell you that nil does not have the method email and so you need to handle that. Yeah. You’re forced to either do an if or for example, you can use try, which is basically a method that says call a method on this object. Unless it’s nil. If it’s nil, just return nil. But yes, it kind of forces you to do that.
Jeremy Jung 00:13:18 And Crystal, how do you handle errors? Because a lot of different languages they’ll have things like exceptions or they may have result types. What’s sort of the main way in Crystal?
Paul Smith 00:13:31 I’d say I’d group it into two types of errors where you have runtime exemptions still because things do break. Not everything is in a perfect world inside your type system, databases go down, you know, Redis falls over or whatever. So you still have runtime exceptions. And then you have the compile time errors, which we kind of just talked about. But in terms of how those runtime exceptions are handled, it’s, I don’t want to say exactly the same as Ruby because there probably are some subtle differences but extremely similar to Ruby and that you’re not passing around errors. It’s so it’s not like Go where you are explicitly handling errors at every step. You raise it and you can rescue that error kind of like a try-catch in other languages and you can also just let it bubble up and rescue at a higher level, which I personally prefer because not every error is something that I care about and kind of forcing me to handle every single error everywhere means that it is harder as a reader of the code to tell which errors I should care about because they’re all treated as equal.
Paul Smith 00:14:41 So I like that in Crystal. I can say this particular error in this particular method I want to handle in a special way. And somewhere up above the stack I can just say anything else, just print a 500, log it, send it to Sentry.
Jeremy Jung 00:14:56 Yeah. So it’s very similar to, like you said, Ruby or any other language that primarily relies on exceptions. Like I think Java for example, probably falls into the same category.
Paul Smith 00:15:07 Probably. I haven’t used it in quite some time, but I imagine it would be similar.
Jeremy Jung 00:15:12 You had mentioned that that Crystal was like pretty fast compared to other languages. What are the big benefits you’ve gotten from that raw speed?
Paul Smith 00:15:21 The biggest benefit I would say is not having to worry so much about rendering times. In Rails, for example, you can spend a ton of time in the view, even though everyone says databases are slow, they’re not that slow. In something like Rails, active record takes a huge amount of time to instantiate every single record. So how does this play out in real life? You could, for example, in Lucky if you wanted to load a thousand records and print them on the page and probably do that in a couple hundred milliseconds maybe, which is a totally reasonable, response time. The same thing in Rails would be many seconds, which is not reasonable in my opinion. And this can be really helpful partly because, it just means your apps are faster, people are getting the responses quickly, but also because you have a lot more flexibility.
Paul Smith 00:16:19 I’ve built internal tools where they want to have the ability to search all of the inventory or products or whatever else and they want to have like a select all or you know, be able to select everything. And in Rails you can’t just render all 1000 products because it basically falls over and you can try and cache stuff, but then that gets complicated. So you kind of have to paginate. But when you paginate, that makes it hard to select things across multiple pages. So then you need some kind of JavaScript to remember which ones you selected across pages. And it just balloons the complexity, right? If you know, hey, we only have eight or 900 products, we’re not going to suddenly have 20,000 and Lucky you just render them all, put them all on the same page, give them all check boxes and it’s in the user’s hands and 200 milliseconds and you’re done. You just removed most of that complexity. So those are some of the ways that that speed is playing out. And, and I think one key difference there is some people think speed is just about scalability. How many people can be using this? The speed improvements I care about are the ones where even if you have one request per day, I want that request to be insanely fast. And so that’s kind of what you’re getting with Lucky and Crystal,
Jeremy Jung 00:17:42 When you talk about web applications, you know, with Lucky being a web framework, a lot of people point out that a lot of the work being done is Io, right? It’s talking to the database; it’s making network calls. But I guess you’re saying that rendering that template, those are things that having a fast language, it really does make a big difference.
Paul Smith 00:18:02 It does. Yeah. I think the whole database Io thing, a lot of times that’s what people say when they’re working with a slow language. If you have a fast one, it’s not as big of a deal because this was the same with Phoenix and Elixir. Like I loved how quickly it could render HTML. That was huge.
Jeremy Jung 00:18:22 And like you said, that opens up options in terms of not having to rely on caching or pagination or things like that.
Paul Smith 00:18:31 Yeah, this is huge. I mean, an example from work, we just announced GitHub discussions and I’m on that team and one of the big things we were trying to get working was performance of the discussions show page. You could have hundreds of comments on that page and we were finding that most of the time taken was actually spent rendering the views and calling methods on the different objects to render things differently in the seconds. And we can’t cache those reliably because there are so many different ways to show that data. If you’re a moderator, you get certain buttons. If you are an unverified user, like someone who just signed up, you see a different thing. If you’re not signed in, you see a different thing. And so you can’t reliably cache those. And we had a lot of cool techniques to kind of get that down, but this is something that if this were written and Lucky, it just would not have been an issue.
Jeremy Jung 00:19:31 And GitHub in particular is written in Ruby, is that correct?
Paul Smith 00:19:37 It is, yeah. It’s using Ruby on Rails and I’m not trying to knock Rails. I really love Rails. I mean I’ve been using it for 12 years. I like Ruby but hey, if there’s something that could be even better, I’m open to that.
Jeremy Jung 00:19:51 For sure. You have used Rails for 12 years. How would you say that your productivity compares in Ruby versus in Crystal?
Paul Smith 00:20:03 I think that’s tricky. It’s kind of better and worse. And what I mean by that is I think Crystal, I’m more productive in Crystal. You do have compiled times and we can talk about that. They’re not the fastest, they’re not the slowest. But I do find that I can write more code and then compile once and it kind of just tells me where the problems are and I have a lot more confidence and I spend a lot less time banging my head on like, why isn’t this thing working and it’s because I passed the wrong type somewhere. However, Ruby has a massive ecosystem. So there are things that exist in Ruby that I would have to rewrite in Crystal. And so that for sure, no matter how productive I am in Crystal, Crystal is not as productive as requiring the gem and then just using it.
Paul Smith 00:20:52 So the hope with Lucky though, is that we’re building up enough things that you don’t have to be rewriting everything. And the community’s also really stepped up and writing a number of libraries that are super helpful for web development. For example, somebody just wrote Web Drivers dot CR, which makes it so that it can automatically install the version of Chrome driver that matches the version of Chrome that you have installed. So you don’t have to manage that at all. That’s something that was in Ruby for a while and will be in Lucky probably in the next release. So yeah, I think it’s better. It’s one of those things that will get better with time.
Jeremy Jung 00:21:34 So in terms of the actual language productivity Crystal, it sounds like basically a net positive, but it’s more in the community aspect and how many libraries are available that’s where a lot more time gets taken?
Paul Smith 00:21:49 I think so. And then just the initial ramping up. It is a new language and so there aren’t as many stack overflow questions and answers.
Paul Smith 00:21:59 There aren’t as many tutorials, so there’s definitely some things there that, but like I said, those are things we’re working on, especially for one of Lucky try and make sure we have really good guides, really good error messages. We tried to borrow a little bit from Elm, not specific error messages, but just the idea that an error message should raise something human readable and understandable. And if possible, help guide them in the right direction of what they probably want to do or at least point them to documentation to make it easier. So we’re trying to help with that as much as we can.
Jeremy Jung 00:22:37 I kind of want to move into next more into your experience building Lucky, you know, you were a Rails developer for many years. And are there any like specific major pain points, I guess in Rails or in your previous web development experience that you wanted to address with Lucky?
Paul Smith 00:22:55 Yeah, there were some, more specific than others. Some easier to solve in the sense that the solution is like it works or doesn’t and others that are a little bit more abstract. So I’ll talk about some of the specific things. I often said that I’m into type safety. I don’t think that is quite true and I think it, especially if you haven’t used Lucky, it just doesn’t click what that means or why it matters. Because you just think like, oh, so you know, it’ll tell me if I pass an integer instead of a string. Like who cares? I’m not seeing those kinds of errors. What I’m most interested in is compiled time guarantees, whether that’s with a type or some other mechanism. And that’s there not just to prevent bugs, but to help you as a developer to spot problems right away and give you a nice error so you know what to do about it.
Paul Smith 00:23:50 So for example, one of the things that I’ve seen in basically every framework I’ve ever used, regardless of whether it is type safe or not, is that you need to use an HTTP method, a verb and a path. So for example, if you want to delete a user, you would have forward slash users forward slash one to be the ID. The tricky part is you have to have the HTP method delete for it to do the delete action. But sometimes you forget that you use a regular link and you wonder why the heck it just keeps showing you this thing instead of deleting it. Or the particularly insidious one is when you have an update and a create one uses post, one uses put, if you have an update form and you forget to put the method put, you get all kinds of routing errors. Because it says, hey, this doesn’t exist. And you went, well why doesn’t this exist? I can see it right here. I’ve got the route, I’ve got everything. Oh it’s because I forgot to put the HTTP method is a put and it just waste time. So that’s one of those things where we wanted to compile time guarantee and Lucky and so I don’t want to go too in depth here, but basically what we did was we made every controller into a single class that handled the routing and also the response.
Jeremy Jung 00:25:13 If I understand correctly, when you have a page and you want to link to a specific user on that page, then you would use this, this function link to, and you would pass in the class that corresponds to showing a user. And then you would pass parameters into that function. Like for example the idea of the user. And if you didn’t do that, then you would have an error at compile time, not correct. You wouldn’t need to like start the website and then go to the page and have it basically exploded. Which I guess is typically what you would expect from most web frameworks.
Paul Smith 00:25:58 Yeah. Or what’s worse, it wouldn’t explode, it would just generate the wrong link. And you would have to remember to click that link or write an automated test that clicks that link. So it’s really easy for bugs to sneak in. And this just completely prevents that class of bug as well as just makes life easier. Because if you forget a parameter while you’re developing from the start, instead of just generating something with like a nil ID, it’s going to say, hey, you forgot this. It just saves a lot of debugging time. And I think it’s also more intuitive if you’ve ever used Rails helpers or Phoenix, any of this man, the conventions, like it’s a singular, is it plural? Is it, does it have the namespace? Does it not have the namespace and Lucky that it’s gone? You just call the action the one that you created. You call that exactly as is.
Jeremy Jung 00:26:52 It sounds like this is maybe a little more explicit, I guess?
Paul Smith 00:26:56 Yeah, it’s a little more explicit, but I hesitate, I’ve heard a couple things in the programming community. One the Rails started is convention over configuration, which that was huge because you had to learn the convention, but at least once you did, you knew how the other Rails projects were. And then another one I hear is explicit over implicit. I don’t buy into either of those in particular. Because sometimes implicits better, sometimes explicits better. I mean, for example, as a quick example, I don’t hear anyone arguing to bring back the old objective C where you had to manually reference and dereference memory. That is technically more explicit, right? But does anyone want to do that? No. So I don’t think explicit over implicit, you have to think about it. Everything needs to be judged in its own context. And what I think is even better than convention over configuration is intuitive over conventions. Meaning you don’t even think about it. You don’t even need, there doesn’t need to be a convention because you’re literally just calling the thing that you created. Like anything else, there’s nothing special about that. It’s a class just like any other class and you call a method on it. Just like any other method. I think it’s tricky because I think it’s also easy to say explicit over implicit and make your code super hard to follow.
Paul Smith 00:28:27 And it’s like, yes, it’s more explicit, but also, I just wrote 20 lines of code instead of one. And those 20 lines could differ because I do it differently than the other guy or girl.
Jeremy Jung 00:29:10 Another thing about Lucky that’s a little different is that for templating, instead of having somebody write HTML and embedding language code in it, you instead have people write Crystal code. So could you kind of explain sort of why you made that decision and what the benefits are?
Paul Smith 00:29:32 Yeah, sure. So a lot of things actually with Lucky kind of I did not want to do or were definitely not how I started doing things and it just kind of moved in that direction based on the goals and I think that’s part of what makes Lucky different is that we don’t say, here’s how I want to do it. We say, here’s what I want to do and I want it to be easy, simple and bug free. So what we started with was using templating languages just like you’d use in almost any anything where you write your HTML and then you interpolate values in it. At the time I wrote Lucky and this may be changed now you could not use a method that accepted a function or a block is what it would be called in Crystal and have that output correctly in the template.
Paul Smith 00:30:26 I think it just blew up. I don’t remember, this was two years ago, three years ago. The other problem I was having was, it’s not just a template, any bigger size framework also has partials or you know, fragments or includes or whatever you want to call it. It also has layouts where you can inject different HTML and different parts of your HTML layout. And those are all things that a person has to learn when they’re learning your framework. What are these methods called for generating a partial or for calling a partial or injecting stuff in different layers with layout. And it’s also more stuff that I have to write. And with Lucky, like there was already a lot to write or building the ORM and the automated test drivers and the router and like everything. So I can’t afford to just do stuff like everyone else does it if it’s not pulling its weight.
Paul Smith 00:31:27 So eventually I started experimenting with building HTML using classes and regular Crystal methods. Some of the requirements for me when I was building it was it had to match the structure of HTML and it had to be very easy to refactor. Meaning I can pull something out into a new method, and it just works so easy refactoring and then I also need to be able to do layouts with it. The reason for that is ELM also uses code to generate HTML, however, it is not approachable to a newcomer. If for example, you have a designer and they pull open an ELM file and try and look at what that, what that generates, no way. I mean I’m a programmer, I still don’t know what it generates without really looking through ELM. And that’s partly because you are generating data objects, so arrays of arrays or maps or whatever else.
Paul Smith 00:32:30 So I didn’t want that. It has to be approachable to people and look and be structured like HTML. And so we actually were able to do that. I don’t know if I need to go into huge detail, but basically you can say, hey I want a div inside of that. I want an H1 underneath that, I want another div. And you’re not building arrays and maps and anything else. What that provides is actually a lot of things that I did not think of. One super easy refactoring, if you have a link in a particular page and you don’t want to copy that over and over and over, extract the method and you call it like any other method, there’s nothing to learn, it’s just a method like anything else. It can accept arguments just like anything else, your conditionals work, you can extract that into a component which is basically another class, and it tells you explicitly here’s what I need to run.
Paul Smith 00:33:27 And it renders the thing. You always have the correct closing tag. I have been bitten so many times by shifting stuff around and forgetting a closing tag and my whole page looks wonky and I have to go through layers of indentation. That just doesn’t happen if you forget an end. So you would have a do end when you’re creating these blocks, it blows up. It’s like, hey, you’re missing one. And the coolest part is you just add an end in there and you run the Crystal formatter and it re-indents everything perfectly. And then on top of that, that wasn’t enough, like I just loved how easy it was to refactor and use. You don’t have to split up your code from your template like in Rails you would have a helper. So you’ve got like, here’s your template, but then you might have a helper in a totally separate file if you’ve got something that pertains to just that page, you can just extract a method.
Paul Smith 00:34:21 It’s right there. But this also made it so we can do layouts without any special work. Your layout is basically a class. You would say, here’s my class with the head. It renders the head, it renders HTML body or whatever. And then it calls a content method or a sidebar method or whatever else. And your page, if you wanted to render a list of users, inherits from that class and implements a content method or a sidebar method. And so when that’s rendered out it just calls those methods. So we got all of that for free. If you look at our view rendering code, it’s 50 lines because basically we use a macro and give it a list of tags like you know, paragraph H one, H two, whatever and generate a bunch of methods and, and that’s basically it. So from an implementation perspective it’s extremely simple.
Paul Smith 00:35:16 Plus you get all these niceties around refactoring is super easy. It’s super easy to tell what a page needs to render at the top of the page. You just say, you know, I need a user, I need a paginate, I need a current user. So you know what that page needs. You don’t get that with a template, and you get all the power of Crystal for rendering layouts however you want. That all basically came for free. So it was kind of a happenstance that templates weren’t working and this just worked out better. People, a lot of people when they see this are like, what the heck is this? I hate it. And I always just say, just give it a try. Just give it a try for a little bit. So far one person has said like, okay, I don’t like it. And you can use templates if you want. We’ve actually built that in, but everybody else is like, now that I’ve used it, I love it.
Jeremy Jung 00:36:08 What it sounds like is in a lot of JavaScript frameworks for example, like React, there’s this concept of components, right? And so you can create what looks like new HTML tags but really has other HTML in it. Like let’s say you have a list of, businesses and maybe you have a component that would have all the business details in it. It sounds like in the case of Lucky, you kind of can do the same thing. It’s just that your component would be in the form of a Crystal class and so there isn’t any new syntax and you’re not mixing different languages. Like you’re not mixing HTML and JavaScript instead. Everything is just using Crystal.
Paul Smith 00:36:58 Exactly. You have two options. You can extract a private method. because sometimes it’s just a small thing you want to extract only use by one page, just do a method, if not, uh, extract a class. And the cool part about all of this is that you don’t need to restructure anything. Meaning you can start with everything in one method in your content method and then you pull out just a little bit into a private method and then if that’s not enough, cool, pull that out into a class so you’re not forced into just pulling out classes all over the place if you don’t need one. It really worked out kind of really well because it also makes testing easier. You can pull out a class component that just does one thing and you can instantiate just that component. And test just that HTML. And once again, this is very easy because it’s a class you call it and run it like any other class.
Paul Smith 00:37:56 And so that’s been a big goal of Lucky is try to reduce, and this also comes down to the whole like convention over configuration is how do we just make it so there is no convention, it’s just intuitive. Like if you know how to extract and refactor a Crystal class, you know how to extract and refactor stuff for a page in Lucky automatically. And I mean of course there’s still some degree of learning and experimentation, but it’s the same paradigms. If you want to include methods in multiple pages, use a module just like any other module. So that was very much a goal and that’s part of other parts of Lucky. For example, querying in something like Rails, the model is for creating, updating, reading everything. In Lucky, you do create a model and we use macros to actually generate other classes for you. But you have a query object that is a class.
Jeremy Jung 00:38:57 What am I passing into my query object? What does that look like?
Paul Smith 00:39:02 Let’s say you have a user, by default it generates a user::base query. So basically, you have this new object namespaced under the model, and by default the generators generate another file. And basically what that does is it creates a new class called User Query that inherits from that user base query class what you would do in your controller or action or anywhere. Say user query dot new by default. That just gives you a new query that would query everything in the database unless of course you overrode initialize and did something else. Then it would use that scope. So if you wanted to further filter down, you would call, for example, if you wanted the name to be Paul, it would be user query.new.name(Paul) as a string. Because Lucky generates methods for every column on the model with compile time guarantees.
Paul Smith 00:40:04 So if you type O that method, it’s going to blow up. If you rename the column later, it’s going to blow up. If you accidentally give it nil, it’s going to blow up and tell you to use something else. But that’s how you would do it. You say dot name is Paul, or we also have type specific criteria methods. You can do things like dot age dot GT for greater than 30. And so you have this very flexible query language that’s all completely type safe. So in your scopes, if you wanted to do something like recently published for a post, inside that method, you would do something like published at dot GT for greater than one dot week dot at Go. And you can chain that. So you could do post query dot new recently published dot authored by Paul or whatever. So that’s basically how it works. You just have these methods that are chained that you can build upon in pretty much any way you want.
Jeremy Jung 00:41:08 In a lot of applications now people use JavaScript frameworks, whether it’s React or View or Angular. What does integrating with JavaScript libraries and frameworks look like in Lucky?
Paul Smith 00:41:21 I think easier than a lot in the sense that you can generate a Lucky project with different modes. So when you initialize a project, you can use just the command line with some flags or the default is to walk you through a wizard which will say do you want API only, in which case, you know, it won’t even have HTML pages or the default, which is a full app. What that does is it generates Webpack config for you. It sets up your public assets and images so that they can be copied and fingerprinted. And so out of the box it already has a basic web pack, set up for you that handles CSS. It handles most of your ES six JavaScript type stuff that people typically like. That’s just handle out of the box. If you want to include reactor view, you would include that just like any other Webpack project in terms of building it.
Paul Smith 00:42:19 And it’s actually a little simpler. We use Laravel Mix on top of Webpack, which is basically a thin JavaScript layer that calls Webpack underneath the hood. If you want a full single page app that’s also totally supported, you would basically have just one HTML page that you know has the basic HTML and body tags and within that mounts your app. So whatever that is for your language and view, it might be just a tag that’s like main app and then in your JS you would initialize that tag with your app. And we have fallback routing so that you can do client-side routing if you want. It’s not particularly well documented which is the biggest problem. Some people are helping with that because a number of people have done React and View. And so hopefully those will be fleshed out a little bit more, but it’s totally supported in the long term though, we’ve got plans to make it, so you don’t even need those types of frameworks quite as much.
Paul Smith 00:43:26 Since we already have class components and a bunch of other things, I’m working on a way to add type safe interactivity to HTML. So you’re not writing the JavaScript, you’re writing Crystal for the most part and it can interface with JavaScript and you can run, you know, use reactive view inside of it. But a lot of your simple open close, if anything like that is going to be handled client side but written with Crystal and server interactions will also, those will be sent over an Ajax request but will also be type safe when you call the actions and do all the HTML rendering similar to Livewire for Laravel or Live View by Phoenix, but with some, differences. That’s not done yet, but it will be. And I think it’s going to be really exciting. I’ve got a proof of concept up locally and it’s really awesome.
Jeremy Jung 00:44:25 We had a previous episode on LiveView and I think the possibilities of things like that are really interesting of being able to not have to have this sort of separation between your JavaScript front end and your server back end yet still be able to have the kind of interactivity people expect.
Paul Smith 00:44:45 Yeah, I think it could be cool. And that’s also where speed comes into play When you’re doing interactions like that, you don’t want to wait even a hundred, even 50 milliseconds is noticeable for those types of interactions. And so Phoenix also fast, really fast template language basically gets compiled down to Elixir and so that helps a lot. I do think there’s some big flaws that I’ve seen in some other implementation, well I don’t want to say flaws, that sounds a little overly harsh, but things that I personally are just deal breakers for me and one of those is some client-side interactions have to be instantaneous. They just have to be, if I click on my avatar in the top right, I expect the menu that has settings and logout to be instant. If there’s any kind of latency in the network and it takes 200 milliseconds even that’s going to be a weird interaction.
Paul Smith 00:45:46 And it’s going to feel like your app is broken. And of course that’s exacerbated by people not in your country. This is another problem. People are doing these things deploying servers in their own country. Put a VPN in front of your computer in Australia or even the UK 400 milliseconds. That’s just, you can’t do that for a settings menu or for opening a modal. And so there needs to be some way to do those interactions instantaneously. Live Wire by Laravel, the same guy that wrote it built Alpine JS, which is kind of, it looks a little bit like View, but it doesn’t have a virtual DOM. It operates with the DOM that you generate. That’s what it uses for client-side interactivity. So you can do the server-side stuff, which I mean if latency’s there you’re, if you’re submitting a comment, look there’s no way around it. You’ve got to hit the server. Right. But if you’re opening and showing something, a menu, a tab, a modal that’s instantaneous and is handled by Alpine. So Lucky’s actually going to use that along with our own server rendered stuff to do client-side interactions instantaneously.
Jeremy Jung 00:47:02 So Alpine, it’s a JavaScript front end framework you said similar. Similar to view, it’s without the virtual DOM and it sounds like what you’re planning is to be able to write Crystal code and have that generate Alpine code? Is that right?
Paul Smith 00:47:17 That is correct. because it’s mostly in line and it can’t do everything. But most of what I want from client-side interactions are typically super simple things. I want to open and close something; I want to show tabs. And those are things that Alpine’s incredibly good at because you don’t need a separate JavaScript file. We can just generate something that says it uses X as itís kind of modifier X dash click toggle the thing true or false toggle open to true or false. And X if or X show. And then if it’s open or not, those are things that we can very easily generate on the backend and make type safe because we can say, you know, this has to be a bullying and here’s the action and all those things are then type safe. But you can still do JavaScript if you want. So you can still use JavaScript functions in there with your Alpine if you need to.
Jeremy Jung 00:48:18 Yeah. That just sounds like the distinction between that and like a LiveView or a Livewire is that my understanding is with those solutions you’re shipping over basically diffs in your HTML and that’s how it’s determining what to change. Whereas you’re saying like you may still have some of that, but there’s certain interactions where you just want to run JavaScript locally on the person’s client and you should still be able to do that even if you are doing this sort of sending diffs over the wire for other things.
Paul Smith 00:48:54 Yeah, exactly. Alpine’s made for that, the biggest key differentiator between Livewire, LiveView is the type safety, all those nice things that you get in Lucky you’re going to get also for your client side interactions. So if you have an action and you have a typo or something, it’s going to blow up. It’s going to tell you if you forget something, if you miss the wrong type. I mean, and this is something that’s very hard in the front-end world because you either have to run an automated test to make sure you catch these or the worst you have to open up the console because like why isn’t this working? I don’t know. Now we have to dig into the console. It’s not even where you typically want to see logs. And so being able to shift that to where you’re used to seeing errors and before you even have to open the browser, I think that’s going to be a huge deal.
Jeremy Jung 00:49:47 I think on the, the server side testing is pretty well understood in terms of, you know, especially if you have API endpoints or you have just regular server code, like people know how to test that, but on the client side there’s like so many different ways of doing it feels like, and a lot of them involve spinning up browsers and get kind of complicated and so yeah, it’ll be interesting to see if you can shift more of that to the server environment that a lot of people are used to.
Paul Smith 00:50:19 Yeah, I think it’ll be cool. We’ll see how it goes and yeah, I do think there’s definitely complexity that comes with moving it to JavaScript, especially if you have a single page app. because then you need to spin up an API, you need the server and an API when you use your cypress tests or whatever. Or a lot of people mock the API, which sometimes is fast but can get out of sync. In which case you lose confidence in your tests. So having it in one spot is I think really great, and we do have the capability to run browser tests that’s built into Lucky because I think it is still good to have at least a couple smoke tests for your critical paths to test the happy path. But I mean if you can write fewer of those, that’s great because they take forever to run
Jeremy Jung 00:51:11 For sure. Yeah. In Lucky there’s a lot of features that in other frameworks would not usually be included. Like for example, there’s authentication, you have this setup check script to see if your app has all its dependencies, things like that. I wonder if you could sort of explain sort of how you decided what sorts of features should exist in the framework versus being something that you leave to the user to decide.
Paul Smith 00:51:39 I think things, if there’s no downside for one thing, if there’s no downside, only upside and almost everyone would benefit from it, I want to include it. So that’s for example the system check script. We also have a setup script and that’s what we tell people to use instead of saying like first installed yarn and then run your migrations and blah blah blah. No our documentations don’t even mention that. It’s like run script setup. And the idea there is it, it serves as kind of a best practice. It kind of pushes you into things to say like, hey, put stuff that you need in here. Then we layer it on the system check, which also runs before setup. And every time you boot the development environment where it’ll check, hey do you have a process manager which you need, it’ll check whether Postgres is installed and running because that’s required.
Paul Smith 00:52:36 So if you go back to kind of that criteria, it’s useful to pretty much everyone. Meaning like if Postgres isn’t running and the app’s not going to work, everyone would need to know that. And it doesn’t really have a downside. If you don’t want it for whatever reason, you just delete it or stop running it. That’s not a huge downside. That’s like, you know, one click. So that’s part of why that’s included. I don’t like spending time on things that aren’t delivering actual real value. So I don’t like spending time figuring out why my local environment is not working or why it was working and now it suddenly isn’t. And with something like a system check that makes teams happier in the sense that let’s say all of a sudden adds somebody adds a new search capability and it requires elastic search and I do a get pull from Master, do my feature as soon as I boot the app.
Paul Smith 00:53:30 If they’ve added something to System check that says, hey you need Elastic, it’s going to tell me it’s not going to just blow up. It’s going to be like, hey you need Elastic search now install that and run it. These are the types of things that I really think are going to save a lot of time in terms of off, that’s another one of those where it’s like so many people want it, and it should be easy and simple and not like five different ways to do it. But not everyone wants it, which is why we made it optional. You choose in the wizard like if you don’t want off, fine. I’d guess that most people generate it with auth. I know I do because I need it. And the thing is we also changed how auth works in the sense that it’s mostly generated code. It’s not just a bunch of calls to some third-party library. So what that means is it is easy to modify. So if you want to add email confirmations or invitations or anything else like that, you’re not mucking around in some third-party library. Its code generated in your app that you can see and modify. So it doesn’t lock you into anything. It’s very flexible and it helps you get off the ground running and that’s why that was included. Uh, and I’m sure we’re going to have other stuff that may be included or at least an option of being included in the future.
Jeremy Jung 00:54:53 Yeah, I think one of the conversations that people are having now is, particularly in the JavaScript ecosystem, you end up pulling in a lot of different dependencies. You end up having to make a lot of different decisions. And so it’s interesting to sort of see Lucky kind of move back in in the direction of say a Rails of trying to kind of include the things that you think probably most people building an app are going to need.
Paul Smith 00:55:20 Yeah. It’s a little more in that direction. I think on the flip side, Rails are starting to include so much that people are starting to get mad almost. And it’s like so much that you’re like, what is this? What is happening? So we want to strike a balance there. And so part of that is being very careful about what is included. I think some of the things that are included in Rails could just as easily be added after the fact, meaning 20 minutes of work and you can add it. Those are the types of things I probably would not include in Lucky if it’s 10, 20, 30 minutes to, you know, add it and modify your app and only 50% of people even want it. We’re probably going to just say, here’s a guide on how to do it and make it easy, but not do that as a generator, if that makes sense.
Jeremy Jung 00:56:11 What’s an example of something like that that would be pretty easy to add in after the fact and doesn’t necessarily need to be included?
Paul Smith 00:56:20 Well on Rail6 it’s coming up. They have this action mailbox thing that handles inbound emails. I’m pretty sure by default that is included. I could be wrong so don’t quote me on that, but I’ve been seeing a lot of Twitter stuff lately of people being super pissed about it. So I think it’s there. that’s something I definitely wouldn’t include because I think I’ve written one app ever that uses inbound emails. I mean GitHub does too, but I have not written that, and a lot just don’t have that. So it’s odd to include it, especially given the fact that it’s not particularly hard to set up yourself. I think based on what I’ve seen, or action text is another one where it has ways of making rich text editing easier. That might be something too where it could be added on later that I think at least has a little bit more merit because I think it’s common for at some point to be like, we need a rich text editor. But those are the kinds of things that I would probably push off and it’s not a best practice either meaning I think it’s smart that it has active record by default and chooses a database for you because it’s best practice to just use active record. Right. And you’re going to have the best time using active record because that’s what everyone uses. So including that makes sense. But yeah, something like Action Mailbox is like what’s the benefit in including it?
Jeremy Jung 00:57:49 Yeah. Just because the majority of people who are writing applications, they’ll never need that inbound email feature. As opposed to your example of authentication, like probably the majority of applications people are building will have authentication in them.
Paul Smith 00:58:06 Exactly. Yeah. And it’s something that’s hard to add, meaning it touches so many parts of your application. And because we are generating stuff, it’s not easy to add after the fact. But stuff that is easy to add and easy to remove that. Another criteria is how easy is it to remove it? So we include a few default CSS styles, but super easy to remove. It’s basically like you go to your application CSS, it’s like, delete everything below this line. You delete it and it’s like you’re done. But it’s nice because it makes it look decent and not like a horrific, ugly thing when you start your app. Right? But it’s easy to remove. And so that’s something, for example, that we also include by default.
Jeremy Jung 00:58:52 That’s also I think, the distinction between something that’s generated code and or configuration that the user can see. I mean, I think your setup scripts and your system check scripts, one of the things that makes those kind of more straightforward is the fact that they are in your code base and they’re bash scripts, right? So yeah, if you want to modify it or you want to remove it, they’re kind of right there. Whereas something like an action text or action mailbox is probably in like the Rails gem, right? It’s in the library. So you don’t even see it in your code base. I guess that would be the distinction there, maybe.
Paul Smith 00:59:35 Yeah. Or you might, but you don’t know why it’s there or what it does. Or another concern is how many things does it hook into? So for example, one of the big things is, like I said, default styles. How many places does that hook into things? Just one, right? You go to your main CSS file and delete it. But there’s a way to do that that I don’t particularly like. I’ve seen some people, for example, use Bootstrap or any framework. It doesn’t matter what it is. The problem with those is it also modifies the generated HTML and the scaffold. Because by default it’s adding classes like column three, medium button, blah, blah, blah. If you don’t want to use Bootstrap, you have to remove Bootstrap and manually go through all of the generated HTML files to remove the Bootstrap classes. And so that’s like a key difference too, is how easy is it to remove? And we really want to only add things that are easy to remove or really hard to add.
Jeremy Jung 01:00:39 What is the adoption of Lucky look like? Do you know of people using it in production currently?
Paul Smith 01:00:46 Yeah. I don’t have exact numbers, which I think is good because it reduces anxiety a lot not knowing. And it’s like, is it going up? Is it going down? But people are using it in production a lot from the very early days of Crystal. One of our core team members, Jeremy, he’s been using it at work for two and a half, three years, and they’ve had great success with it. They replaced some of their Railís microservices with Lucky originally for the performance boost. And I think this is common. They stay for all the nice type safety and the reliability they get. It’s hard to explain with just words, but then you use it, and you see an error and we try and make them nice. Not all of them are, but we try and make it nice, and people go, oh, this is nice.
Paul Smith 01:01:39 Or people are annoyed that they see this compiler error and then realize, oh wait, actually it did catch a book. So, but they’re having great success. Big performance boost, like something like they reduced their number of servers by like 70% and their response times got cut down 60 or 70%. So yeah, they’re having great success. And then a few other people are building client projects using Lucky, I don’t know what they are. Some people, there’s just not, they can’t say to the public unfortunately. But yeah, people are using it in production, which is really exciting.
Jeremy Jung 01:02:16 Looking at the Crystal community, what does that look like? You know, is it pretty active? What are your thoughts on the community?
Paul Smith 01:02:24 Yeah, it’s quite active. They’ve got sponsors, quite a few corporate sponsors. So they’re making decent money to help fund development. They’re aiming for 1.0 I don’t know exactly when, but they did a blog post saying it’s going to be soon. And I’ve talked to them in person about it, but I don’t know how much, I’m supposed to say, but soon. Which is fantastic because then you’re not going to have to deal with the breaking changes, which have definitely been happening the last two years. And I think it’s good because the language is improving and changing things, but once 1.0 hits, people are going to be able to jump in and they’re not going to have to update their apps every three months or whatever. But yeah, a lot of participation and the sponsorship money goes a long way. A lot of the development is based in Argentina and the dollar is super strong over there.
Paul Smith 01:03:15 So meaning if you’ve got corporate sponsorship in dollars over here, that goes a really long way towards development. And they’re all super nice. I’ve talked to a lot of them in person. Super nice, super smart guys. The community itself, in terms of forums and chats, that’s where I’m a little hesitant. It’s active, but I think not particularly welcoming for newcomers. Just really strong personalities. Very smart, but very strong personalities. And I would say it may be better to come to the Lucky chat rooms. We’re very strict about our code of conduct and not about nitpicky things, but just in general that, you know, you talk to people with respect and empathize and we’re not the type of people where you come with a question and we’re like, well, did you Google it? We’re going to try and help you. And so I think it’s a very welcoming community. And even if you’re not using Lucky, feel free to hop on our chat room. If you go to the Lucky website, there’s a link and yeah, we’re pretty nice over there. So things are moving forward. We’re trying to get to 1.0 around the same time as Crystal. Maybe a little after. But I think that’ll be a big milestone.
Jeremy Jung 01:04:31 It’s interesting talking about the community, because I think when you think about Ruby, one of the big parts that attracts people is not just the language or the framework, but it’s, you know, having an inclusive community, having people that are really friendly. So it’s good to hear that Lucky you’re striving to do that. Like why is there that divide?
Paul Smith 01:04:52 I’m not entirely sure. I mean, part of it is I am a sensitive person, and so I am kind of trying to create the community that I want, which may be actually way more upbeat and positive just because I want new people to feel comfortable. And I think maybe part of it is with Crystal, they don’t have that much time, I think is part of it. And so it’s easier to brush stuff off. Some of it could be just that they don’t care about the same things that I personally do. There’s nothing actively bad going on. It’s just I prefer things rather than to just be okay or average, right. I want it to be exceptional, right? And a place where it’s just like, don’t worry. You can say something if it’s, you feel it’s dumb. We’re not going to be like, pile on.
Paul Smith 01:05:43 We’re going to be like, hey, it’s fine. And here’s maybe an alternative. So yeah. I mean, go to the Crystal rooms. I still do, I still get help. There’s a lot of really smart people. You just got to put on like a little thicker skin and be prepared for like, why do you want to do this? Have you tried this other thing? Have you done this other thing? In a way it’s a good thing because they’re making sure that like you’ve tried your different options and you’re not just asking to do something that’s a horrible idea, but it can make people, I think, feel like their idea’s getting attacked or whatever, right? So that’s what I mean by part of it is just like if you’re sensitive that’s going to come off as probably harsher than it was intended. But you can still get a lot of help.
Jeremy Jung 01:06:26 Yeah. I guess it’s just trying to find the right level of yeah, I don’t know what the word would be, but yeah, making people feel comfortable.
Paul Smith 01:06:35 Yeah. I do have a high bar for that because like, I am sensitive and I grew when I learned a program all online with books and with forums, and I remember how hard it was as a new developer that didn’t know best practice. And people would be like, why are you even trying to do that? That’s so stupid. And it’s like, dude, I’ve been programming for like six months. Calm down. And I think it’s common. I think, I mean that happened in the Ruby forums, I happened to be in the Rails forums. It’s a common thing I think, across the internet and various communities. So it may not even be that Crystal is particularly bad. It’s probably a lot like most communities, but we just want ours to be exceptional in terms of making people feel welcome. And you know, if someone has a bad idea and air quotes bad because maybe it’s a great idea and we just don’t have the context, but if it is a bad idea, we’re not just say, why are you doing that? Blah, blah, blah. First let’s help you solve your problem. And then talk about how this might be better. Maybe there’s a better way to do it. And it just feels a lot better. People are more accepting too of your feedback when you’re not just immediately jumping on them and say, why are you even trying to do that? And so I think that’s important.
Jeremy Jung 01:07:52 Yeah. I mean I think that probably applies to really all projects, right? Like they could all kind of stand to learn from some of that and kind of see it from the other person’s perspective who doesn’t have sort of all the same knowledge that, you’ve been building up. And maybe they can bring you a new perspective as well that you didn’t even think about.
Paul Smith 01:08:15 Yeah. I mean, we’ve changed stuff in Lucky, a lot of stuff that I was pretty sure about, and they asked if it could be done differently, shared their use case and it’s like, oh yeah, I made a mistake. And so it’s good for everyone. Like if you show a little bit of vulnerability and openness, you’re much more likely to learn and you’re much more likely to learn new and novel things because the people with the strongest opinions are often the ones that have that opinion based on some principle they read about or a talk or something else. It’s the quiet people that are like, hey, can we try doing this like a little differently? And you’re like, whoa, I’ve never thought of this because no one else has, but you’re new. You came up with this great new innovative idea and you felt comfortable sharing because we’re not just shooting people down constantly. And so, yeah, I wish more communities did that in general because it’s mutually beneficial.
Jeremy Jung 01:09:19 That’s kind of a good place to start wrapping up, but where should people go if they want to learn more about Lucky?
Paul Smith 01:09:26 First place? Luckyframework.org. That’s the main website. It has guides, it has blog posts that you can follow or subscribe to with new announcements. And it has a link to our chat room as well as the GitHub. So that’s where I’d go. Feel free to hop on the chat room anytime. We’re all really helpful and try and be nice. And so like people shouldn’t hesitate to run in there if they have problems. If there’s stuff that’s confusing, feel free to open an issue on Lucky. We have a tag that’s like improve error experience. So we have dedicated stuff just to do that. Yeah. In fact, if you start a Lucky project and you get a compile time error when you first start or are fresh on a project, it says, hey, if you’re still stuck, go to our chat room and ask for help. Everyone should feel free to do that.
Jeremy Jung 01:10:22 Very cool. And how can people follow you and see what you’re up to?
Paul Smith 01:10:26 Paul C. Smith on Twitter? Probably the best way to do it right now. Maybe one day I’ll have a blog or something, but right now it’s Twitter.
Jeremy Jung 01:10:34 Cool. Well Paul, thank you so much for coming on the show.
Paul Smith 01:10:37 Yeah, thanks for having me. I really enjoyed it.
Jeremy Jung 01:10:40 This has been Jeremy Jung for Software Engineering Radio. Thanks for listening.
[End of Audio]
SE Radio theme music: “Broken Reality” by Kevin MacLeod (incompetech.com)
Licensed under Creative Commons: By Attribution 3.0