Intro to Cats-Effect (Gavin Bisesi)

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
[Music] hello everyone my name is Gavin becae I'm a principal engineer here at taken metrics I've been here about two years over that time I've been working on our back-end systems primarily and working a lot on our data platform and some of the tools that we've started using some time along the last year and a half I've been really taking advantage of the cats cats effects type level ecosystem there's not a lot of great material out there on the library I think in terms of beginners getting started if you don't already have a concept of what's going on there's some great material out there if you already kind of know how things work with it but there's not a ton of things even to people just learning so I'm gonna get into that so in my blurb on the meetup group I talked about it as a functional side-effect library so I'm gonna talk a little bit about that means so when I talk about functional programming I mean programming with pure functions which is to say referential transparency it's a really important term I'm gonna give a short refresher for anyone who hasn't come across it before because it's pretty foundational to understanding any what this library provides and B what is the point of using it in the first place so referential transparency is a property of expressions in code and specifically not statements and declarations that says that you can replace the result with its definition without changing the meaning and it's pretty simple but there's a lot of kind of things that go into that this is an example of something that is referentially transparent here just edition you can replace the result of addition with the expression it doesn't change the meaning this is example of something that is not referentially transparent i cannot replace print wine here with hello and reuse it multiple times and have the same meaning the shot of this is in the small this isn't really a problem for most people the problem comes when you have a code base that's worked on by a team of people over weeks and months and anyone tries to touch anything anywhere then it starts kind of falling apart and it gets a lot harder so the point of using referential transparency encode is that you can read code and understand what it means without reading the entire rest of the codebase you don't need to know where something is called because you know that where it's called can't change the meaning it also means you can change existing code and know that you haven't broken it by accidents because something implicitly relied on for example being called in a certain order at a certain time and you changed it in a way that you thought was safe but it turns out that was the one test case you didn't have written in your codebase so just summing up you want to write code that people can read and understand and you want to write code that can be changed without breaking things that's the point so just more foundation really quickly because I use this word a little bit in the talk of coming and not everyone necessarily has come across it especially if you're from more of an LP or just like imperative programming background is Combinator's so comma leaders are composable building blocks that modify their input and return you something else in a reusable way and an example of this is the map function you have a list and you can map a function over it and you get something else you can filter those are some really simple examples of Combinator's so they don't necessarily do a behavior or Express much logic in and of themselves but their building blocks that you can compose into something bigger that has a specific meaning without rewriting all of that every single time an example of something that's not accommodator is a C style for loop you can't reuse a c style for loop very easily you have to basically repeat that over and over in the code every time you want it so this is just some set up for the rest of it the slides are compiled using time so I need by imports I'm going to talk about the imports and things a little bit later on so tax effects provides a type called IO which is one of the big sort of the main interface that you'll see learning library for the first time and it's fairly similar in concept to future at least we'll look that way at first if you've used teachers you can construct something using the apply method or just like parentheses you can call map you can call flat map and in the same way that when you have futures you should not call a weight when you have I always don't call the unsafe run functions they're there for kind of end of the world and not in your code so values in your code that have this type are not running something so for example print line that I had an example before when your execution hits that line of code where you call for the print line function it is doing something then in there io values on the other hand are pure descriptions of a computation so this is not the computer robot assembly cake this is a recipe for the robots to assemble a cake so and the value of it so it's type parameter eyes on a it's a computation that will or when it is executed and that's specifically not what it's constructed but when it's executed it will either produce a value of a or it will fail to throwable or it will never complete and that's perfectly fine and normal for example most of the time a web server you don't actually want it to like complete and return you want it to just serve that's a fact in addition IO offers some type classes anyone not familiar with that word I'm gonna summarize in a nutshell as a kind of pattern of design that allows you to talk about what behaviors a type supports without using inheritance and it allows you to apply it to types that you don't own and just be very in general polymorphic you can write code that says give me something that supports the behavior I want for example the ability to call a map and then I'm gonna write some code that does stuff I don't need to know anything else about it except that I can call map I'm not going to talk about the type classes in cats effect I just want to mention that they're there in practice we do use these more than i/o here in our code base but basically it's just describing the capabilities the IO has in a way that the ecosystem can kind of play together without all having to be exactly IO so but I'm gonna focus on IO because it's sort of the more pertinent point for someone just learning this is a graphic from the documentation talking about the type classes in Kats effect in their relationships I'm not gonna spend any time here just these are things that exist you can look them up later so I think again going back to the future' comparison when you start using IO it looks very similar to future and so it's kind of hard to tell what is the point of using it again I've mentioned referential transparency but that's not like you know business sprint goal let's have referential transparency is not really a selling point so I'm going to compare it with Scala futures a little bit because I think that's the most familiar thing that people not already exposed to the FB ecosystem will run into so for describing actions io is a value that describes how to perform an action a future is a handle to an action that has already started and it will allow you to eventually get the result of it in terms of the way they're designed for speed I always optimized for throughput and future is more optimized for fairness future thread shifts by which I mean in JVM terms submitting a thread to a thread pool submitting a runnable to a thread pool every time you call a map or flat map so if you have a big chain of just pure data transformations every single one of those gets rescheduled on the thread pool which is horrible for throughput because you're constantly blowing your CPU cache and shifting threads so you don't need to IO on the other hand has an explicit thread shifting model where you can tell it where you want it to shift and it will occasionally introduce some if it thinks it needs to when executing just in case you should have but didn't ah let's see and I don't think it's a huge point but it does benchmark consistently fat just faster than future for a lot of workloads futures in Scala are not cancelable like I said they represent a computation that has already started and they don't provide the ability to cancel there is the thread cancel on the JVM it's not really safe portable or reliable so in general that's not helpful to use it on the other hand I always can be cancelled or rather you can describe you can have an action that describes doing concurrent work and maybe canceling some of it and then you can have things to happen in response to that cancellation unless you express very high-level logic where as future a common example of this might be let's say you are an HTTP server and you are calling some back-end that you want to serve results to the user that back-end is under load as taking 30 seconds to reply to any requests you might want to say you know what if it takes more than two seconds cancel it I'm gonna go to my low resolution service that gives a rough approximate of an answer in a tenth of a second instead and I'll let you do that and that thread that's doing the business of making those network calls can be cancelled and your CPU is not doing work that you're paying for for results that you don't care about so IO can describe concurrent and parallel actions it it uses an end-to-end green threading model which is basically JVM thread which is kind of sort of the underlying part of futures is represented by an operating system thread - Scala jeaious and those are fairly expensive they are managed by the kernel in terms of scheduling to some extent and they take a bunch of memory and overhead whereas for i/o which is a subtype that's called fiber in the library you can have many thousands of them running concurrently and it's really no load at all it's just object allocations to some extent and everything is non-blocking by default I did mention Scala j/s because running on JavaScript you only have one thread there is no such thing as multi-threading in JavaScript and cats of X does support it works on Scala J's just like so for concurrency this is a little more detailed than you probably would run into at first using it because a lot of applications don't necessarily need to talk about concurrency very much if you're a web server you don't really can care about concurrency probably you have an HTTP library that's just this is my route here's how I handle requests you handle all the concurrency stuff for me but I just want to kind of establish the point that you can write complicated and sophisticated concurrency logic and IO we've done that it also provides great utilities for testing it and making sure that it actually does what you think it does it has a forking operation which is the start method it returns fiber data type which is sort of a description of a concurrently forked job fiber lets you cancel manually it's rare that you ever end up dealing with fibers and in general you kind of want to avoid doing so they're very low-level there's a bunch of high-level combinators and then additionally libraries that extend beyond that for example fs2 provides a ton of capability for doing that but some common combinators that you'll run into that are using concurrency under the hood is you can race this would be exhibit useful in the example that I mentioned of you're calling an expensive service you just want to cancel it and do something else instead this is an example just fire them both off take whichever one returns first cancel the one that didn't timeouts are extremely common provides utilities for dealing with those you can have it do a non-blocking asynchronous sleep I believe and for example to kind of draw a parallel this is kind of now I guess to JavaScript I believe like said they sync or something like that I forget the exact name not a JavaScript person but just to stress the point that it's not actually blocking a thread it's just saying hey wake me up again at this time and then I'm going to continue there but at the meantime you can CPU is free to run other work instead you'd also concurrently execute a bunch of work again using start manually you can have a list of things that you want to execute execute them all in parallel you can have a couple of IOT's you want to execute execute them in parallel get back the results tuples yes yeah so the question is am i gonna talk about where we give the fibers a thread pool to work with I am gonna cover thread pools a little later in the talk it's the short answers don't have to worry about it very much okay so kind of establishing that baseline for doing concurrence in that concurrency with IO I'm gonna contrast again the future whereas the things I just talked about niño you have concurrency via explicit Combinator functions that say I'm calling a function that says I want to do something concurrently future instead has concurrency implicitly and it depends where and when you call functions instead of which functions you call so that's how you do concurrency a future the first code snippet there is sequential execution of two unrelated jobs and the second code snippet is concurrent execution of two unrelated jobs and if you're looking hard to find the difference it's that the first one is def and the second one is valve that is the only difference between them and that determines whether you'll get concurrency or sequential execution with that code constructs and tying back to referential transparency if you are looking at this expression here and I ask you is this code running concurrently or not with futures the answer is I don't know and I can't tell without looking at the rest of the code I'm pretty sure anyone using futures for some amount of time that's probably either read about this pitfall or ran into it headfirst did you have question - yes on the previous slide you said was a deathly one was about yeah so that's over here on the slide so the first code snippet it's def job one is a future and then the second code snippet is Val job one is the future those two snippets have different semantics in terms of how they execute the results of the flatmap depends on the results of job one executing job 2 does not this ties into where I said before that a value of type future is a handle to a computation that has already started executing so when I say Val future this is immediately at this line of execution start running this thing asynchronously and the fact that that is a question that's absolutely worth asking to clarify and that kind of shows why this API in future is extremely painful when you have a large code base contrasting concurrency with IO the first snippet the flat map is exactly the same structure a flat map it's sequential execution 100% of the time the second one as an example showing some Combinator function that you can get implicitly you have to iOS and you want an i/o of a tupple you can put them together and do tuples that's also sequential execution this is low-level manual concurrent execution using fiber and start I start job one and get the fiber that describes that concurrent computation start job to wait for the results of job one wait for the result of job two and then high level there's a part up 'old variation on toppled which is do them concurrently there are other ones those aren't the only ways by the way those are just examples in a lot more code you'll probably be using something else than that but I do use this yes yep why start armed because start remains referentially transparent a and B it's more portable when you use the unsafe run methods you can end up like deadlocking yourself and you lose the referential transparency when you're doing reef actors for example there are examples of code using concurrency which will execute fine with one thread when you construct them all using combinators but if you constructed them with using unsafe run and things like that instead you'd deadlock that's a really good question saying what the start return start returns and IO of a fiber so again IO is a value that describes a computation so what do you do start you get the description of a fiber and fiber as a handle to a concurrent computation so and it's a really good question that you asked there because I think part of the thing that makes Scala hard and part of the thing that makes cats the cats effect hard is getting familiarity with understanding the intent by looking at the type signature instead of anything else to being able to understand the intent of okay so start returns an Iowa fiber that means this describes a fiber ok go look at a fiber it's a thing that lets you talk about concurrent actions that are running yeah join is waiting on the results of the concurrent process and then re-establishing so it's very similar to if you've used like low-level forking in C C++ and the answer was what does join return turns of a oh okay so we'll talk a little bit about resource management with IO it's a topic that comes up very frequently and I tend to hang out in the getter channels for Scala cats cats effect icy questions on this come up pretty often from people who have come across IO and haven't seen the resource type provided by cats effect but have a problem that is exactly dissolve it sorry but they have a problem that it is exactly designed to solve so a resource is again a value so where I owe is a description of a computation resource is a description of how to acquire release and kind of manage something with a lifecycle resource has methods that you return an i/o that basically has a used method that says give me a function that does something with the resource and returns a result and then I will handle all the lifecycle stuff for you it's very similar conceptually to a try finally set up but try finally don't compose well you can't refactor them the behavior of the things depends on whether they're inside the block or not so you can refactor but you came to abstract over them easily in case they only work really with imperative things if you put a try around an i/o it will basically always work because constructing i/o is just constructing value it's literally a case class and it will not throw an exception well this is JVM so I won't say it well not throw an exception but it really shouldn't so one of the things that lets you do is just in the type signature when I mentioned before about getting fluency about reading the intent my type signature when you see a resource in our type signature this is signal to you that this part of the code is going to be handling the life cycle of whatever a I'm talking about whereas if a function just takes the a as a parameter you know that it's not it also in contrast with try finally will I guaranteed finalization finally is mostly guaranteed but when you start getting into nesting things it gets very hard to reason about also resource let's you talk about what to do and if your action gets cancelled which is something you can't do with try finally because Scala standard library doesn't talk about cancellation so I'm gonna give a little bit of a brief tour of some more types in the library here not going to spend a ton of time on them just to know that they exist and just say oh I think I might have a problem that relates to this I can go look up the details some of the types that you'll see very frequently our context shift blocker and timer context shift is sort of the kind of cats effect pure slash referentially transparent analog to and execution context it handles that thread shifting that I talked about when I mentioned before that IO has an explicit thread shifting model before a context shift value is what provides that ability and that has into your question before about when do you pass thread pulls in to do things blocker is a data type that just wraps an execution context and is used to say I'm going to use this for thread blocking i/o work timer is a data type that allows you to get the current time in a way that works nice with cats effects and it also is what provides you the sleep call that I mentioned earlier so you can delay delay execution for some amount of time there are a couple concurrency primitives before when I described being able to write very sophisticated concurrent programs using cats effect these are these three I think are some of the ones you'll come across most often which are semaphore ref and deferred a semaphore basically is just similar to a lock you have n permits you can acquire them you can release them if it doesn't have any left trying to acquire will semantically wait until one becomes available ref as the description says there it's mutable shared memory but it's referentially transparent all the operations are in terms of i/o so you you know when something will be modifying the value and you can refactor and compose programs that talk about mutable shared concurrent memory in a way that still lets you refactor them understand what's going on and actually test it and deferred is a promise it starts empty you can complete it giving it a value you can get value out of it if you get when it's not filled it will wait until there's is a valued etiquette returned yes question on timer that's it those are functions that I often like in unit tests just sort of stuff in replace is there any kind of mechanism to to sort of do time scaling and stuff like so the question was for timer and others are there testing methods for them in short yes that's actually one of the big benefits of specifically using timer when we started in our code base we had a lot of rap you know instant dot now in IO and that works fine there's like actually nothing wrong with that it's completely fine to do that however if you use timer instead and use the library that if you use the interface that the library provides it will be more portable and it does provide a version for testing which has basically an internal clock and you can simulate sorry it is a thread pool that will simulate a non-deterministic parallel execution for you by randomly picking something to run any and any given clock tick of the things that are valid to run at that time we've used that in our code base pretty extensively to reliably and confidently test some concurrent behaviors and say I am absolutely sure this does not deadlock because I just ran a hundred thousand permutations of commands simulating two years of clock time passing it's called test context and it's from the Katz effect laws library IO app is another important type and it's basically the analog to the scala app trait or just a an object that has a main method it provides you context shift timer and some basic thread pool setup by default so you don't have to set it up yourself there is a variation that lets you overload them and return some custom thread pools which we use for instrumentation and metrics and sort of thing so Kenny the thread pool management a bit more it's another graphic stolen from the lovely documentation and especially the best practice that you want to follow and this honestly will basically apply the non Kats effect as well it's just that the future makes it really hard because it takes the implicit execution context everywhere you have a computation pool that's scaled to your CPU abilities you have an event dispatcher pool that's only responsible for basically callbacks timers and that sort of thing and then you have a pool for blocking work for example if you're calling if you are interfacing with a library that for example anything built on top of JDBC that will block threads on you you can make sure those go on a specific pool that is not going to be taking up something that your CPU could be doing specifically also global execution context that's built into the language I do want to say it does work I'm not panning it we have what we haven't used this exact one we used one based on it while we were migrating toward cats effect things did get a little better for us in the number of ways once we stopped using it the main problem is that it's not an ideal choice for anything that you're doing with cats effect it makes sense for a future because of the implicit execution context in there it's designed for mixed work where you're doing a combination of compute and blocking i/o work in the same thread pool but because of that it's not optimal for either workload with a cat's effect application you're basically always talking about the compute pool when you want to talk about blocking work the blocker and context shift data types that I mentioned previously let you talk about it explicitly and say these specific computations I want to run on this other thread pool and then go back to the computer when you're done just for some of the problems in here CPU bound or ik suffers when you use global or like any fork/join pool because it will decide to create new threads I mentioned before that future will thread shift on every operation every one of those is an opportunity for the third Pole to create a new thread it's not that hard in practice to end up with you know 20 threads on the two core machine running your compute work which is you might guess not ideal so going again over the three pool model from the slide there you go computation pool which is a fixed sized one scale to your CPUs once we switched to that instead of a fork Turing pool we immediately obviously had measurably saw performance improvements from some of our more CPU bound work anything dealing with JSON for an example easily doubled or tripled in performance just from not using a fork during pool and that was without even really Katz effect the blocking i/o pool is a cached read pool specifically it's best to do it unbounded you can do a bounded one because you know any system you are running on regardless of how much it abstracts or hides it does have physical limitations the failure mode of an unbounded threat pool I find preferable to a bounded one because a bounded one will tend to give you deadlocks whereas an unbounded one will tend to give you out of memory exceptions I prefer debugging out of memory exceptions than dead locks your event dispatcher pool we haven't actually set this up manually i/o app provides one we use it while we were in transition from more of a kind of scholar futures code base to using Katz effects we just acted by the compute pool and it was not really a problem we had already again on blocking threads I mentioned before there's a blocker type and Katz effect and you can explicitly say I have some blocking work I want it run on the separate thread pool downsides of it and again reasons to not use the global pool for this kind of setup is you CPU core that you're paying for is not doing any work while it's waiting for those thread blocking calls you have one of your cores actively involved in the important task of doing nothing and waiting for something else to happen and that reduces your bandwidth for how much concurrent work you can do one of the benefits of it as well is there's no question when you see in a type signature you know what it's for as opposed to type signatures where you say give me an execution context parameter you don't know what it wants to do with it you could give it a compute optimized execution context but it was actually doing Fred work and you've just shot your performance alternately it could be doing compute work and you gave it a cash thread pool and now nothing's executing efficiently because it's trying to run 50 threads on two cores I mentioned a little bit before and response to questions about timer so I'm not going to go into this too much there's a really good clock type that you can get from timer and these usually will do as implicit in the code not much about them just other than to say that they exist it's preferable to use them as opposed to IO wrapping things like instant now just because it opens up the door to using that test context that I mentioned and being able to write predictable and reliable tests over your complicated concurrent behavior is a real lifesaver I have written a fair amount of fairly complex kind of infrastructural glue code with Kats effect I've absolutely written deadlocks because any time you're dealing with concurrency again it can't save you from the real world of concurrency which is horrible and scary but it can't least give you the tools to deal with it and the test cases that I was able to write by using these types were able to show me exactly how much I had made mistakes and deadlocked myself and I was able to fix them pretty easily anecdotally we had a deadlock a few weeks ago from me using something incorrectly once we noticed that it was there it was about a two or three line fix and then adding a regression test was maybe like ten lines of code for the regression test and reliably detecting and preventing that deadlock from happening again all right I mention dial app this is basically the replacement for your object with a main method it's not necessary to use it there's a pretty hefty code sample because I just wanted to show sometimes when you're coming over an overview of things like this I've talked about I don't know somewhere on the order of like ten different data types and that's a whole lot to take in that's I style an example of how you kind of put things together and structure them so talking through this in a minute you have a def run method instead of a def main it returns an i/o instead of returning unit because basically it allows you to be it allows you to maintain your referential transparency all the way to the edge of the application the implementation of i/o app goes and does the parts that can't be refactored which is fine because you're not going to refactor it so this is an example just to show if you have an i/o an action you'd like to perform you can lift it into a resource context and say when you acquire this resource do the action when you cancel it don't do anything so an example of for example a database type this is a resource where you probably want to have a connection pool you don't want to just make a new connection every time you run a query that's something that has a lifetime it's something that you eventually want to close and clean up even though in most applications you'll have it for the whole app lifecycle still just having a clean shutdown is nice and then also for when you do end up using resource bound of things internally in the code knowing that you've handled the resource cleanup correctly on preventing resource leaks is a huge help so and this all it was code all by the way compiles none of this is fake code I'm gonna sharing the slides afterward the slides are all compiled using the top library so everything actually does work here this is a typical way on the structuring things if you do end up using IOM is you have your app logic you take your dependencies in I mentioned the difference of like reading the intent from using a type signature that says I take a resource parameter versus I take a value of something in this case you pass in the values you know you do whatever your applications going to do and then you can run it this is a huge method I mentioned it says use my resource and then just say in this case I'm kind of doing the work internally there are all ways to structure it ok so everything I've mentioned before so far is uh pretty nice in my opinion I think most of us are probably not creating new projects and new code bases very frequently so it's absolutely a real-world problem of all of the sounds great I'd love to use it except that it's not already written that way in my code base because we didn't know about it we didn't care before we didn't choose to use it that way it was made under different decisions I can say from experience it's absolutely possible to transition a large mature code base from kind of the more Wild West and ad hoc style of using futures and just starting to be really really careful whenever you change things to make sure you didn't break something and you can refactor it incrementally towards this we've done this so I'm gonna talk about Interop a little bit because we might not want to use feature in our code base but there's a whole gigantic ecosystem of high quality libraries out there that were written either before this before cats effects was or even if they weren't they don't care to integrate with it you can integrate in both directions you can have something that returns the future and wrap it into an i/o so this is an example I have an existing logic function in my code base that is already there I don't want to touch it I know it works if I refactor it maybe I'll go fix it but I'm gonna leave it future for now because it works I don't want to touch it I am writing new functionality that wants to reuse the old one I can still do that in IO so where I have there I owe from future and then it takes an i/o of future as a parameter so existing logic returns a future and I'm wrapping that that is again because reiterating a value of type future represents something that has already started running constructing a future as a side effect and the type signature of from future is designed to remind you of that and kind of make it more likely that you will write the behavior that you intend yes oh thank you just running there yes that's correct so Iowa ply is by name parameter and it's what you use to wrap running some bit of side effects for code yeah the question was there that the future doesn't begin running and it doesn't because it's a by name parameter you don't strictly speaking need it when you have when if I say I Oh existing logic the type of that expression is IO of future of int and then from future just plucks the result out when it eventually becomes available conversely you can go the other direction I have a trait that demands I implement a method returning future I'd like to have more confidence in my implementation and have it be safe to refactoring there's an unsafe to future method it takes an IO of a and returns a future of a and it's worth stressing all of the dot unsafe whatever methods are places where you lose that referential transparency and you transfer back to imperative side-effects so IO lets you describe actions it lets you build combinators that build up complicated actions from small component building blocks in a way that is actually comprehensible but it also lets you actually get out to the edges for the cases where you know I can't afford to go refactor the entirety of my codebase in one commit so I'm going to do this incrementally our code base more stabilizing now but over the past year definitely we've seen a lot of these in here sorry I missed a question oh yeah my question is can you go to previous time as the existing logical data to cover that you have from feature to our own future and trial yes well that really provides function just to couple straight from page well so the question was why wrap the future in i/o instead of converting directly from future tayo why force you to use i/o of future of X so the answer to that is it was a design decision not really an architectural concern it was there's actually a ticket on the bug tracker you can go and read the discussion of where the rationale was decided but basically it was done that way to make it more likely that people would write the correct code the reason why taking a future as a parameter is dangerous is because having a vowel of future is dangerous when you write when you write Combinator's that work in future every single time every single time you want to take a future as a parameter you need to do it as a by name parameter so it's lazily evaluated if you forget one your program will still compile it will just mean something different than what you thought it did so it was a design choice specifically to kind of force awareness of that on the person calling it and make it more likely that they'll do the right thing so if you have some imperative code that's just you know functions returning unit some side-effects you want to call so I have an example there of first invoke one step then invoke another step translating those is wrapping them in IO apply and then flat mapping them so in a way that's fairly similar to how you would translate a unit function to a function return in future of unit you again you just lift them into IO using the constructor and flatmap so this is brief summary some of the Constructors for future and Combinator's just have direct analogs future successful is I hope your future failed is IO raise error implicit concurrency you need to that's one of the ones where a search and replace will not necessarily give you the result you want if you know you're not using it search and replace away but if you are using it then you will want to make sure you use the correct like explicit concurrency Combinator's another case where you have to talk about the last full point so imperative initialize something and then like a closed method that returns unit that's a case where you translate using resource but the last one side effects in map function are incredibly common in my experience in the codebase with futures example being I have a future returning me some database results I'm going to map on it transform it and then inside the map I'm also going to log something those are places where it is very important that you do translate those correctly and those are ones where the compiler can only help you a little bit you basically instead of doing these side effects it looks similar to the imperative example if you imagine that imperative sample is inside of a map function you translate the map call to a flat map call in the same way so I don't have a code sample for that but that is crucial because it is another case where if you are taking a sample of code with futures in translating at i/o you do need to make sure that that one you do correctly if you want to preserve the intent exactly best practices even though the future are not to do that so much hopefully you're not but if you are you can translate it you just have to be a little more careful once you've translated it you'll know that it works the way you wanted to some general tips that we have kind of compiled from our experience of using it some of the combinators that end up being useful come from Katz itself and not Katz effect the old one I showed before for sequentially executing to iOS comes from cats itself it works for IO using the type glass mechanism I mentioned before tup old works for anything that has an applicative instance if anyone's familiar with that basically things you can map and a little bit more on without getting into too much detail some functions are under cat's effect implicit slides in general concurrency with IO requires that you have an implicit context shift instance because again we have explicit kind of thread shifting so when you're talking about concurrency you need the context shift implicit if you want to construct one I awkward IO app gives you one for free you can otherwise construct it from an execution context manually similar logic for timer if you want to call the timing methods or some Combinator's that involve time for example timeout they'll ask for an implicit timer again reiterating the fork/join comment it won't break most likely I will not promise anything on the JVM won't break but you will be leaving performance on the table and spending more money for your compute power than you need to and if you do end up in a position where you need to use the unsafe methods on them make sure you really understand what's going on there are some cases where you'll want to but in general you don't often need to certainly for anything that's not interfacing usually usually the answer is if you're interfacing with a complicated Java API if you're not doing that most often you never need these some common errors I mentioned before doing that side-effects inside of a map function some common errors that you can do where you forget to wrap them correctly that's kind of a gotcha another one is the implicit future concurrency thing to be careful for you absolutely will want to also make sure you have the warned value discard compiler option on because that will save you from yourself so many times even like I've been using the library for well over a year I absolutely still get you know some mistakes from that occasionally I'm instead of simple for that but in a nutshell if you type annotate something as returning unit when the expression does not in fact return unit the compiler will helpfully assist you by discarding the results of the thing you had and then returning a unit for you war in value discard prevents that from happening with i/o it's pretty severe because instead of returning the description of the action you want to perform you will construct a description of the action you ought to perform discard it and then return nothing in terms of application structure some things that make it easier as you go to kind of convert I'm running a little low on time so I go through this very quickly using semantic interfaces so basically describing what something means in business terms an example of this might be I have a user repository it has a get user method not I have a user table with a method that is do user query just changing the wording making the types reflect the intent will help you make sure you've translated correctly because when you read your code reading the code out loud sounds like the description of the behavior you want you can interrupt using the things I mentioned and a really important point is you absolutely again can incrementally convert you do not need to do a whole code base if you want to start using Kats effects you don't even need to do your whole code base ever if you have a working code base that's not using Kats effect and it just works you don't end up touching a segment of it you don't need to go touch it you can just interface with it stop whenever you've reached the point that you think is a good enough value for your effort and you know you can choose to write new code in i/o and then there's your face where you need to it's pretty easy so basically summarizing again kind of the point of all this I've talked about a whole lot of different data types interfaces code behaviors again why care about library number five thousands of things you need to learn or people say that you need to learn because it lets you write code that doesn't break when you change it you can write reusable code and combinators for example there's library call FS - that lets you do lazy streaming built on top of i/o you can express some extremely sophisticated concurrency and application structures with it in a really sick synced way have an example today that was about four lines of code for two paragraphs of description of the behavior of hands there's also a large library sorry a large and growing ecosystem of really best-in-class libraries built on top of it things like HTTP clients servers database interactions and in our experience if there isn't a library for it the interface is nice smooth caps effect using the things I mentioned about interfacing the futures you can take it to take advantage of existing Java and Scala future based libraries and continue using those you don't lose access to those by using this you can just wrap them build on it I have some links and resources here I'm going to be sharing the slides out after the fact so you can click these links if you care to I have a few minutes for questions here at the end so the question is about performance penalty from nested data structures and flatmap the answer is IO benchmarks better than future and additionally if I take four argument and just take as granted that it does allocate more objects and is more expensive on GC pressure and things like that unless the majority of your clock time is spent doing flat maps as opposed to calling a network or doing database work or computing values it doesn't even matter like you can take a very large performance penalty before it actually matters to the performance of the behaviors that you care about but the answer is no it doesn't have a big performance penalty and often it will perform better and even if it did it usually won't matter for your actual workload in practice outside of micro benchmarks any other questions there are other libraries so other libraries similar to cats effect IO for example Scalzi z io all of the ones that are in sort of a functional programming area including the IO will be built on similar concepts of build a description of the thing you want and then execute it unsafely later how they implement that or specific design trade-offs that they make internally may vary I can't get into too much details I don't have enough time talking about differences between the IO but I mentioned earlier that cats effect provides type classes that describe the behaviors io supports using those type classes you can write generic code that actually can be used by either the IO or Kats affect IO or monix tasks and most of the ecosystem I mentioned the libraries built around it are using those interfaces so you if you decide that in your own internal library you'd rather use Scalzi IO you still can use libraries built on top of Kats effect and get all the benefit from them and it does offer a lot of similar benefits in terms of application maintenance things like that yes in the documentation really pushed the idea of creating threads of differing different so you say the documentation they mentioned creating different thread pools yes why is it they don't just supply so why don't they supply them out of the box I Oh app does supply them out of the box for cats effect IO as for why Scala standard library doesn't the API for the future type was designed around using a single implicit thread pool and the global data type is designed for mixed workloads it's possible to do a multi pool setup with Scala futures it's very painful because you have to explicitly pass the implicit arguments all over and if you forget one your code compiles but does something other than what you meant so that's why Scala doesn't probably know out of the box --cat spec does provide them out of the box or alternately you can manage all the construction yourself which is what you get from out of the box if you can use i/o app if you already have a pool set up that is using separate pools you can use those same pool objects for Cap's effect if you have a one pool setup in your application you can do that as well in that case I would use a fork during pool for it we have done that using one pool for the entire application in the early stages we transitioned to using a two pool setup with the context shift and then a blocking pool just by constructing a blocking pool separately at the top of the app and passing it through the call stack dependency chain so the answer sort of you can do what you want yes yeah so the question is it's using implicit how is it using multiple they have different data types the context shift you have one context shift value that you pass implicitly and that's your handle for thread shifting on your compute cool it also has the interface that says do a portion of work on another explicitly passed thread pool and then shift back to the context shift pool after I can go into more detail some code samples if you have questions after the fact but in general they have separate data types a little bit it is in some previous slides so yes you're right I misspoke slightly so the question is they're not all quite provided context shift is prided so IO app provides you an existing implicit value of context shift backed by a fixed pool and implicit existing value of timer backed by a 2 thread scheduled execution service and it provides to a method you can use to get a resource of a blocker so it doesn't it doesn't initialize the blocking pool for you but it does have a method in the library where it's one line of code to construct one any other questions mmm thank you all for coming thank you for this you [Music] [Applause]
Info
Channel: thoughtbot
Views: 8,207
Rating: 4.982379 out of 5
Keywords: scala, cats-effect, boston meetup, functional programming
Id: 83pXEdCpY4A
Channel Id: undefined
Length: 54min 53sec (3293 seconds)
Published: Fri Oct 25 2019
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.