Why do I prefer Clojure to Haskell?
This is an episode of The Eric Normand Podcast.
Subscribe: RSSApple PodcastsGoogle PlayOvercast
I prefer Clojure to Haskell. It's probably mostly due to accidents of history: getting in Lisp at an earlier age, spending more time with Lisp, and only having one Haskell job. But I do have some philosophical differences with the Haskell philosophy as it relates to Haskell as an engineering tool. In this episode, I go over four of them, and try to relate them with anecdotes.
Transcript
why do I prefer closure to Haskell? So this is going to be a very personal, very anecdotal, maybe even a little out-of-date discussion of my preference. I was asked, I'm gonna give it. Alright, my name is Eric Normand and I help people thrive with functional programming. So it's a little late on a Sunday night, had a long weekend and a long week and I just want to get this out. I should be asleep but I want to finish this up. So I have to tread very carefully here because I am comparing two languages and I'm not comparing any theoretical possibilities meaning I'm not trying to compare types to untyped, right? I'm not saying dynamically typed better than typed. I'm not saying typed is better than dynamically typed. I'm talking about languages that have real physical manifestations, real communities, real possible opportunities that they create and when you look at the whole of those, I prefer closure, okay? Even though I still like types. I like types but I find that I feel better using closure. Like I said, this is going to be very personal. I'm going to go deeper into why I feel better about it but I do want to preface all of this with one more thing. I want to say that Haskell is really well designed. It is up there with closure as a language. It's really clean, it's well thought out for its purpose. I think it's doing a really great job and it's a pleasure to work with, okay? So I'm saying all these good things about it just to be sure that you know these things can stir up a lot of emotions and I'm just talking about my own preferences, okay? And I don't think that there, I don't think you could ever say that closure is better than Haskell in some absolute sense. I would never make that claim. I, but I do have a preference. I have to choose one usually, you know, you get what, where do I look for for jobs? If I have to get a job, I'm going for closure. Okay, so let me just go through, I have a list of things. Oh, well before I go into them, I do want to say so I worked in just a little context. I have been really fascinated by Lisp for a long time, like starting in 2000, 2001. I got really into it and just over the years picked up a lot of common Lisp, then moved into closure and I've just been very happy there. So I have, I have a bias that I have a lot more history with Lisp, a lot more experience with it. So I'm, I'm definitely better at Lisp than I am at Haskell. Okay, I'm not an expert at Haskell or anything, but I did work in a job in Haskell for almost three years and I, I got to know the, I mean at least the beginnings of the type system, you know, I could get code to compile. It took a while, but I did eventually learn to write code that would compile the first time, a lot of the time. Okay, sometimes, you know, the compiler finds things that you couldn't, you couldn't foresee, but I, I was writing production Haskell code, making decisions about what libraries we needed to use, you know, modifying existing code, designing new parts of the system, new features. So, I mean, I was, I was in Haskell land. Okay, but it's just one job. Okay, that, that's another thing, is it took me a long time, I left that job in 2013, yeah, 2013, and it took me a long time to figure out, I guess, to separate out, like, what part was it about that code base? Was it Haskell? It was the code base in our coding style and practices versus like, what did I not like about the job and what domain we were working on versus what was Haskell that I didn't like, right? So, there were, there were a lot, there was a lot going on there, and I left that job for a closure job, and I was very happy being in closure, but again, it was a specific job that had a lot of other good things about it, and so, you know, it's, it's hard to, to pull these things apart, and of course, this was back in 2013, so things might have changed. Everything that I say might have changed since then. Okay, so I'm going to get started here. So, the first thing that Haskell has, is it, I would say that, I mean, this is just my interpretation of it, but this is the feeling I get, is that they value type safety or type soundness over pragmatism, and this makes a lot of sense, and you know, I say, I'm just, you know, it's just a feeling, but they actually say that, right? The, it's part of the DNA of the language, they say something like, it'd be impractical at all costs, something like that, where they're trying, they're not worried about, if it's practical, they're worried first and foremost about it being mathematically sound, right, logically sound, and this is great for a research language, which is Haskell's main purpose, but I found a lot of difficulty with it in, in working with it, you know, in an engineering situation, where I had to make features work. I'm going to tell a little story, so I had, I was doing a feature and I needed to talk to an API, and so I needed to choose an HTTP library, and there were certain constraints that really meant that, well, there were certain features that I needed in that client library, and the, the real weird one, the weird feature was that we needed to be able to specify our own certificate file, you know, like an HTTPS certificate, an SSL certificate, because we were talking to certain, a certain API that had a self-signed certificate, it wasn't with the main certificate authorities, and they had sent us out of band the certificate that we needed to use to verify their thing, okay, and then there were a couple of other things that I needed to be able to do, and so I went looking for an HTTP library, found many in the Haskell, in the Hackage, right, and I started looking at the features, like how do I set the certificate file, and how, and it was kind of easy, because some of them, I would just say, oh, it doesn't do that, so let's just cross it off the list, some of them is a little more involved to see, you know, whether, what features they had, because they weren't well documented, etc, which happens with all ecosystems, right, it's normal to have, like I'm not faulting Haskell for that, it's trying to tell the story, and then I was like, well, you know, I should be looking for something like lib curl, because Haskell uses C libraries, the same way closure uses Java libraries, and wraps them, Haskell uses C libraries, and wraps them, so I looked for the lib curl, because that's kind of like, lib curl can do everything, right, so I found a lib curl, it was, you know, it was called something like Haskell curl, or something like that, anyway, I don't remember the name, but it was something like, clearly, this is the curl library, and it had, it was wrapping lib curl, like that's, that's what it did, and what the, what the author of this library tried to do, a very noble thing, was try to put some safety around the options, lib curl is a C library that takes all of its options as strings, and so there's the chance that you will pass in like the wrong format of the string, or you'll, you know, you're supposed to put a number there, you know, inside of a string, right, the number in the string, but you don't put a number, and so, you know, at runtime you get an error, and so this author was trying to fix that at compile time by saying, this needs a number, and this needs a, and it's a very noble thing, right, but the problem was that it was not complete, it did not have every single possible option, it had like most options, and the most popular options, and guess what option it didn't have, setting an SSL certificate, and so I looked for a backdoor, because look, it's lib curl, there's got to be a way to just pass at the strings, you know, just to bypass the type, bypass this options type that they created, and they didn't have one, so I went through all of the HTTP libraries I could find, and they all were crossed off the list for, you know, missing one feature or another, and I told this to some co-workers, and two separate co-workers, one at a time, they both said no, that can't be right, and they looked, and they concluded the same thing, and these are people that were much more into Haskell than I was, so they didn't have the same biases that I was bringing up, and long story short, we wound up writing a shelling out to curl, like the curl command line, we would shell out to curl to, because you could set whatever options you want with strings, and that's what we did, and it was not type-safe, it was just strings, but you know at runtime, you know, especially in development, you're probably going to test out all those strings anyway, so it worked out, it was fine, but the main thing that this story, the point of this story, is that a closureist would have left the back door, a closureist would have written it so that you could have bypassed it if you wanted to, and if only just because they would think, okay I'm gonna give all this safety, that's great, but what if I didn't finish the library, right? What if I didn't, what if there's a new option, I don't want to have to release a new version, just to get that option, right, and make people upgrade and stuff, they should be able to just upgrade curl and get that new option, or maybe they are compiling curl with different options that I didn't know about, you know, so there's just this more pragmatic notion in closure, in fact you might have even seen that the closure one would have two libraries, maybe not two libraries, but two modules in the same library, one was the totally unsafe version that called libcurl, let's you pass in a bunch of strings, and it does no checks, it just passes them straight through, so that's kind of like the minimal wrapper, so that you could use it from Haskell, but it's basically just as, it just does whatever libcurl does, and then there would be another one where it's like, well if you want a better type safe way of generating the options, here's a type that can convert into, you know, the array of strings that you would pass into curl, that's how a closures would approach it, they would start with the first one, this simpler one, the one that does as little as possible, and then optionally let you use this other one, and then encourage you to use it of course, but it would just be a totally separate separate thing, okay, so that was number one, so I say that I prefer pragmatism over type safety, I mean for a case like this where they are in conflict, I mean I got to get my job done, and this library, I mean it's probably useful to a lot of people, but it wasn't useful to me, and it could have been, there's no reason why there couldn't have been just like a thing that says like unsafe, do not use, is the name of the type constructor, right, but it didn't have that, and in my experience in the closure library those things are there, okay, so number two, so types are really nice because they often simplify your code, the value level code, right, so there's all the stuff about the types, and then there's all the stuff that's kind of like what's gonna get executed at runtime, and that is called the value level, because you're just talking about the values, like add one to this and whatever, and that if you put more stuff in the types, there's fewer checks you have to do, like things kind of work out better, you know, if you're working at an abstract level, start using type classes and stuff, you can simplify your value level code, which is great, but very often in libraries, like, you know, part of the same story, I would look open the library, the documentation for it, and there's just a mass of type information that you have to learn, even just to evaluate whether there is a or whether this has all the features you need, right, so I would start reading about these types and all these definitions, like, okay, how do these interact, and how am I supposed to use them, there's just a lot going on there, and I talked to someone who's, you know, very into types, and about this, because it was kind of bugging me, it was like, I can't learn some weird type, just to know whether the library is going to do the right thing at the value level, right, I mean, you really have to trust that they got the type right to be able to represent the value that you need, and so I talked to him about it, and he said, yeah, that's a thing, that, you know, in general what's happening is people are moving complexity from the value level into the type level, so they're having fun working at the type level, that's like solving a puzzle, just feels really good to, like, invent things and make machinery and stuff, and then look how clean my value level code is, but man, to get to understand how that type level machine works, it's a lot of work, and it dawned on me that what I was seeing, especially in this case of someone trying to make the, make the options to curl, type safe, what they were doing, and the reason it didn't have all the options was it was just like a little pet project, like, let me see if I can do this, let me see if I can, you know, push this push, it spread the Haskell philosophy of type safety over this, you know, C philosophy curl library that just uses strings for everything, let me see if I can do it, let me see if I can figure out cool types that represent the whole thing, and I mean I could just see it happening is like people are just kind of playing, now some people are getting work done, and that's cool, but a lot of the libraries I look through seem much more like theoretical, like let me see if I can do this, and that's fine, right, it's Haskell is a research language, and people should be playing and exploring the type system, but as a programmer, as an engineer, trying to get ship features, you know, that was an impediment, instead of, instead of trying to figure out like what are the ergonomics of this, in total, not just the ergonomics of the value level code, but the whole ergonomics of it, they were focused more on showing off in the types, I'm just, I'm gonna, I'm exaggerating, I don't know, and I'm projecting, I don't know exactly what they were doing, but that's often what it felt like, and I don't feel that in the closure community, I feel like there's very straightforward patterns that people use, you learn the patterns, and the patterns are simple things like options go in a map, the name of the option is the key, and the value is an appropriately typed value for you know, that option, and it's, it's really simple, I mean it's easy, it's straightforward, and you don't have to have like new types, we just use the exceptions, we don't have to come up with new error types, it's just, it just is straightforward. Okay, number three is kind of, I guess it's an ironic thing, so once I had, you know, internalized enough of the type system to have my own type discipline, where, because at first I'm trying to do things that I was used to doing, and they wouldn't work, and I would wonder why, and then I'd, you know, that I'd get in compiler error, and then I'd eventually figure out, oh yeah, that doesn't, that can't be sound, I can see why they, you know, the compiler is complaining, and you know, eventually you internalize some, you know, like a bag of tricks for like, okay, I can do this, but I can't do that, and you know, it turns into some, you know, useful coding skills, you can get anything you want done within the type system, and that kind of keeps growing as you learn more stuff, you know, I remember when I first learned how to make a monad transformer, you know, like, okay, now I can get, I understand how they work, and I can make my own, and you know, it just kind of keeps growing, and then when I went to closure, I had that all in my head, like, I had this discipline of only return values of the same type from a function, other closure programmers would be perfectly happy, you know, I would have, before I learned Haskell, you know, okay, some branches I'll return a string, some I'll return an int, I'll figure it out later in another, another piece of the code that calls it, like, I would do that, but after learning Haskell, I started having, I started realizing the value of it, okay, this always returns an integer, this one always returns, or this one always takes a list of integers, it doesn't try to, like, figure out, oh, you just passed it one end, okay, let me wrap it in a collection so that, you know, just to be clever and have a little bit better ergonomics, like, it actually, it messes with things, and I realize that in Haskell, like, you know, the soundness, the, the straightforwardness of the types is really important, and I could bring that from Haskell to closure, but I felt frustrated all the time, bringing stuff from closure to Haskell, you know, I'm a longtime LISPR, I'm very into, like, DSLs and, and, and ways of, of, of using, you know, polymorphism to, to make, get work done, right, so I had a little thing at my job where, you know, we're, if you're the author of this document, and your password is this, and your username, your user ID matches is, I had a little thing where you could just kind of query for, for the documents that you needed, and just compose up a query right there in Haskell, and everybody hated it, and they didn't just hate it, I could see why they hated my implementation, I was still learning Haskell when I did that, and, you know, I didn't know what I was doing, I had just, you know, was, was exploring, I was playing and, and trying to figure out how to get it to work, but what I didn't understand was they didn't even like the idea of it, they wanted a straightforward function that said, you know, fetch documents by author ID, and then just do some complicated code in there, and then another one, and just like do some complicated code, instead of a query language that let them compose the thing up, and, so there was both, there was that sort of social rejection where, you know, I would do a thing which I thought, okay, I finally got this working, isn't it cool that, that you can do this now, which was a totally like lispy thing to do, total rejection like, no, why would we even want that, it's, it doesn't make sense, and then, so okay, this might be, this might be, you know, the team I was working with, and I'm not saying like all Haskellers are like that, but then a lot of things that I, I tried, I, I just couldn't get working, like, they just, I could not figure out a way to represent the, the type I had in my head, which I used in closure into Haskell, okay, so it could be, my skill level wasn't enough, but I felt like the, a lot of times it was simply the design of the language and the type system were like just at odds with those kinds of things I was building, whereas they're perfectly natural in a lisp, and so, okay, all this is to say that I felt like it was much easier to transfer stuff from Haskell to closure than vice versa, if that, if, if that, and so that's the ironic thing, right, that Haskell can express more stuff than closure, because you can talk about the types, whereas in closure you can't, but it's easier to transfer the, the good parts from Haskell to closure than vice versa, now those good parts, now, I have to, you know, I understand, in Haskell, a lot of the good parts are built into the compiler, and so, you know, you're getting static checks on them, and then when you, when you transport them over to closure, it's all, it's all relying on you and your head, but I could still transfer a lot of them, just, just from, just the knowledge, oh yeah, I learned what a monad is by programming in Haskell, and now I write monads in closure, I do, I recognize, oh yeah, this is a monad, that's great, I don't have type classes, I still make monads, right, but to make a DSL that's based on a data structure and put that into Haskell, it's really hard, I mean, you have to write a million types to get it to work, okay, so that's, I mean, like I said, it's just a personal preference, I find that, that transfer is easier, I can go read about category theory and still do it in closure, whereas a lot of the stuff that I want to do in closure doesn't, doesn't translate, well, I wish I had a better example of something that I just couldn't get done in, in closure, had something to do with, like, I mean, you know, type classes are a very specific kind of polymorphism, and the thing is, each, it's not polymorphism like, like closure or Java has, where you can use any type that, you know, meets that interface, and you don't know what type it is, and that's the point, you just call the interface methods on it, whereas in Haskell, the compiler still has to know the type, and I was doing things where I was like, okay, and now here's where I'd return different things that have the same, that are, you know, using the same type class, so it all, and all I do is call methods them from the type class on it, so shouldn't it all be good, and these things like that, where there, that, you know, people talking about all the ad hoc polymorphism, that's what I wanted to do, and I couldn't do it, probably ways around it, not an excellent Haskeller, but I was having trouble. Alright, this last thing, oh man, this is gonna be hard to explain, and it's, again, it's like this feeling, I'm sure someone has, you know, someone will pick apart my argument, please do, please do, I want to, I want to learn, okay, so, yeah, I would rather, I would rather hear, I would rather get an email from you, just to be clear, where you tell me I'm wrong, then to like, read some post on Reddit, like, blasting me, I understand that when you blast someone like that in a public forum, it's also, it benefits other people who, you know, want to see both sides, they got my side on this episode, and now they're, they want another opinion about it, but I would like to learn too, so if you blast me online, send me an email, so I know, so that we can talk about it, okay, so, this last one, number four, so types in Haskell are, there's a, there's a lot of emphasis on, like, mathematical, logical soundness, which is great, because if you have an unsound type system, like, you're just opening the door for, for crazy stuff, right, then, you know, it doesn't make sense, and you're weakening your type system, and it just, it's, it's, it's a bad idea, but, it also seems like there were a lot of design choices in the types, and the type system, that really preferred a more mathematical formulation of types over a real-world formulation, and real-world meaning, so you're modeling something in the real world, and the real world has certain complexities to it, that you need to capture in your type, there's that, and then there's also, you're writing a program that has to live in the real world, and deal with, you know, systems that don't have the same type system, you know, over an API, they have to deal with, like, incomplete data, so there's a lot of, there's that two, two sides to it, like, the thing you're modeling does not, the thing you're modeling has a real-world complexity to it, and then the, the interaction that your system is having with the rest of the world is also complex, and one, one aspect that's been talked about quite a bit now, I think, is with, maybe as a type in Haskell, and it's, that it represents this idea of an optional value, and how do you model whether, like, that this value you might have it, or you might not, right, you need to, you need to have some notion of that, because you don't have, you know, so, not everything is required, right, so you have an optional value, and there's a nice rich hickey talk about this, about how making something optional is, it should not affect the clients of an API, so if the API requires something, let's, let's call it X, requires an integer X, and then you make it optional to provide X, the clients shouldn't have to change, because now all the old clients are still providing X, and the new clients can omit X, or choose to, to, you know, to provide X, and the outward facing stuff shouldn't have to change, of course, inside you're going to have to change, because now you do have to know, and check whether you have the X, that's fine, but the outside clients shouldn't have to change, and when you make something in Haskell go from, let's say, integer, because you're in, you're, you accept the number to now saying it's an optional in, so maybe in, it's a type change, which requires recompilation of the clients, and then you're going to have to propagate that maybe out, outward, somehow, right, so now instead of just providing the end, you have to say it's a just int, right, you have to change your code on the client, which you shouldn't have to do, I mean in theory, right, I'm talking about in the ideal world, you shouldn't have to do that, because you're only accepting a wider range of inputs, so the clients shouldn't change. Okay, that's my explanation of a rich hickey talk that he gave last year, I think, at the conge, and it's, when, when I think about it, I think, well, maybe, actually does capture the, the essence of the, like, sort of mathematical, pure essence of optionality, you either have the value or you don't, so let's represent that as a, as a, some type, right, you're just, we're just adding one extra case where you don't have it to the integer case where you do have it, and yes, that makes sense from a very formal, you know, nothing, kind, nothing is going to change perspective, but in the real world, optionality is actually more complicated, and if you're talking about API endpoints that change over time, because they do, because we do come out with extensions and, you know, software never, never stops evolving, it doesn't work so well, and I, he expresses it way better than I ever could, or I even thought of doing it, and when he explained it, I realized, yes, that's the kind of thing I'm talking about, where, yes, maybe just, it really does boil down the, the essence of optionality, but it's only that essence, it's like, they made the essence real, and they forgot about the real world implications of it, maybe they didn't even think of it, because that wasn't the point of Haskell, right, and they'll say, you know, it's impractical at all costs or whatever, no failure at all costs, something like that, so the other thing is, is at the, at the extremes of trying to make your system more and more type safe, you're pushing more and more checks into the type system, using it more and more, you have this explosion of types, because you, you don't want, for data modeling purposes, like you don't want maybe's everywhere in your code, so you kind of push it out into an outer level where you have different, so, oh, it's too abstract, I'm going to give a concrete example, we had a type called document, and document represented a document, a saved document, and at some point we started making a single page application for editing this document, and we needed to save the work of the, you know, in the, in the browser editor, back to the server, even if it wasn't complete, so we had this type that we couldn't fill out, we did not have values for a lot of the fields, and you don't know which ones, right, so we, we could have put maybe everywhere, and say, oh, maybe they haven't filled that out, they haven't filled that out, but what we wound up doing was having a thing called a document draft, and the document draft tried to, it was a separate type that said, okay, this is a type just for capturing all these values that, that we have, and kind of ignoring all the values we don't have yet, that the user hasn't inputted, and then eventually they would fill out all the fields, or at least all the required fields, and then we could create a document out of it, out of the draft, we convert the draft into a document, and it's like, yes, that worked, but it would be nice to have some kind of higher-level algebraic type that could represent all drafts of all the different types that we had, instead we had to write the types each one, and you could see that there's some similarity between, well, they were just capturing fields, and what we have, and we know we don't have the other stuff yet, so we're just, you know, it's okay, we, like, some kind of, some kind of higher-level thing that we could have made, and maybe it would be possible to sit down and come up with a type that represented all of our things, it probably would be, but it's just, we just never, we never got there, right, and it just, because it would just be an explosion of types, like it would just be, we would, we would try the types and probably get it wrong the first time, and then we have to make new types, and figure it out, and work that stuff out, and, you know, I just felt like the stuff that we were given in the language was not trying to address this real-world thing, now, this is where, you know, when I've had this discussion before online, this is where people say, well, you just need to learn the types better, or maybe you need this extension to Haskell, or this language has this other type of feature, I understand that there are, you know, there's a variety of type systems out there with different, you know, and you can make your own with Haskell extensions, and different languages have different ones, I understand that, like, the thing was, like, I'm talking about Haskell as what it is, like, you know, as what it was in 2013, I'm not talking about type systems in general, I think it would, it's definitely possible to have a cool solution to this in types, but I'm not talking about that, I'm talking about Haskell versus Closure, and this is, you know, I'm talking about, this is before spec, this is before other stuff that Closure has now, Closure's data structures can be used as this kind of grab bag of fields and values, and that's kind of like all you, you know, you didn't need to come up with a system for storing incomplete, incompletely filled out fields of forms, like it just kind of, it just kind of happens in Closure, and it's actually hard in spec because Richiki has admitted he got it wrong, that he didn't realize how important this idea of optionality was when he was developing spec, so, and he's working on a second version, okay, so it has gone on way too long now, I want to just say again, I want to learn, so please, you know, be gentle on me when you tell me I'm wrong, because if you're too rough, I won't be able to learn, right? Okay, so I'm gonna give the four things again as a recap, so Haskell values type safety or soundness over pragmatism, and when you're doing a very pragmatic thing, like making an HTTP request, very often you want to, you would rather like risk having the, you know, a poorly formatted string, then, and get, and get the message across to the other side, then be stuck with the types, you know, being not, not a complete description of the options that you need, often I found that a lot of the complexity was moved from the value level code to the type level, and that made it difficult to, like, okay, look, if I'm gonna use the library, I'll make the investment, I'll learn the types, but just to evaluate the library, it's often hard to, I mean, it just takes a lot of time to, like, understand these types, and when, why I need them, what they're for, poorly documented, stuff like that. Okay, number three, type discipline transfers to closure, because once you internalize it, it's in your head, whereas a lot of the stuff that I'm used to doing in closure, that it's kind of like advanced closure stuff, doesn't translate so well, and, you know, maybe, maybe it just means I need to, I need to study more type theory, and category theory, and, and, and more of the type level features of Haskell, and then I'll be able to do it, you know, but I've, I, I never got there. And then, finally, I think a lot of the, the types in Haskell are, they're, I mean, let's, let's be honest, they're, they're designed for their simplicity and for writing papers about, and use, for use in papers, and they're not really made for real-world usage. Now, if they're useful in real, in the real world, then that's great, but there wasn't their main purpose, and closure is, is different, you know, it's made a lot of choices to, to make sure that in the real-world, like it's dealing with these real-world issues of, you know, APIs having to evolve over time, incomplete data, coming in, poorly formatted data, all of that stuff. All right, this has been a long episode, I try not to go this long, but I felt like this, this is an important topic, mostly because someone online that I respect asked the question, and he asked, "Why do I prefer closure to Haskell?" So, there you are, there's your answer. If you like this episode, you can go to lispcast.com/podcast and find all the past episodes. There you'll find video and text and audio of all of the episodes. You'll also find links to subscribe and to find me on social media. Please let me know how I'm wrong. I hope that, I hope that I didn't say anything that kind of triggers some other arguments that I'm not trying to make. For instance, types versus dynamic. I didn't really go there. You know, I had, I mean, part of it was I had to work with the Haskell type system, and I really thought that it was a, it was a good type system. It was very helpful. It was like having a little logician on your shoulder looking over what you're working on, and I appreciated that, and I miss it sometimes when I work in closure. But yet, I still find myself preferring closure. So, there you have it. Okay, thank you so much, and thank you for listening. My name is Eric Normand. This has been my thought on functional programming. Rock on.