SF Scala: Introduction to Cats Parse By Jeff Lewis
Recording: SF Scala: Introduction to Cats Parse By Jeff Lewis
[Music] thank you everyone for coming to sf scholar uh on this april 14th uh tonight's speaker is jeff lewis and he's going to be talking about it giving an introduction to cat sparse over to you jeff perfect well uh yeah like was said i'm going to be giving a little talk here on cat's parse which is a library that i became acquainted with at the end of last year um as far as i know cats parse is new as of the end of last year as well um and i actually started using it because i i try to be involved in open source and sometimes i'm more involved than others but uh i saw on twitter that http 4s was part was porting their parsing of headers over from using the parboiled library over to using cat's parse and so i got involved with that and i i thought i chose an easy header to do actually because i chose the origin header and i was thinking oh it's a pretty basic one but i didn't realize that actually would include like ipv and ipv4 so ipv4 was pretty easy but ipv6 i was like really happy to dive into uh cat's parse and learn how to use it so it was a good time though and uh and honestly was one of my first big introductions to using purely functional parsers and i was just struck by how how powerful they are so that's kind of the background to this and then were actually just chatting a little bit about uh why you would choose to use cats parks um there are a lot of parsing libraries you could search around github and probably find hundreds of them for scholars so uh catsparce uh you know maybe other people can jump in if there are better reasons but from my uh from where i'm standing i think the best reasons to use catsparse are one that it doesn't rely on uh macros for its implementation so a lot of parsers in scala they make heavy use of the scala 2 macros which was fine for scholar 2 but those are kind of end of life now with skull 3 and so carrying your code over from scholar 2 to skull 3 if you're relying on one of those parsing libraries is going to prove tricky so that's why a lot of libraries have been uh rewriting their parsers using cat's parse because it will be a seamless migration over between the scala versions and another thing that i love about it is just that it does make heavy use of the cats library which is something that i'm already familiar with and something i already use in my application so i'm not bringing in a lot of you know new dependencies or having to learn new concepts really does build on top of the cat's ecosystem in in a helpful way so uh yeah without a little bit out of the way we'll go ahead and jump in here so first of all for anyone who is uh you know new to this subject i just wanted to briefly give an example of kind of what a parser is at a high level um and what we're mostly familiar with i think in the development world are json parsers since we we deal with that a lot with http requests and such so basically a parser is just taking a string representation of something and turning it into a more usable uh representation such as like scala case classes so uh here's an example of just like a little json payload that you would parse into a case class and so you'd have some sort of parser and you actually could use cat's parts for this or you could more likely use a library that's you know made for json specifically but um that's kind of at a really high level what parse or a parser is and then i want to quickly throw in the mandatory cat gif since i'm talking about some cats library i'm gonna talk about what cats is and i'm not gonna get much into uh category theory terms or anything like that here i'm trying to keep this as accessible as possible so i might mention them a little bit here and there but basically when i think about cats i just think of it in terms of being a library of functional programming abstractions um and i'm pretty much going to leave it at that for now um and really where cats comes in in terms of cats parse is in order to leverage a lot of the cat's functionality for parser combinators and what a parson combinator is is basically what it sounds like it's something that allows you to combine multiple parsers together so an example here if i have these parsers p1 through p4 and i want to combine them in some sort of way um here and an ore are examples of parser combinators i'm able to combine p1 and p2 using like an and combinator and then the ore as well and so when we get into more of the actual code what that ends up looking like is this lower level here um in catsparse you'll see this operator and we'll talk about it more as we go through this is the product operator that is used to basically and two parsers together and we'll talk more about what that means and then there's an or else operator or i believe in newer versions of catspar so you can actually use a single pipe character as well instead of writing or else out but that is basically equivalent to this so basically it allows you to treat your parsers like you would treat any good functional programming concept where you want to be able to compose things so you're able to start with these smaller units like these little parsers and then you're able to compose them together to form a greater whole of a parser that you can take advantage of so a few operators here that i'll pop up so i already talked about the product operator and i just wanted to throw these up there because i'm going to use these terms throughout the talk while i'm walking through some examples so in addition to the product operator we also have the product right and the product left operator which anyone familiar with uh cats will recognize these most likely um but basically what these operators do so product we talked about is basically an and um products left and product right are similar where they're and except for we're essentially ignoring either the left or the right hand side depending on which operator we're using which is helpful for a few reasons that we'll we'll cover one of them is that it simplifies the return type of our parser because we're kind of throwing away one side that we don't really care about and the other is as far as i understand it there are some optimizations inside the actual uh parsing engine that runs your parser where if you uh tell it that you don't need these values then it doesn't have to hold on to them uh as it's parsing so you have a little bit of an optimization there as well perfect so that's just a really quick really high level um introduction and what i'm going to be doing from here is actually uh taking uh something that i worked on earlier in january um for those of you who aren't familiar i started this little uh monthly scholar project that i've been posting at scholarmonthly.com um and in january i did a whole sort of i call them challenges but i guess uh they're really just kind of projects that you can follow along with and um i did a whole challenge that was based on cats par so this uh talk is based off of that so you're more than welcome to go to that website and follow along or you know reference it after the fact but um i'm just going to kind of go through what i call these fundamentals which basically it's a bunch of smaller practice problems that each try to highlight a single concept of the cat's parse library and then once you're kind of through all the fundamentals you hopefully have what you need to to dive in and do a bigger project using the library so without further ado um here is some code some example a very simple example of using cat's parse um so here i just kind of represent this is the string input that we would be putting into our parser and this is the output that we would be expecting so in this case i just created this little silt hierarchy that has either a zero or a one to represent a binary type and this example is if i put the string 1 in i would expect binary.1 to come out and so in constructing that parser you can see here that we have this parser as the return type so um important thing to note here is that we're not writing code that actually goes and takes an input and returns an output we're actually just writing code that creates a parser and from there on that parser you can call parser.parse and pass in the input that would then parse it so this is really declarative in the fact that you're not doing any parsing you're just describing how you would parse these certain inputs into your desired output so in this case we are taking the either the char zero or the char one and we are just telling it what to map that to and we're combining it with that parser combinator we talked about or else and so from that's that's literally all we have to do is so it's very declarative it's either we have this or else we have this and here's what we want to turn it into and uh from there we would be able to parse that so hopefully as we keep going you'll get a little bit of an intuition of how that or else works this example is almost too simple but um just shows you how that or else works um this is similar but we're going to be showing how the product operator works or that and that we talked about um so in this case we're taking a letter and a number and as input and we would just return it as a string but inside of this letter and number class so you can kind of think of this as parsing and validating at the same time but one thing to know is there's this really great uh import you can use uh that's inside of the cat's parse core library it's called rfc 5234 and it basically contains a bunch of pre-built parsers for you such as alpha or digit that cover just like you know alpha characters digit characters there's also things like white space or tabs or just a lot of things that you need when you're building parsers um so you'll probably be importing that into pretty much any parser that you build at least uh in my experience so far um so yeah in this case we're going to be taking a letter and then a number and this string operator that we're calling here just takes this and turns this into a string if we didn't have this string here then what the product operator actually would do is it would return this as a tuple so we would have an alpha and then followed by a digit inside of a tuple and then we would have to kind of stringify it ourselves whereas a string goes ahead and it collapses the letter and number together for us so that we can just map it directly into our case class um awesome so this example is building on the binary one that we did earlier except for now we're being a little more ambitious and we're taking a non-empty list of those binary characters so you can see here if we have the input one zero one one we would expect to get this binary list class with a non-empty list in it containing one zero one one of our binary types so you can see here this binary parser right here is the same as the one that we built above this just parses a single binary character given a zero it'll return to zero one it will return a one and then we are using this new um operator called rep which is short for repeats as far as i can tell um and so in this case we're just saying this can repeat any number of times and then this actually is really nice in that it already gives us a non-empty list as its output and so we can just map it directly into our binary list something i should have put in the slides more explicitly but something that you'll maybe think about here is what if we wanted the list to be able to be empty rather than a forced non-empty list and that's where there's actually another operator called rep 0 which we'll see later but that would actually return a potentially empty list so it would return a normal scala list instead of a non-empty list and that's something to note actually is that there's a parser type in this library and there's also a parser zero type and the difference between them is if you see a parser zero it means that it could accept an empty input whereas a parser will consume one or more characters and if you look inside of the january skull monthly uh walkthrough that i was referencing earlier you will see there it used to be reverse you used to have parser one and then just parser instead of parser zero so that actually got flipped since you end up using parser one more often than parser zero so i just made more sense to have those flip-flopped so here is the next example and so these are starting to get a little bit more complicated you'll see and in some of these examples as we move forward i'll put um some examples of multiple types of input that it should accept and when you're building a parser you also start thinking about types of input that it should not accept um so we'll talk through that a little bit as well um but here we just have a really simple um name class and we just want to take in someone's name and the parameters i didn't write up on here because they're a little long but the parameters for this were that the name has to start with an alpha character so a letter of the alphabet upper or lower case and then it can contain spaces it can contain apostrophes for examples like this name here o'brien and i think that was all of the constraints on this one so we're leveraging things that we have used before in the past such as the product product operator but then we have a new um operator as well that we're using called one of and this basically allows us to pass in a list of different parsers and say that it can be any one of those things in this place so in this case we're passing in it could be an apostrophe it can be an alpha character or it can be a space and again these alpha and space they're both coming from that rfc import that i showed earlier um so if you're not sure where something's coming from it's probably from one from that import or just from the cats parse core import um but yeah so this one is pretty straightforward then so we're saying it has to start with an alpha character there has to be at least one character and has to be alpha and then after that there can be um any number of well i guess one or more of these characters following that and again we don't want this as a tuple here we want to just take it as a string because that's what our name class is expecting so we just pass that string into the name class by mapping over it and um it's probably worth noting that we do have to map over this because this this returns if you take everything other than this map it's actually returning a parser of string instead of a parser of name so when we map we're basically getting inside of that parser in order to transform its type from string to name so if you are familiar with cats i will just briefly mention that parser is a monad and so you're able to use all of the monadic functions as well as functions from functor or applicative with it uh however uh you should limit your use of flat map as much as possible for performance reasons you should rely more on like the product functions that we've been showing um product right product left because of performance on that another thing i'll mention real quick before i go into this slide is uh as you're going through this library the most helpful thing that i found when i was like not sure how to do something was just to use my ide to click into the the different functions and look inside of the parser implementation and it's really pretty approachable how it's been implemented because it's not making use of things like macros excuse me like other parsers uh are like the cardboard one we talked about or others uh you're really able to follow pretty well especially if you have a little bit of knowledge of um cats and i think the whole implementation of the parser like the the core part of it is something like three to four thousand lines which sounds like a lot but a lot of that is the actual parsing engine and the part that you really need to look at kind of understand what's going on is is a lot less so it's really pretty approachable to read through the the library and what's going on um so this example we have we're going to be parsing out a score so like a score of a game of some kind uh the one team versus the other team score and so we have this simple case class this time we're taking these as integers rather than as strings you'll notice and we introduce a few new operators i believe so the main one that we're using here is this product left operator that we talked about earlier so we start by just taking and thinking about these one piece at a time so how would we parse this one two three or this four five six for example and in general that's i think the best strategy for building these parsers is just thinking about the smallest piece possible and thinking how would i parse that and then combine them up from there so in this case we created a little mini parser called multi-digit which is just going to take in a multi-digit string and it is going to uh represent just that so uh again we're using digit from now rfc we're saying that it can repeat uh one or more times and then we're just using that string function to go ahead and map it and turn it into an integer which we know is going to be a safe operation because this digit parser will only let digits through so we don't have to worry about that to int i guess other than if we had cases where there could be too long of an int or something then i'm actually not sure what that would do but um so that maybe that could be improved a little bit but in general that should work um and then here we have our first example of using one of these other product operators where we're ignoring one of the sides and the reason we're able to do that here is because this little parser here represents the space hyphen space so you can see we're using just char to say we're expecting a char of a hyphen and it's going to be surrounded by spaces so we could have written this like space product char with the hyphen product space but there's luckily this function called surrounded by that comes in handy pretty often where you have a character that's gonna have uh something on both sides and we don't actually need that to create our score class and that's why we're able to go ahead and just use the product left operator and basically throw away this right hand side because we don't actually need this in order to get our final result so that does simplify what we're going to be dealing with inside of this map and like i mentioned earlier i think there's a little performance optimization involved there as well and then we have our uh last multi-digit so we have the two multi-digits making up that parser and i also will mention something that took me a minute to understand is you will have to use parentheses a lot of times when you're using these operators that ignore one side if we didn't have those parentheses then we would be ignoring that entire side so we wouldn't be getting that other multi-digit output so that's just something to keep in mind as your operator precedence and where those parentheses go and with that uh i guess not with that but one other point i want to make about this uh you'll see me use this a few times if you're not familiar with this.twofolds call this is something that you can do on a case class uh to to build a case class where you're basically saying instead of passing in the parameters one at a time into the case class you're basically saying i'm going to pass in a tuple that represents this uh case class so i'm passing a tuple of intent instead of passing the parameters one at a time so it's just a little bit more of a terse uh syntax which you know skull developers love that her syntax so all right so this one is a little bit uh more verbose you'll see some of these uh examples i really had to think how i could i tried to restrict it so that the examples would be kind of forced to use certain concepts rather than a lot of times there's a lot of different ways to do something so i was trying to come up with examples that would be almost forcing using a certain concept so that's why some of them are maybe a little bit longer even though you could have a shorter example to illustrate the same thing so in this case i created pretty much my own little tuple type you could say it's really just two case classes called two and three that represent just a tuple um with either two or three values inside of it and the reason that we're gonna do this one is because we're going to show um let's see this back track what backtracking does um so some parsers if you've used other parsing libraries you'll know maybe a little bit about backtracking and a lot of parsers actually backtrack by default so you don't actually have to put this.backtrack anywhere because it will automatically backtrack you actually have to tell the parser if you want it to not backtrack um the reason as far as i understand it that cat's parse decided to have explicit backtracking is because uh backtracking uh by default is less performant because a lot of times you don't actually want to backtrack and so having it where you just specify it makes it so that you're not taking the same kind of performance that you would have if it just defaulted to backtracking um so for those of you are familiar with backtracking basically what it is is it's telling the parser what to do when an error is encountered so if i'm trying to parse in this case uh the three tuple out and i encounter an error while i'm parsing such as like there isn't a third item because it's only a two tuple then i'm telling the parser go ahead and backtrack basically unconsume all of the input that you consumed as a part of trying to parse this so then it can be consumed by something else down the road and so the reason that's necessary is because a two tuple and a three tuple look the exact same until they don't and the parser doesn't have any way to look ahead so it's just kind of reading along and then the parser fails and then we tell it if you do hit that scenario then go ahead and backtrack unconsumed move your cursor back and then try to parse it with the two parser instead um so i won't walk through each step of this parser specifically because i think the rest of it is just using examples that we've already covered but that's that's really the new thing i guess one other thing to mention is you can see here we are actually mapping and uh matching on this uh tuple so this is what i was saying if you don't put this dot string on your parser then it actually just spits out the items individually so here we actually have the three items so we have item one item two and item three which i called a b and c here um that we match on um and the nice thing about surrounded by is that it already for you ignores the stuff that is it's surrounded by so this item dot surrounded by is actually just returning an item for us so b here is just a string which is uh really handy awesome so here is another example of an operator we're going to go over this one is called soft and it's somewhat similar to backtrack in that it is telling us or it's telling the parser rather what to do in the event of an error the difference is uh the bactrack example was really dealing with a an or else parser combinator so we were saying if there's an air happening on this side of the or else then go ahead and backtrack and then go ahead and try to parse the rest of it and soft is really dealing more with a composition on a using a product operator so we'll walk through this and i'll kind of show you what i mean so first of all we'll look at how this parser works on just this first example it's kind of what we would expect we have basically a name we'll ignore the software now followed by a dot which can repeat zero or more times followed by another name and the zero more times is because of the other examples so um if this is the only name that we needed to parse this could actually just be a normal rep rather than a rep zero so it'd basically just be a name followed by a dot repeated any number of times followed by a final name and that would be our username however it gets more complicated when we need to parse uh different types of inputs such as a username that doesn't contain any dots and the reason for that is because what would happen if we were parsing this without this soft call right here is the parser would consume for this name it would consume the entirety of this schmidt input and then it would go and look for a dot it wouldn't find a dot and so it would fail and so that input would completely fail to parse so what the soft call does here is it allows us to say if you don't find this dot if something fails on the other side of this product operator in other words then unconsume the input that you consumed here so again it's really similar to backtracking except for it's dealing with these uh product operators rather than the or else operators um and that allows us to basically this whole thing ends up consuming nothing on the schmidt case and then this name is what ends up consuming that input and one other oh yeah go for it um i wonder um maybe somewhat maybe you or maybe someone else knows some more theory on on how to think about this but usually uh there's many ways to write a particular parser so here you've written it in such a way such that you say okay let me try to read the name and then a dot and as many of those on the left hand side um and you've explained the use of soft here and maybe this was just an example to show to show the use of this kind of soft coupling which is a result of the non-backtracking metaphor um but an alternative way would have been a kind of right associative way of parsing this where you say i'm always going to get a name and then i might get i'll get zero or more dot names on the right hand side and then that would not incur this issue about back back tracking and i wonder if you have any sorry the dog is uh begging for my attention um i wonder if you have any ideas uh about when uh when you see these things how to write them one way versus the other what your thoughts are yeah i know that's that's a really good question yeah it does it does come back to what i was saying where i tried really hard to think of examples where you couldn't do it both ways but sometimes it's really hard to like think of an example that absolutely locks you into the one implementation um but yeah and in general i mean my my preference would be towards not using backtracking or soft whenever you can avoid it like if there's another way you can think about it the parsers tend to be a lot simpler to to write and to read so um but yeah sometimes your brain just doesn't even want to cooperate with you and you can't think of how to do it without so i guess it's kind of a a balancing act there um but yeah if anyone else has any input on that feel free to hop in as well hi um uh so getting uh getting out what oscar was saying there um backing up to the theory a little bit um so uh parser commenters implement uh a form of uh sort of ll type parsing basically um with lsl piercing with sort of non-commutative or which gives you sort of like this disordered choice thing um and what it turns out that implements is the full set of deterministic context-free grammars and the trick with deterministic context-free grammars is that there is a proof that by refactoring the grammar for any deterministic context free grammar you can refactor it down to something which is l1 what l1 refers to is as long as you have one token of look ahead you can parse it without any backtracking whatsoever so it's actually even stricter than soft uh where soft technically lets you sort of backtrack over like the whole thing would be like if soft only worked on car or something like that um so technically it is assuming you don't use flatmap flatmap is weird for other theoretical reasons but like if you stick to solely applicative parsers you can always refactor your grammar such that the only use of soft is on a car but you will contort yourself into terrible terrible thoughts and and just like your errors will be awful and your life will be awful and i really really don't recommend it this is why people invented yak but like it is actually theoretically possible to do without soft and backtracking if you want that that's really good to know um yeah they're actually there's actually one parser i'll have to point out in the challenge side of scholar monthly that i could not figure out a way to do without uh backtracking so it'd be interesting to see if someone else can come up with a better uh little solution than what i did i know but appreciate the context that's cool i'm gonna keep going i think there's a couple more of these and then i was going to uh show my ide and go through a few examples of the other side of the skull monthly challenge that i created so okay so this one is actually really simple kind of a breather from what we're working on on the other problems um this example is really just to show you that you don't have to know everything beforehand like you not everything in your parser has to be static so in this case i don't even know what the allowed chars were going to be before creating my parser but that's okay i can go ahead and have those pass in and still create a parser so this really unlocks a huge number of possibilities for creating parsers when um you're able to create these parsers with the with the declarative dsl that counts parse provides and you don't have to know everything about what your parser will accept or do beforehand so you really start to have a lot of power um when you combine those concepts together all right so here is i think this is the last yeah this is the last example um and basically here is just showing uh the option operator that catsparse provides so uh here we have just a car type which supplies a make and a model where the model may or may not exist so two possible inputs would be like nissan versa or just nissan and then we go ahead and put that into uh car type so you can see a lot of familiar things in this parser we have our make and our model which are basically the same thing we could have abstracted that little piece out of course but the only difference is that this dot question mark says that instead of this parser returning a string it returns an option string and then we do a similar thing down here where this space after the make of the car may or may not exist because if the uh model of the car isn't there then we're not going to have that trailing space so that's why that question mark is there as well and you'll see that we're using the product left operator because we want to go ahead and ignore that space we don't actually care about it we just want the make and the model as a tuple so that we can create our final car type all right so that is pretty much it for those fundamentals and so the january challenge that i came up with uh is i was watching the netflix show the parks was gambit or the the queen's gambit i should say if it was called the parsons gambit that would have been a whole different show that's what the challenge is called because it's basically taking uh portable game notation from chess and and they kind of like cover it a little bit in the show it's you know not a big part of the show but uh basically it's taking that notation and building a parser for it and it's really crazy because uh you know a crack at this with cat's parse and not really trying to write it with as few lines of code as possible the whole thing was less than 100 lines of code and uh you can imagine that there's quite a few little rules to the how the portable game notation works and so it was really awesome to see how cats parse was able to go in and just make this whole process very simple so i'm going to share a different screen here and kind of show in the code what that looks like so let me just rearrange my windows here okay perfect i'm gonna make this a little bit bigger and increase the font size all right someone just chime in if that is still too small but um i'll go ahead and start by showing stuff i'm not gonna get into the particulars of how portable game notation works just because there's a lot to it and if you if you want to really follow along with this you can look at the challenge i uh on skullmonthly.com i go through and outline kind of how it all works and all that good stuff but um basically this is just the model that i created so this kind of comes out of the box in the starter code um and it's just going over like the model you need so you have ranks and files on a chessboard that's just the rows and the columns you have your just different piece types that you can have on the board um there are a square which is just like a coordinate on the board so it's like a row and a column combined or you know file and rank rather um and then it gets really interesting with uh the notation there are these disambiguators because when a move is recorded you cannot always tell the difference between uh two pieces uh it would just by recording like the basic move it's possible that two or more different pieces could have completed that move so there's different types of disambiguators that portable game notation takes advantage of and then there are things like check status so that's represented on there so if you're familiar with chess there are things called checks that you keep track of on the notation and so then we get down to like having what a move is which is basically there are a few different types of moves they're just your regular moves which just capture what kind of piece did i move what square did i end in on the move and a disambiguator if needed i guess a few more things so if you captured another piece as part of the move and then um if that ended in a check as well so that's kind of like your standard out of the box move and then pawn moves have their own exception for how they're represented um a promotion which if you don't know what that is don't worry about it is a different representation and so is a castle so there's just all kinds of different moves and this is why this was kind of an exciting example to use uh cat's parse with just because there's a lot of different little exceptions um and then when it comes to a turn which is just you know each player making a move uh you basically have either a full turn which is two moves or a partial turn because the game could end on just a single move um followed by finally an outcome there are several different ways a game can end um and that leads us up to having this whole game so now that you're a little bit familiar with the model i'm going to show here not in super great detail other than maybe uh in a few parts uh how i went ahead and built this parser um and like we kind of were talking about there's a ton of different ways you could do this i actually saw a few other people's solutions who followed along and did this challenge and um they were like you know not completely different but they had some significantly different eye ideas and different things that they did so um but you can see i'm basically just taking each piece of the model and building a parser for that piece of the model by itself so like how would i parse out a file how would i parse out a rank um or a square which is just you know like you'd imagine a file uh followed by a rank and then just kind of building this up through all of these different parsers to where they can start to be combined so move is where it really starts to get kind of uh i guess gnarly compared to the other ones where these ones are like pretty basic you know it's just like a mapping from one thing to another move is where we really start to combine everything together um and this is the parser i was actually referring to a minute ago where um i wasn't entirely sure how i could make the non-pawn move parser and so a standard move i don't know why i called it non-pawn instead of standard but i wasn't really sure how i could make a standard move parser in a single uh parser so um i kind of ended up with this whole mess and i'm trying to remember the whole reason but i believe it was something to do with the disambiguator um because it can look the same as a square and so the parser when it was a singular one it couldn't tell the difference between if it was looking at a square or if it was looking at a disambiguator um so anyway if anyone feels uh ambitious and wants to go look at this how to do this in a single one it would be cool to hear about that so you can see extensive use of just everything we talked about these option types these product operators of different sorts and then really i try to build up still a parser for each different move type and then say how those are all combined together um at the end and then ultimately we are able to uh combine those to create a turn um this is the parser for like the outcome of the game like we talked about at the end of the model and then it really comes together pretty simply for how you create a game in total so it's basically just turns that repeat and we do have to have a backtrack here because there are some uh characters that could be consumed by turn that could actually be an outcome um but basically we're just having a repetition of a bunch of turns followed by the outcome and that's the whole game so that's another thing i really love about uh cat's parts and really declarative parsers in general a lot of the love i give kaz pars is probably uh more fairly given to a lot of declarative parsers but i really love that you can just zoom in on one piece and kind of see what is a game made up of and you don't have to dive in any deeper than you want to if you want to know what makes up an outcome you can if you want to know what makes up a turn you can but you really don't have to dive into those pieces unless you really want to or need to for some reason um yeah so that is sort of a quick high-level overview followed by you know maybe what more realistic example looks like i would encourage if anyone is more curious in more real world examples go look at http 4s and all the parsers in there you'll see there's a whole bunch of different examples and i think some interesting use cases um and if you want to embarrass me you can go look at the ipv6 parser and show me how it could be improved so that was that was a fun one to build and uh yeah so i think that the overall takeaway for me is just um i used to honestly think that writing parsers was super boring but it's because i used to write them imperatively or i just kind of iterate through like they teach you in school or whatever with your little c plus while loop and um uh learning how to do it with declarative parsers is just kind of a game changer where uh it really becomes a more manageable and approachable problem by just decomposing your parser down into little pieces and then building it up from there so so yeah i think that that's all that i had so i'll uh open it up for questions if there are any any questions anyone on zoom or twitch great talk uh jeff thank you so much thanks yeah appreciate it let's see so i wrote up some parsers using the ato library like late last year like one week before cat sports came out or or something is there an immediate benefit to porting them over to cat's parts i assume this makes libraries like otto more or less obsolete or are there reasons to prefer one over the other in certain situations um so i'm not familiar with the other parser that was referred to there um like i said there are a lot of them so um i would say it really depends on your use case and i'm again i'm not familiar with that one but if it does take advantage of using macros it could be more difficult to port your project over to scholar three so that could be one reason to migrate uh another other reason that i liked this library like i said is it uh makes heavy use of cats and those cats type classes which i'm already familiar with and i found it because it doesn't use macros it's like really easy to just click into anything and see what it's doing a lot of times if you're clicking through a library and trying to understand it and you come across some macro code it can be kind of um it just kind of like raises the bar for what you have to do to read through and understand what's going on uh so yeah i don't know i think it really just depends i would say there's no maybe no like inherent value in doing it just for doing it um but if you are looking to migrate to scholar three or um whatnot it could definitely be worth it and and honestly like it can be pretty easy a lot of times uh other parsing libraries use very similar notations or or things so it can be fairly simple to difficult depending on the differences between where you're coming from and and cats bars great yeah the the same person asked the questions saying that i don't think it's based on macros but i don't think it's meant to be performant as primary use case really talking about at all of course yeah gotcha yeah yeah so in that case i mean i guess you could benchmark and see if you know i would say my my philosophy with performance is just if it's if it's not a problem it's not a problem you know if it is a problem then find something that will uh solve your problem and move from there i we were talking at the beginning of the call um i think it was daniel had done a benchmark like a kind of for fun benchmark between cat's parse and par boiled in http 4s to see what the difference was and he was saying that cat's parse was a little bit slower than the parboiled implementation but not enough for it to matter so the fact that it doesn't rely on macros and it's only a little bit slower for me i think for the majority of use cases that makes it worth using um but yeah beyond that if if you're between two libraries that are similar performance and neither use macros then there may not be a huge reason to migrate stuart you you asked how much production use is cats parsing yeah that's a good question i don't really know beyond the fact that it is going to be or is in the newest http 4s version i'm not sure if that's been released yet or not but um you know that i'm sure we'll see really widespread uh production use pretty quickly as people upgrade there and i know i've heard talks of it being used in some other libraries i'm sure some more people maybe on this caller or otherwise could speak to that better who are more involved with more uh like a wider range of libraries but i have seen it being talked about in a lot of places ryan peters has put 100 agreed about performance not mattering until it matters more interested in how cut sparse seems to have some momentum behind it as it just came out i think that alone is a pretty convincing reason yeah yeah i totally i totally agree with that i think uh the fact that it is kind of like the cat's standard parser i think it's more likely to see a lot of support over a long period of time so yeah if you're looking for somewhere to go that's probably a safe bet i i personally feel like caspar's would be a pretty safe bet especially with the implementation not being really complicated and macro heavy if worst case scenario if it did like stop getting supported you could more easily support this library on your own than probably a lot of others ralph on twitch is asking will the chess example run in scholar three ah that's a good question i haven't actually tried to do it in scala three um it should i think the only dependency is cast parse so um yeah it should yeah let me know if you do give it a try jacob kubrickos just said the bronze i mean cats bars yeah he met cat sports runs not necessarily your chest application so cool yeah i guess the other tendency i have is uh i use m-unit for running the test so i don't know if m unit's uh scala 3-friendly or not but you could always you know swap that out or something if you really wanted to port it over believe it is scholar 3 friendly i believe it is i haven't tried myself but i believe it is correct me if i'm wrong anyone online any more questions or discussions around cat sports anyone is cast parts restricted to inputs of type string or can it also accept fight array that is a good question um i don't know for sure i mean we could actually maybe see real fast it looks like the parse function just takes a string so you probably would have to convert from your byte array over to a string um prior to parsing unless there's another function that i'm unaware of great there's a slight delay with twitch so i'm just waiting to see if there's anyone for your answer to be actually won't hurt your answer before checking if there's more questions any more questions everyone great oh here we go uh kind of on a similar note i have sometimes wondered about the difference between libraries like cat sparse versus things like s codec are those two things isomorphic in some way or are or are there things that escodec does with binary data that is specifically not a good fit for cat sparse that's a good question i'm not super super familiar with uh s codec um if anyone else on the call is feel free to build that one any takers yeah what's that no i just asked uh if there was anyone who's familiar with this codex carry on sorry oh yeah i was just going to say if it does have a lot of like optimizations for binary specifically i would almost guarantee it's going to be more performant if you are working exclusively with binary data like the example that i showed of working binary and cat's parse was definitely just kind of a contrived um thing i like you're not gonna really have you know bit shifting or things like that uh built in so i don't know but i would imagine you're like this is definitely more uh pointed towards uh higher level parsing i think great mentor on twitch i said thanks for the talk and and or pointing to scholar monthly and that was followed by thank you for the talk by ralph as well uh stewart uh just asked how about a scholar monthly pitch any teasers um you know i've uh i've done like three of them now so took the month of april off got a little bit busy uh last month with work and stuff but uh yeah i mean i think there's a few fun challenges in there so if you look at the first three um they cover the first one is cats pars the second one is going over the folds operator in scala and kind of teaching you uh how to use fold on different types of collections or even on like sealed hierarchies um and then the march one was uh going over the monocle libraries they're talking about optics and i realized that one was a little bit of a jump in terms of uh how advanced it was so that one is a little bit trickier if you're not familiar with some more functional programming concepts so i'm thinking there's going to be some coming that are a little bit more accessible than that one um i'm hoping to do some on some of the cats type classes coming up like applicative which we talked about a bunch today just because i feel like applicative alone uh you can do just a lot of really cool stuff with so probably a little bit there um and i'm hoping then to you know get a few more advanced ones again like uh scala 3 meta programming and stuff like that so so yeah check it out and uh i'm very open to hearing feedback so i have like a little discord channel you can get to it from the website scalamonthly.com and uh yeah share your feedback i i don't think that it's uh a ton of people like following it yet but it's it's been fun for me to kind of dive in with these libraries and put these together so ralph's asked a cert might be an interesting to look at look at also what was that ralph just said the circ might be interesting to look at yeah yeah absolutely yeah so i really like doing them on libraries because i feel like there's a lot of really cool libraries that are out there and uh i i feel like there's a lot of uh work that goes into just maintaining them and it can be hard to maintain your library and create a lot of content about how to use it at the same time so um yeah trying to kind of get behind more of that side of the open source community to hopefully give some more practical examples because i know for me some of these things were harder than others to learn in terms of functional programming without you know certain like entry level examples right any more questions or discussions anyone twitch has gone a bit quiet completely earlier great well jeff thank you so much for the talk it was it was a fabulous talk really appreciate you taking the time to do this talk here thank you everyone on zoom and twitch for attending and um i'll be publishing the video for this event quite soon as i've had a lot of things about when it's going to be published so i think there's a lot you've got a big keen following who want to see it so i'm hoping to get it out quite quite quick um thank you so much and uh if there's nothing else for us to discuss i'll keep the rest of the evening back to it back to everyone thank you all thank you jeff thank you very much jeff thanks all i appreciate it thanks all for joining really nice to see everyone thank you bye everyone