but we can also extend to different types of observable data and we can extend it to do all kinds of other things. State management, it's a good intuition for how DOM event listeners operate. So if you wanna add a button to a page and have events occur it's also sort of how Ajax requests and networking requests are gonna work. So here we have another example. We've got these two different event types and have sort of intentionally switched their order. So now we can sort of fire those off in different orders based on labeled events. So our, what used to be just one queue is now a map of queues to event types. And then we can have another sort of version of it where instead we hold some data and every time we change the data we make that data available to some functions for state management, okay? So this is observable data. You can see we also optionally give the ability to reference the previous value using variadic functions here. So because we don't have a fixed number of arguments to our function type in JavaScript we're able to pull these kinds of tricks. These will get ironed out later on but for now there are some conveniences that we can pull from this. So let's go ahead and run that example. And you can see as soon as we change this initial value of hello to Bob we get the first listener going off and logging out the original value, hello. And then a second listener fires which is going to give you the new value. So these could be supplying them out to other callbacks. They could be running network calls themselves and you can get this notion of sort of a chain of functions that call each other, building up a call stack, but potentially with asynchronous steps where you're not adding things to the call stack immediately. So this brings us to sort of a fundamental problem. JavaScript is single threaded usually. Worker threads don't count, they're a bit of a newer addition but what I'm talking about is the fact that JavaScript runs with an event loop and when an event is triggered there's a call to an event listener that's placed in a queue kind of waiting for the event to resolve and once the call stack is cleared of synchronous behaviors the JavaScript runtime is gonna start to execute listener calls from the queue. So it's sort of very similar to this but the reality is that this emitter is obscured. It's implemented off in the browser maybe in C++, it's in the node core, it's part of the web APIs that you think of as sort of the JavaScript base library, all right. So there's a really great talk on this that I can never do justice to is called what the heck is the event loop anyway from Phillip Roberts and it essentially goes through just this with a lot more demonstration. So if you're interested in knowing a lot more about how the event loop works I really recommend that as a great starting place. Now there's also some modern abstractions that have been added on to help us prevent what's called call back hell, this idea like maybe we have so many listeners and additionally the old syntax for functions was very big and then relatively clunky relative to these arrow functions that we're using. So we came up with promises they're sort of a wrapper for unfinished computations they can't be canceled once you start them off so here we're using fetch which is an API for doing network calls so we're calling off to this AI age predictor based on your name. So there's a difference between this example and the previous examples here we could trace through all the actions and never lose track of sort of where the JavaScript interpreter might be operating and how it might evaluate out but here we get to a point where we call fetch and we'll very quickly get confused because the fetch computation doesn't wait for us to call fire on any of these it kicks off immediately through a browser API. So this returns a promise here which we then called dot then on but the computation is sort of running in the background in another thread that we don't directly control and when that thread completes and receives its response this is going to be kicked off and then likewise this is going to be kicked off. So we have this sort of chain of events and if you squint this is gonna look a lot like a monad and you don't have to squint that hard this actually kicked off as this API was being discussed there was a hot debate about adding features like monad to JavaScript however there is some convenience here because you don't really have to think about whether you're calling bind or map on this you can return promises or not promises and that was determined to be the more sort of JavaScript friendly API. Now we also get some pleasantness that looks a little bit like do notation so we get a sync of weight and if you sort of equals a weight if you squint that into an arrow you might imagine this looks quite a bit like Haskell's do notation and you wouldn't be wrong. The kicker because it is just like do notation is async actually denotes that your return value gets wrapped in a promise so then you can go ahead and catch for errors at the end just like you might catch some monad say that has monad catch or monad throw. So we have these sort of functional like types of expressions where we can sort of set up chains of computation and also set up binding do notation but like I said it's not cancelable we haven't officially created monads or functors in JavaScript and this is where the next great example comes from because there's this great library called future and future allows you to wrap up promises with a different type which does implement monads and functors and by functors and alternative and all kinds of other abstractions that you might be familiar with. So if you are using JavaScript and you cannot use pure script this is a great system to use. It also has certain API helpers like enforcing that you supply an error handler before you get to unwrap and get your actual value out. You can also set up cancellation as well so there's a lot of great things to get out of the Flutures library. There's also a way to do async await like syntax using iterators. There's a lot of great options for you with Fluture but the main problem that you might have is that Flutures errors, they're great type errors but they are at runtime which brings us to pure script. So with pure script we are gonna be talking about three different techniques the effect monad promises and their relationship to af as well as fibers. We're gonna be taking a look at some examples with fetch and afjax. So let's just move over to the examples. I've created a couple of simple wrappers here. I've created two wrappers. One is it uses the fetch API just so that we're comparing apples to apples but this fetch API it's going to essentially just take a callback and call our callback. So there's not a whole lot going on here. It allows us to just sort of have a callback like interface and pure script. You'll also notice there's this just nullery function so we have currying here. We get the URL, we get a callback and then this nullery function is an effect. So when you talk about an effect in pure script we're just talking about a lazy value, a function that is deferred, okay. Now we've also got this one for promises. So this one is gonna return an effect with a promise inside. And we can change promises to afts and afts to promises. So we'll talk a little bit about that. Pure script has two different types of effects that used to have many more but we've mostly gotten rid of them. So there's like we can return just a promise and say, oh, we've got the promise but there's not really great tools for unwrapping a promise. You'll mostly want to use aft unless you're returning it for a JavaScript user maybe in a library. So what we've actually got here, we've got first just a callback based API where we're using our effect call so we just supply a callback there. Let's go ahead and run that and we get some compiler output and then we actually get the effect itself running and when we get a result we'll get the callback fired and it's stalling out for me. So I'll just move ahead. So the next thing that we can do is we can set up an aft. So here we're getting the result as an aft and what we've done, we've got this fetch function that we've implemented in PureScript just using a promise helper to unwrap the promise and supply it as an aft. Aft gives us all of the powers of functors, moanads, bifunctors, oh my, and it's cancelable. All of the great features of Fluture, however, we're in a PureScript context so we can kind of reason about our types at compile time rather than at runtime. Okay, let's go ahead and run that. Hey, and there's our output. So again, we're just outputting text as things land. There's not a whole lot else going on. We're just calling log. All right, so now we've got these sort of basic examples. What I wanted to do next was show a PureScript native example. So let me just come down here. This is aft jacks, uncommon the code that runs it. So aft jacks is the Ajax library for PureScript. So we're returning an aft. We can do a native request. This has sort of full type safe setup for your request. So here we can do a request with a get method, a given response format, so you might be anticipating a string response or a JSON response or some other response. You can actually set it up to do some of the parsing for you if you like. In this case, I'm just leaving it as a string, so we're not getting too much into the parsing world. But it's the same request essentially, so we can run this as well. Okay, so that's sort of the native way to approach this. There is also a shortened helper just called get, which allows you to use much less of this. But that is the basic idea of an aft jacks usage. So one thing that you'll note about aft jacks, and it's something that is common across quite a few PureScript libraries, is it's distributed across multiple modules for typical usage. So I'd say this is sort of a design principle across PureScript, is it's very, very modularized. So just be prepared for that, that you'll be jumping around all of the different modules in that library. Even compared to Haskell, it's quite modular. All right, so we talked about fetch, we talked about aft jacks. I do wanna spend some time with fibers. So fibers are a really interesting feature. Fibers essentially allow you to take afts and control these forked computations. So you can start to reason about aft as a unit of asynchronous computation, but a fiber is really a chain of asynchronous computations that you could then call various methods on. So you get some reason about it. You can see like fork aft, I think is probably the most descriptive, intuitive function that you might see coming from Haskell because it works exactly like fork IO.  You can make a fiber invincible or you can make an aft invincible. So there's lots of different capabilities that you can set up for fibers and for working with complex asynchronous computations. All right, so just to summarize PeerScript's approach to asynchronous computations. You get lots of different type safe wrappers to define the outputs of unfinished computations and you also get control over synchronicity and asynchronous using effect and aft. So effect is for all synchronous effects which is why you have to supply callbacks everywhere and aft is for asynchronous effects. So you don't have to think about supplying callbacks. You can just operate on your data without too much concern about what is synchronous or asynchronous, although it is clear in your notation which one is which because we're in due notation. You also get type level knowledge of blocking operations which is sort of, I guess, the collaried to the above. You get abstractions for chains of computations which follows familiar type class interfaces laws and you get the flexibility of flutures but at compile time and you get due notation. So let's go take a look at a production example. I wanna show you a library that I've been working on.  We're using the WS library in JavaScript. We've pulled in a bunch of foreign imports which are just wrappers around various library calls and what we can set up through a whole bunch of layers around the specific web socket interface that we're using is this query M monad which allows us to just have a reader T over some config and an app and this essentially allows us to set up some guarantees through adding and removing listeners sort of all over the place as queries go out and come back to abstract over the fact that web sockets don't give you good request response guarantees and so this sort of builds up a dynamic dispatch system. So you can see it, we do quite a bit here to build up a type safe dynamic dispatch system. We build up listener sets and methods for adding and removing to a listener set but through this system we actually have a very nice API for working with web sockets where you don't have to concern yourself with like a mother listener that listens for everything that this web socket could possibly spit out. We have it but we have it sort of buried in some async helpers and we can abstract over it. So this is a really, really strong use of the af mode ad I think in pure script. I encourage you to take a close look at this. This is the Cardano browser TX library on the Plutonomicon. This is one that I've worked on with the team at MLabs. Well, that's it. Thank you for watching. There should be a Q&A session right after this if I was able to be there in person. Just a reminder, MLabs is hiring Haskell, PureScript and Rust developers at all levels. We're a premier blockchain consultancy working on Cardano, Solana and Polkadot. Please take a look at MLabs.city for more details and I will see you at the Q&A. Hey everyone. So yeah, if you have any questions for Ben, pull it in the chat and yeah, then see you at the event. Hey there, I also have all my examples up. I know I had to go through that fairly quickly in the prerecord so if you have questions, if I went too fast feel free to let me know and I'm sure I can share my screen here as well. I'll hold a minute for questions just because I think if I share my screen it might, oh, I think it might be okay. Which PureScript async handling approach is mostly used in popular libraries in production and am I using that Xmonad? I am using Xmonad. Which PureScript handling approach, async handling approach is most popular? I would say AF. I would say if you're going to get, you're gonna do with any library that's doing network requests and quite a few others. You'll see AF first and foremost, I would say. How can code using AF coexist with something that uses promises or some other API? Yeah, let me share my screen quick because I just wanna show you to a library. So once my screen settles down from Zoom being crazy with it, there is this great control.promise module if you're on pursuit that lets you freely convert to and from promises and AF. So to and from AF are all here. I'm just gonna flip back over to the chat as well. Yeah, so that way you can sort of pass things back and forth from promises to AF, AF to promises. So especially if you're dealing with a JavaScript library returning promises, super, super common these days, you can flip back over to an AF for the easily. Does AF support canceling a promise? If so, how? I have not dug into the specific way it cancels a promise. You certainly get a cancel method for any AF, but let's take a look over here. I don't actually see cancelers that get spit out in this library. So it may be doing something just a little bit different than what you'll see over in the AF library. So the AF library has a number of constructors to sort of build up an AF, but you'll see they always return like an effect with a canceler. I actually don't get that here. So it might be that you can't generically just get a canceler out. It may be that it modifies your promise callback in some way, but you could always take a look at the source, I suppose. Gonna take me a while to parse that. I'm gonna go with right now, they're not easily cancelable right out of the box, but I encourage you to take a look at the docs on that one. Is there a significant performance difference between using different handling approaches? I don't notice a significant performance difference, but certainly there is going to be some penalties. Some are gonna be better than others. I don't think one is severely worse than the other, but your mileage may vary. There you go. Robert just confirms that promises can't be canceled because they come from JS land. Thank you, Robert. Does it make sense to write custom FFIs around HTTP calls like fetch? I wouldn't really do that in production. I would just reach for after acts. If you really don't like the way after acts is doing something, you do have the option like I just was. I don't think there's anything super wrong with it, but in general, I do prefer working with after acts. At least working from PureScript. Working in JavaScript, I really prefer to work with FUTURES. And again, like there's a to FUTURE from FUTURE, sort of a set of methods in the FUTURES library to go from a promise to a future. How does it compare with async await in JavaScript? I'm not quite sure what you mean by it on a pump. Okay, so how does AF compare with async await in JavaScript? I would say they're really comparable in terms of the sort of making asynchronous programming appear synchronous. I see there's some back and forth also about promises and canceling. We'll get to that. But I would say AF is really comparable to async await, but with more type safety. Certainly because we're in sort of a Haskell-like language, we're in PureScript, it's going to force us to make sure that our binds and our lets are sort of separated out. Whereas binds and map calls and lets with a promise or async await, if you put an await call in a place where you don't need it, I don't think anything bad happens, at least not last I checked. So it looks like there's this abort controllers, which I've actually not heard of, but not every promise supports abort controllers. Is AFJax using Fetch or the old Ajax APIs? AFJax does use older Ajax APIs. And in fact, it uses the XHR2 library, I believe in Node and the XHR HTTP request from the browser. So it is an extremely old Ajax API, which gives you compatibility everywhere. And it means you're using an API that is very highly optimized because it's been around forever. Thank you very much, everyone. I suppose that's all questions. Take care, have a great day. Thanks for watching. So everyone, I do wanna give a shout out to Ben. Ben has been awesome. And the reason is it's actually like three in the morning at like four in the morning in Toronto, he kind of woke up early to get the stock. So thanks Ben. I'm pretty sure everyone here loves that. And yeah, it was great to have you on. So for that final talk of the day, we have Nathan. Nathan too is a member of the PureScript 4 team and he works for the Rista Networks on a different note. He'll not be talking about more heads. If we're talking about managing the stack when we call the inevitable recursive functions because when functional programming, everything is recursive. Nathan was unable to make the conference, but he has fortunately made a recording and has sent it to us. So on we go. Hey everyone, I hope you're having a great conference. My name is Nathan Fabian and welcome to teaming the stack in PureScript. Let's face it, PureScript kind of has a call stack problem. And this is actually pretty common in functional languages. And what it comes down to is that PureScript lacks loops. Most languages have some sort of looping construct, but PureScript only uses functional recursion. In most run times, recursive calls are really any function calls take up additional stack space and recursive calls aren't special in that regard, which means consequently, if we loop too many times with recursion, then we'll get a stack overflow. A common way around this is something called tail call optimization. Tail call optimization is kind of a misnomer, I feel like, because is it really an optimization if it's necessary to actually write correct programs in a functional language? But the gist of tail call optimization is that it turns first order self-recursion in tail position to a JavaScript leaf. I just say JavaScript because that's our most common backend. And first order just means that we only call the function recursively in a non-higher order context. So it's not passed to a function. It's not captured under a lambda that someone else might invoke dynamically or anything like that. Self-recursion means that we only call a single function ourselves recursively. And it happens as the last thing to evaluate in the function call and evaluating the functions. The last thing to do is call ourselves recursively. TCO is specifically a static transformation. It only looks at local information for a particular binding, a recursive binding. It doesn't involve any sort of global control flow analysis or anything complicated. It's really straightforward to implement. And just to bring it up, the general form of this is called tail call elimination. And other runtimes that are not JavaScript like Erlang or Scheme will support dynamic tail call elimination, which means that any dynamic call in tail position will be optimized to not take up additional stack space. And actually like Safari supports this for JavaScript, but no other JavaScript runtimes do, unfortunately. So to get kind of an idea of what we're doing, I just wanna look at a very, very basic quick example. This is a common data type, list data type. This is exactly what is in the core libraries. And there's a function. Let's look at a function sum. And all this does is it pulls out integers and adds them together. And it has a one recursive call to itself, but it's not in tail position. It calls itself, but then after that call is done, it has to take the result and add X to it. So this would not be optimized by the compiler. However, we can do a simple transformation. This has what's called a worker wrapper transformation, which has a worker go that is recursive. And it has a wrapper that invokes the worker. And in this worker, we take an additional argument, which is an accumulator. And then we kind of keep that state and we only call ourselves in tail position. So whereas before we invoked the recursive function, then did something with the result in this worker wrapper transformation, we take the result, we go ahead and add it to our running accumulator, and then we recurse after that. This gives us a tail position. And since this is also first order, this will get compiled and optimized into a type JavaScript loop. It doesn't have to eat additional stack space or allocate anything. So one thing that's actually interesting is that any first order non-tail position recursion can be mechanically transformed into tail recursion. So into this kind of form that we're looking for. As long as it's first order, it can be, any of that recursion can be rewritten into tail recursion. And you might say, okay, if it's mechanical, can the compiler do that? And the answer is lots of compilers do. PureScript does not, but this is actually the steps I'm gonna go take you through are transformations that many, many compilers do automatically for you.  And so depending on the transformation that it has to do, it might require doing a lot of additional allocation on the heap, which may make your algorithm a little bit slower. So if you need performance and you know you have bounded input that isn't particularly large, you can go for a recursive implementation that is gonna be a lot faster. But if you have unbounded input, then it might be a good idea to go ahead and just eat the heap cost, the allocation cost. And so it might use more space and it might be a little bit slower for access because it has to do more dereferencing, but it will be stacked safe. So our example here that we're gonna try to transform is a little bit more complicated. We're gonna look at a data type that's really similar to our list data type and it's a binary tree. And this isn't really that much different. It's just we have an additional recursive node. And so what makes this relevant is that our recursion actually forks. We have to go in two different, go down two separate branches, or curse down two separate branches. And if this were a binary tree, this wouldn't be that big of a deal because it would be logarithmic or if it were a balanced binary tree rather, it would be logarithmic in depth. And so you wouldn't really have to worry about stack in that case, but we're gonna assume that this is non-balanced. And so we have no idea, this may be totally left associated or totally right associated. So we have to be kind of careful. So we're gonna look at an implementation of map for this data type. And it's pretty straightforward. This is actually what a compiler, the compiler will derive this implementation for you. Unfortunately, this is not stacked safe because we have two separate recursive calls and neither of them are in tail position. So what do we need to do to turn this into a tail recursive implementation that the compiler will optimize into a loop? First, let's look at where our recursion happens. Our next step will be to move all of our function arguments into bindings. And this is really not super necessary, but it is, it makes it very clear what our kind of, what our evaluation order is and really tells us that this is obviously not in tail position because we have to call these functions, get the result and do something with it afterwards. So this is definitely not in tail position. And it makes it a little clearer what's happening. Next, this is a little, this is a big jump, but it's not too complicated. I'm gonna walk through it. We're gonna convert this into what's called continuation passing style, which is just callbacks. It makes our evaluation order kind of specific. So it's very similar to the worker wrapper transformation that we did before with our sum implementation. We have a wrapper here. We have a worker go here and we have this extra argument, C-O-N-T, cont or continuation. And really all this is doing is taking those bindings that we pulled out. So if we look at this one, we have the binding on the left-hand side here. And then we have the call on the right-hand side. It just kind of flips that around the other way around. So now our call is on the right-hand side and our binding just kind of goes over to the, or our call is on the left-hand side and our binding is on the right-hand side. And because it's a Lambda and we format it, this will end up kind of looking like stair stepping, but it's really not stair stepping. It's just another way to kind of like look at the flow of evaluation. And anytime then we looked at our old implementation where we're doing nothing but returning a value, we replace that with a call to our continuation, cont. And so one way to actually look at this is that this is the same sort of transformation as far as like an accumulator. This continuation is just an accumulator and we're literally accumulating a program. We're literally accumulating code to run. So we're traversing this. We're doing this algorithm and our accumulator is just a new program to run. Really, really interesting, really elegant. Our next step then is to lift these callbacks into explicit name bindings. You'll see this a lot in this kind of stuff. It's like put it in an explicit binding. This makes everything clear around what's actually happening with this. What are all the variables involved? So you can see we make our closures explicit. We have to make sure we capture all of them and pass them to the continuation. This makes it very obvious what our dependencies are. Our next step then is to take these closures and turn that into a data type. So here we have our identity continuation. This is what gets it started with. We have our left-hand side continuation. It goes down the left-hand side of the tree. We have our right-hand side continuation which goes down the right-hand side of the tree. So we're just gonna turn that into a sum type. We have each one, all the values that they capture, they just get put into the data type. Again, we have our cont identity, which is kind of a nil value that tells us it's done. So then what we're gonna do is take all of those closure bindings and turn it into a single function eval that cases on this data type and executes the code that was in those closure bodies. So this is, if we go back here, we see cont lhs. We have this let binding, this go cont rhs and that finishes it. And it's doing the same thing here. We have cont lhs, the let and the new go call and then cont rhs. Now, instead of invoking our continuation explicitly where it was a function, we are instead turning it into a call to eval. So we have eval and this is a typo. This is next here, but it should be cont, but you'll see up here eval cont. So instead of calling cont tip, we just call eval with the cont and our return value. So you'll notice here, one thing that's interesting is that eval is always in tail position. Eval is in tail position here. Eval is in tail position here. Go is in tail position here and tail position here. So our calls are all in tail position. So we have a first order algorithm where everything is in tail position. The only problem now is that it's not self recursive. We have a mutually recursive set of bindings. So we're gonna do a similar transformation that we did before with our continuations is that we're gonna turn our mutually recursive go eval calls into a data type as well, kind of like how we turn our continuations into a data type. So we have our map go and the arguments. So we have the function that we're mapping, the binary tree and our accumulator. And then we have our eval function with our return value and the accumulator. And we're gonna turn these into a case. So instead of having our separate bindings, again, we move this into data types and a case. And so if we look at this now, all of our calls to go are now in tail position. We only have a single self recursive loop. And so this will turn into a nice, stack safe implementation of this algorithm. And this is all very mechanical. You can apply these transformations to essentially any recursive algorithm that satisfies that criteria of being first order. So if that's all you want out of this talk, that's fine. You can leave it at that. That's all you need to know to write stack safe. Basic pure script algorithms. I'd like to keep going. There's a few things I kind of want to explore with this, which I think are pretty interesting. One is just that instead of using tail recursion, like references to go, like our worker, you can use tailwreck, the tailwreck function. And this is in the standard library and kind of the same idea before. Instead of recalling tailwreck and tailwreck makes our kind of our worker here correct by construction. So we can't use go accidentally wrong. And so it'll always be stack safe because we have to return an explicit data type at the end of each iteration. So this makes it a little bit easier to keep things straightforward. So that's just, you don't have to do this. I almost never use tailwreck, but it is nice to get that sort of guarantee that it might be a little bit slower. It's just the issue. But I'm gonna look at something here called a greatest fix point. And if we take our worker and just put that in its own function and owns, like we treat it like a state machine, it's taking an input and like a state and then transitioning it to a new state. So we've got a map call. And it's just every time you invoke the transition function, it transitions it to the next state in a map call. All we've essentially done is removed these like loop and done, we've just taken the work and removed like the extra wrappers. We'll see here, those are just missing. We just map each state to a new state. We can then take, essentially take that loop that we had before or the wrapper and define this eval function which will evaluate a map call and turn it into B. So we can call our stepper and then we just case on our identity, our identity continuation here. If we know that there's nothing left to evaluate in the continuation, then we can just go ahead and return the result. I think this was actually a mistake, these arguments should be put. But it's the same idea. And if it's not our identity accumulator, then we just continue stepping. So this corresponds to what's called small step semantics and programming languages. And this is great because we have a very clear evaluation state that we can start and stop whenever we want. This is great for actually writing things like debuggers. We've essentially defined our own little language just for this map call. And if we wanted to, we could use our stepper function here and step through every single evaluation step of this function. Be great for if you wrote a little language, you could use this approach to make, essentially make your own little debugger for it. So it's super useful. Now I wanna look at, but you know, we had to match on our map eval.cont identity specifically. That's the only thing here. So I wanna look at what this step data type is, this done and loop. Step here is, it's step AB, we've got loop A or done B. And this is essentially just, this is just either with more specific names to make it clear what's happening. Originally, Tailwreck actually used just either, but it was very easy to get them mixed up, like which one is it, is it left or right? And so it was changed to use explicit names, which makes it a lot easier to use. But in essence, it's really just either. So we're gonna transform it to either, we're gonna not use loop or the step data type, we're gonna use either. And we're actually gonna kind of like flip the meanings around here. So done means left, which kind of makes sense. You know, if you think of like either as sort of an error condition, or like a way to like halt or like short circuit the computation, then like left is kind of like that's done, like we're done with it now. Like there's nothing left to, there's nothing left to compute. And so right becomes our loop constructor. We're just flipping the meaning. And then we're gonna take, we're gonna use the PureScript Fixed Points library or fixed points or other fixed point functors. So specifically, the greatest fixed point is the new data type. And we're gonna use our either functor. So either B, so it returns a B, and we can write this fixed function that operates just like Tailwreck. And it will evaluate any fixed point over either B and return a B here. And so it's the same idea, left return, it'll just return that, right, keep going. So it's essentially what we were doing in our eval function. And you can then turn that into, we can take our step function and we can compose it with a condition here, a termination condition essentially. That's kind of what the loop are done. It's just a way to communicate that we need to terminate. And so we can compose it with a side termination condition and get the same result down here of map call A, map call AB to be evaluated. So now we've looked at kind of what the greatest fixed point is. Let's look at, if we try to swap this out with a least fixed point. The greatest fixed point is this new either B and that corresponds to an existentially qualified A and a tuple that has our step function and the state. So the existential excuse me, quantification over A just means that we can't see it from the outside. So if you look at all the types, this A never shows up in any signatures. So it's totally abstract. We have some abstract state and we have some step or function that takes that state and gives us our result back or a new state and to keep looping. So in order to evaluate it, we have to keep looping until we find our left instructor which will tell us the term. So we're just kind of pull out that either. We're left with new F and so we've got A to F of A paired with an A value, an abstract A value. So one thing that's actually really interesting about existential quantification and why a lot of functional languages don't really support them and that's because existentials can be eliminated through what's called the closure isomorphism and that just means essentially that any existential it can has an encoding to a closure. It's kind of where you get objects correspond to existential.  In functional languages, you don't often have to have existentials. You can encode them through closures. So we're gonna look at kind of what that means though like what this isomorphism means. So we're gonna look at what this isomorphism means we're gonna look at a simpler example not our fixed point or new type. We're gonna look at this can show which is kind of common. I wanna somehow represent a data type an abstract data type that supports a show function that can turn it into a string. So we're gonna look at type can show if it was existentially quantified it would be exist A and it would be a function A to string and that object. And this existential can be encoded as a closure with a rank to universally quantified eliminator which is just a really complicated way of saying we're gonna CPS we're gonna continue turn this data type into a continuation passing style. But in order to preserve the abstractness of A we have to make sure the continuation that consumes these values here is must be polymorphic in the A value. So this rank to universal quantification just means that whatever implementation goes in here it must treat that abstractly and really what that means this is kind of our example of here what that means is that if our consumer here uses this callback if we have some abstract A and all we can do with it we can't do anything with it we can't like add it to something because all we have is this function that's paired with it that turns it into a string. So having that A and a function that turns it into a string means that it's equivalent to just having a string and really because of laziness and all that it's really equivalent to having a deferred string or kind of like a lazy string. And that's kind of why in Haskell you'll see like existential quantification is kind of an anti-pattern and it's not so much that it's an anti-pattern it's just that Haskell's really good at universal quantification and you can just encode it with universal quantification and it'll probably be a lot easier to use. So we're gonna try to apply this transformation to our new type. We've got tuple A to F of A and an A. So before we had our existential A we could essentially just turn it into a unit type, right? So we can't do anything except just apply this step function to it. But the problem when we do that with this type is whereas before with this string if we look at this implementation the A in the function only appears as an argument in this function it also appears as in the return type. So it occurs in both positive and negative position of this type which makes it, I don't know what to write here. I can't act, you literally can't write this and that's because you have to use a type level quantification for recursion or what's usually called mute. And in, there is a data type and we'll get to that but PureScript and Haskell actually apply this to any data declaration, data or new type not type aliases or type synonyms. Any data or new type actually gets this implicitly applied and that's what lets you write recursive data types. So in order to write this new quantification that's where we get this data type mute F and this is what creates that recursion. We've created a new, like a recursive data type here that we can apply our F to. So we can actually factor out that unit arrow that's the function unit functor. And so we can get a kind of a simpler mute data type which was what exists in PureScript fixed points and then we can get our sort of delayed mute which we'll need for like evaluation order purposes. We don't want to be too strict and so we'll just compose that F with the function unit functor. So our delayed mute is compose function unit with F. So now let's look at, can we write a fixed function for this and it's actually pretty easy. We're gonna use delayed mute and the same either be functor and you'll see here, we unroll, we're importing data.functor.mue and data.functor.compose and unroll just takes that new wrapper off and so we're left with a compose so we have to kind of and take that wrapper off and so then we have our thunk then we can call our thunk, get our left or our right value and we get, and it's the same sort of thing left just returns the result or we just recurse on mute. So we've written our fixed type and it's actually really simple just to swap this out. So there's a few things here. One is just I've replaced loop and done with right and left. Otherwise it's the exact same worker like the logic here is exactly the same as the worker we had written before. Our wrapper is just a little bit different. We've got our new fixed function here and the worker just has to have this extra like roll and compose and that's to make kind of like the types work out and then we have our delayed computation here and this implementation is just a stack safe as the other one this will run in a constant stack that we've just bundled everything up into these this thunk, this function unit. So can we kind of undo this transformation? So we did all of these steps to get like all these data define these data types turn it into like a tail recursive function but you see this function now isn't even like really I mean, it's recursive but it's not tail recursive because we're returning right but it's just a stack safe because of the way we're computing we're using fixed here and delayed computation the delayedness is crucial. If you remove that function unit this will not work and of course it will just continue to spin and explode on the stack. So the delay is absolutely crucial but we can kind of start to undo these transformations so we're going back now to our mutually recursive go and eval and the same thing this is just as stack safe as the other one this will run in a constant stack. Can we go further? We can keep going then again we can turn it into just remove our continuation data types and just use named continuations here and again this is still stack safe. You can go even further turn it back into our original continue or into our continuation passing style here and again this is still stack safe instead of just identity though we have to turn we have to use this done or this is ends up being what our identity is is role composed const left it's kind of kind of extreme but this is still stack safe and this is in some ways this is a lot nicer to be used can we go even further? We had that kind of direct style where it was just bindings and we can but first I want to look at what this data type is that we're working with so if we take this type aliases make it a rec we're going to call it rec rather and it's new compose function unit either be this is very very very big kind of like a fancy looking type let's kind of like inline that and kind of get rid of all of get rid of all the functerness for now the functerness and just turn it into a pretty straightforward recursive definition we've got rec be is equal to rec we've got unit to either be or rec be again so this is where we get that that's why we have new we have this recursive data type that recurses here and I'm going to do kind of a kind of an interesting transformation here I'm going to take this unit arrow and I'm going to distribute it to the branches of either so instead of unit to either be we have an either where the unit is under each constructor this isn't necessarily a safe transformation in general but for this particular type this is equally as expressive so it's totally okay we don't lose anything with this so we've got this unit to be and unit to rec be we're just going to rename those back to like our nice done and loop so we've got done unit be and loop unit to rec be and this unit to be is kind of unnecessary like the unit here because we can turn that into just done be and loop unit to rec be because if we want like a delayed done value then kind of just most of the time we can just wrap it in a rec so it's not super necessary for our purposes it might be necessary if you were doing additional composition with like the unit to be like after you're actually done with it and this just preserves like laziness value this is why in like Haskell like you don't ever have to think about these units these units like this because everything just kind of implicitly has that in a lazy value but we're keeping this and this is actually this type is really interesting here so if we just go ahead and turn this into our functary name again here function unit rec be and kind of abstract this away into an F we've got this rec F be done be loop F rec F be Wow and so we can recover our rec here with rec function unit and this type here rec is actually the naive definition of the free moment so it wouldn't be an eight Fabian talk with out something about something about free moment so through this process we've essentially just we've derived a it's naive because it's the it's the true essence of it's the minimal definition of free that there are some there are some properties of it that aren't great but for our for this it's totally fine for for for pedagogical purposes it's totally fine and then our rec type alias is what we call trampling so free function unit so instead of going through the process of like writing the implementation for these which could be an entire talk in itself we're going to just import data.free so we have to use we have to define this suspend function it doesn't exist totally it has a delay function but we need this just to like delay evaluations again the delaying the evaluations is kind of subtle when you're dealing with this so it's so we're going to use this definition it's also equivalent to bind with a pure unit you even don't necessarily need with with data.free and this particular implementation you don't need the function unit functor you can you can kind of get around it but for for our purposes we'll keep this here we'll define this suspend function and then we can write just using typical monad stuff with the trampoline monad we can we can recover our nice direct style binding so go FL binds to L et cetera we're just using pure at the end so this is very nice very minimal very close to our original our original algorithm it just has a little extra stuff it still has kind of the wrapper of one trampoline but it's pretty close and then just adding a little bit of syntactic noise but clearly we're in a DSL here our stack safe DSL and we're back to something that is very very close to our original our original definition and this is totally stack safe as well so you may ask why did we go through all that process of like data types and all this kind of stuff when you can just kind of like throw in these function calls and all that and there are actually like a lot of good reasons again I brought up some issues around like this is kind of like defining your own little language and small step semantics you could use this for debugging and stepping through code and but for more practical considerations it's that when you have a tail recursive loop that's specialized to your your subset your language subset it's just it's gonna it's gonna get like a whole lot better on the JavaScript back end the trampoline monad is actually lets you deal with the higher order recursive case and so like any call any recursion anywhere then that just that binds through trampoline becomes stack safe which is very nice but we don't really need this we still have a first order recursive algorithm we don't really need that and so if we go to just like the tail recursive loop with explicit data types that'll the just in time compiler in JavaScript will hit that really hard and it'll be a lot faster than the trampoline approach the trampoline approach has a kind of like a performance hit of anywhere from 25 to 50 times you know slower than the than a naive recursive algorithm the in my experience this is just in my experience of like writing these functions and but it in my experience the other one the using the tail recursive loop with explicit data constructors is about six to eight times slower than our naive algorithm so big performance difference and so that's really kind of where it stands like trampoline is obviously like great and if you just want to like stack safety really really quickly without doing hardly any work you can use trampoline just like this it'll give you something stack safe it'll just be really slow and I haven't benchmarked it but I think it's you know promising to kind of go the middle of the road with for this first order case where you can do sort of like our least fixed point approach are the original one where you're just using closures and a fixed loop so thank you that is that is stack safety in pure script there's a lot more to like talk about around stack safety there's like other stack safe monads like app and stuff but that's just basically it they essentially just use a free monad sort of encoding and that's what that's what gives them stack safety so that's kind of that's kind of like the end of that story so all right, thanks everyone. Everyone, since Nathan's not here Anupam's gonna be taking the questions Anupam, the first question is for this first order what does the question mean?  Hey, can you hear me? Yeah, yeah, I can hear you. Okay, great. Yes, I'll try to take the questions. That was a pretty great talk full of a lot of stuff so I'll try to answer the questions as best as I can. So what is first order versus higher order recursion means? I think what Nate meant by that was that instead of a function calling another function recursively so first order would be a function calling another function recursively and higher order recursion would involve passing a function that happens to be recursive into another function and then that function calling the past and value so it doesn't have static information on what function it's calling and that's the decision so that makes it a lot harder to optimize it and make it stack safe. That seems to be all the questions if anyone has any questions I think for the next 30 seconds just put them on all chat or put them on YouTube I'll be having a look over there too. Okay, so there don't seem to be any more questions if you do kind of get any questions in the chat later. So for now, thanks everyone for all the talks, thanks all the speakers. I wanna thank all the speakers for I wanna thank all the speakers for agreeing to do this and now I'll hand it over to Anupam for the outro. Anupam, take it. Hey, thanks Aditya. So how's everyone doing? That was the end of all the talks but the conference is not over yet because we have the music jam coming up which I'm really excited about. So please stick around for that. Yeah, what a great set of talks. I think the presenters, the speakers put in a lot of work they volunteered their time and they volunteered their large sweat and tears especially considering the time zone differences. So big thanks to all of them. The PureSkip community is like the nicest community I've been a part of and everyone was very nice and very cooperative and so thanks everyone and thanks to the people who made this possible. So our host, Jaspe, this talk came out of a discussion that we were having with the Jaspe folks in 2020. So thanks for that. Hasgeek has been hosting this on their platform and our sponsors who made this possible, MLabs and Melenso, they do functional programming work and I think at least MLabs is hiring as well. So do check them out. They do some great work. And also, yeah, thanks to our volunteers and especially Aditya for emceeing this and everyone else who was involved and thanks to the attendees. I hope you had a good time. I hope you can take something back from the talks and we hope to have more talks, more conferences like this in the future. So please watch out for those. And yeah, I'll hand it back. I think I'll hand it back to Mike now for the Music Jam. So thanks everyone. Enjoy the Music Jam. Thanks Anupam. Thanks for doing this. This was really great. You did a great job. Yeah, thank you very much, Anupam. It's really nice to have this and hopefully next year we can have it physically in India. That would be really, really fun. Yeah, okay. So welcome to Music Jam. We'll let this go sort of for as long as we'd feel like, but I'm gonna create a link now, pure con 2022. You have never been to India. Either would be so, so, so nice to have a conference there. Okay. So I'm sitting at this link. Whoever's, I would like to be part of the Jam can hop on. I'll stay on the Zoom call. Maybe we can kind of do it for, I don't know, sort of however long it makes sense, but I'll see folks on there. So it's hdpsyapp.wags.fm slash p slash pureconf 2022. I see six people on there, which is great. A way to do a sanity check in your browser is, I recommend using Chrome or Safari Firefox to be a little janky, but it works. I'm just press play. So press play in the upper left corner. And if you hear somebody saying hello with some bloops and bleeps, then it's fine. And then you can press stop and it's all good. So I'm just gonna tweet this out because you never know who will show up strongly normal at these Jam sessions. So I'm gonna invite folks to it. Jamming at pureconf, I'll hashtag it at pureconf. Please show me. Okay, great. So I just tweeted it, let's see if anybody else shows up. So yapp.wags.fm slash p slash pureconf 2022. I see in there now 12 people, awesome. So, and somebody's already jumped the gun because I was gonna give instructions on how to edit it, but somebody from the bunch has changed it from hello world to hello pureconf.  We have a proxy syntax, but we can also use a string syntax. Maybe it makes it a little ergonomic and a little bit easier to work with. So we can leave it at proxy and that way it'll continue running in the browser. So I'm just gonna create a single note. So you should hear just this one note bleeping and blooping in there. And now what I'm gonna do is copy and paste a couple of links that can be really nice for you to get inspiration. So one of them is this one, which has a lot of working examples that you can use sort of right off the bat. And another one is the sound library, which is the superdirt since library. Let me send it to you, because otherwise you won't know what sounds exist that you could use just by copying and pasting them. Here you go. So I will copy and paste it into the document. Great.  For example, one of them is called Glitch 2. So if I put in Glitch 2, you'll hear Glitch 2 now. So we have a speed of a note and a glitch. So now this will be an excuse for me to drop in the documentation, although you'll learn by playing around. I mean, specifically in that documentation, which is new, so there's a lot of stubs, but there's a section on mini notation. And that's all this is. This is this type of syntax called mini notation. So what we can do is add a note to it. We could also make it a little bit slower, which I'll do now maybe so there you go. So it's slower for those of you that are listening. I don't know if there are 24 glitches. Actually, there aren't. Glitch goes up in, so if we look at that, Glitch goes up to seven, but there are seven. So if you change it to Glitch, like three or four, five. Sure, Glitch five. Great, sounds nice. So that's it. So if you add a sound, it'll add it to the mix. So it'll subdivide the cycle. It cycles two seconds long. So now we're here, one, two. But if I add another note, then we'll hear triplets. So you could hear, it feels like it's sped up because it has another note in there. There you go. Great, so we just added Jazz 34 to the mix. And now it will see up. Hey, Mike, sorry, a quick comment. We can't hear the sound from your presentation. So people who are not on that page are probably not able to hear it. Ah, okay. Yeah, I could diffuse the sound for sure. My only concern though is that if folks are diffusing it live in their browser, then they'll kind of double hear it. Right. I hear people, I just heard somebody add a bass drum. So people are kind of going. So maybe if it's okay with you, I'll leave it, this sound off, but maybe just encourage folks to visit the website and press play so they could hear it. Because already we have our creators creating and jamming. Sure, sure. Yep. So one thing I'd like to do now is go over subdivisions. So now I'm gonna, so now we have five events that are equally spaced. So subdivisions are compile safe. So I'm gonna make a compile error and I'd like all you to see what happens. So you see that there's an error now. If you click on the exclamation point, you'll see the parser found an opening tag without a closing tag. And that's because we're using type level programming to parse these symbols. So now I'll put a closing tag and it'll update just fine. Great. I heard, I saw a Gabba louder in there. I could copy and paste it back and disappeared for some reason. Yeah, there you go. And we'll add a closing, closing a parser to it. Okay.  But that's fine then. Great. Great. So now we hear the Gabba louder three in there. So it's subdivided into three different units. And you can change it to whatever you want. Like I'll change it to notes just because I feel like it, it's anarchy. So you can change it to whatever you want. So now let's listen to the music that's happening in there. We hear four events. One, two, three, and then three events mushed together. And that's how you create subdivisions in here. So I'm gonna create another subdivision except this time within the subdivision I'm gonna make a chord. And you make a chord with a comma. So I'll do notes 10. And you'll hear a chord. And again, refresh the pages for whatever reason the compilation is too slow. So now I have a chord. And within the chord we can also make some sequences. Yeah, I see folks doing that. Sounds really nice. I'm gonna add a new line in there just to make it a little bit more legible because this symbol will parse it just fine. Well, so one thing I'm gonna do just for fun is start adding a couple like little effects chains in there just to kind of show you what's possible. And we'll see if anybody has any questions too. What does the grouping of notes do? So the grouping of notes in square brackets creates this subdivision. And if you put a comma, then it'll superimpose them. So we'll create a chord. And we have one quite nice one going right now. So you can kind of follow along in the music and see what's being created. So yeah, so now I'll add a rate change. We have a mega chord going on in there. It's taking a bit long to compile for me. Oh, okay. Yeah, I actually just hit that too. Maybe it's the try peer script instance that's barking. It just started having that issue for me as well. I'm out. Yeah. Wow, that's so nice. Whoever added that, that sounds really cool. Yeah, I think there's a backlog on the try peer scripts over now I hear the tabla. I'm gonna send this out on the discord just cause it's a really nice groove. In case anybody else wants to use it. I'm gonna send it on the music channel. So I'll show you guys a little pro tip. There's a function called on tag that I'll import on tag and I've created a tag zero. And for now I'm just gonna create identity but I'll fill it with something kind of cool. So now I'll put change rate cons 2.0. Then my compilation just stopped as well. So immediately it's coming out for me. Could be the try peer script instance that's displeased. But the music continues, which is fine. So you hear now that the thing with tag zero is higher cause I'm calling change rate on it. We can make it even higher. So you hear it higher. I'll bring it lower again. And you could change those change or chain those on tags as many as you'd like. So you can make as many tags as you'd like and add as many little changes as they'd like. There's a syntax error that was just, oh, actually no, it wasn't a syntax error. It was just a server's 502ing. Yeah, I see what you're talking about. Intermediately 502s for me. Yeah, I hear the symbol in there. I mean, I'll turn off my audio for now just so you can listen to the music. So yeah, let's let it run for about 10-ish, 15-ish minutes or so and just see where we go. I'll just chime in cause it looks like we've gotten it into this persistent compiler error state which is a bit embarrassing, but the server, it's never taken this many people editing a document at the same time. Like it's been a bit more tame in other situations. And now it's showing its limits, but it's really nice what you got before. It was looping for me for a while and I didn't even realize it wasn't updating until I refreshed the page. So maybe for some folks, I could see it's still going, which is great. But the triangle synth example that I put below, I'll comment it in at a certain point just so you could hear that that's kind of a nice way to get a synthesizer going. But maybe, yeah, Anupam, if you want to do some sort of wrap-up, just so I see that it is kind of crapping up out a lot and I'm sorry about that. It's, this is kind of a new frontier that it's hitting in a good way, but by the way that it has to be during this nice jam that we're doing, sounds really cool. Hey, yeah, it's, I think we put too much load on the server. So this is going through Try PeerScript for the PeerScript compilation, right? I think... Yeah, exactly, exactly. And kind of usually the, I mean, it's able to handle usually pretty heavy loads I've seen, I'm looking at the logs right now and Try PeerScript is completely freaking out. So I think it's something about the way that we're doing it. Maybe the fact that it's all in one document and it's going so fast and propagating to everybody as it's changing is that's doing it, yeah. Right. No, it sounded pretty cool and it was pretty cool to be able to change code in the browser and hear actual music come out. I actually had my nine-year-old son sitting with me and he was telling me which sounds to pick. He was like playing sounds and I would pick a sound, put it in the code and it got him really excited about making music with PeerScript. And so it was a great demo. Thanks for doing this, Mike. I think we can have maybe just the jam as well as an event sometime later. It'll be great to make some real music, something longer. Yeah, absolutely. And kind of on my end now that I have the server logs, I will look at them and see exactly what's going on. I talked a little bit about maybe deploying Try PeerScript on AWS Lambda. So hopefully I'll do that and hopefully that'll fix the issue. Yeah, that'll be great. All right. If anyone else has something that they would like to discuss or ask, just let me know. Otherwise, I think maybe we can close the conference. Aditya, do you have something more? Not really. I mean, like I said before, thanks everyone. Thanks for attending. This is the first PeerConf and yeah, I mean, I thought it was great. It's nice to kind of see everything. I'm like a bit into everything. So it's nice to kind of, I hear some stuff. I get it. I hear some stuff. I'm like, oh man, it's just flying over my head. It was awesome. I liked all of it. And yeah, I mean, I hope to have another PeerConf. It'll be fun to just kind of get everyone together. Let's talk more about this talk because, you know, yearly changes keep happening. I mean, that's where it's from my end. I don't know anything else. Great, yeah. So PeerConf in India next time, that's the target. And so thanks everyone. Hope to see you in person next time, you know, for the next conference. Take care. Thank you. Thanks everyone. Thanks James. Thanks. Thanks Mike. Thanks, that was great. Thanks.


Thank you for checking out this no spoilers show review. This is for the Television show that actually has two seasons at the moment on Netflix called Mindhunter. It's a David Fincher show and We're up front. I might not sound normal. I'm kind of battling something right now So if my energy levels a little bit lower, I apologize if my voice sounding kind of weird I apologize also if I need to like stop and Like just now like swallow some leakage that's coming out of my head. I know that's really nasty So I'm sorry, but let's talk about Mindhunter a little bit So I just finished the second season of Mindhunter. I watched the first season not long after it originally came out I think those last year 2018 maybe in 2017 technically, but I'm a person who's into true crime This is a show that I would definitely recommend for people who find true crime Stories and procedurals and stuff like that interesting So yeah So I'm gonna talk about this a little bit my feelings on it overall But I don't have my notes like I usually do for movies I don't I don't do that for shows because it's a long time like there are many many episodes It's much easier to just do like one movie. That's like an hour and a half two hours versus like Ten hours or something. So I'm just gonna go with what I got If you feel like I'm missing something that you want to hear about it or you want to say about it go ahead and put it down in the comments and Also, I'd just like to hear what you have to say about Mindhunter whether you've seen it or not your feelings So like I said, this is this is good for people who are into true crime. I am into true crime You know, I don't want to necessarily say into true true crime maybe kind of curious about true crime fascinated by true crime because I feel like it's important to kind of draw a distinction of like a fandom around terrible things like murders Versus having Like not understanding it and kind of wanting to understand more like where I come from with my interest in true crime has to do with I just can't wrap my head around What makes people this way and for that reason Mindhunter in particular is a great show for people like me who feel that way Because it's I mean everyone knows this if you've kind of seen the trailer for it It's basically about in the 70s. I believe this yeah in the 70s when the FBI Started to kind of look at profiling killers. It's I think it kind of started is just Particularly violent killers and then they kind of started specifically focusing on serial killers and they actually coined the term serial killer And that's kind of where this came from so it's It's going back and following that so it's based in reality, but the problem with these types of Films and shows is you don't really know how much is real and how much is fictionalized just for story So it's kind of hard Watching this to know what's real and what's not so when I watch it I watch it more is just like an entertainment nonfiction thing knowing that they're just elements of fiction or of nonfiction to it So yeah, that's just kind of how I watch it now. It looks really good It's it's shot extremely well. It's directed extremely extremely well David Fincher is really great at what he does people know him from like, you know I believe he did fight club and seven, you know He has a particular look and that look is in Mindhunter as well like it's it's Muted colors. It's very dark. It's very kind of grim and it makes sense for the material That's a play here. The actors do a really good job I think everyone I don't think there's like anyone involved in either of the seasons who I was just like Ooh, that acting's kind of rough. They did a great job. The other thing is when they are doing interviews with Serial killers in this in in jail They did a really good job of getting people who could act like them but also getting the the special effects To man costuming to really make them look like them It's actually very crazy and kind of jarring to see that So the writing is really good the It's kind of hard because a lot of people who aren't already into true crime might not like this because it's just it's a lot of dialogue It's a lot of talking. There's not a whole lot of action. Although there are some like crime scene Investigations that go on. It's not a ton.  Like how can we take this information and then apply it to solving open cases and Identifying when it's not just, you know, one person murdered here one person murdered here But it's actually a serial situation where they're all related and I think That's like them putting the information together really starts in season one season two You see a lot more of them really putting it into practice and there's a really It's been kind of Covered in the media more lately a case in particular. I'm not gonna, you know, spill the beans on it That's heavily heavily done in season two. They actually have entire episodes multiple entire episodes just geared towards that one case and it has these characters kind of getting involved in that now That's one of those things where I was talking about like I don't know if in real life these people were involved in that I don't even know if in real life these characters are even real to who was there. Was it more than that? I know a lot of the times For movie sake, they'll take situations where it's a lot more people involved And then they just kind of pare it down into like one or two characters So like those two characters one or two characters are actually representing a myriad people Kind of like in Chernobyl that was the case if anyone saw the HBO show Chernobyl. Sorry. I just but Yeah, I mean I think it's it plays as realistic There's nothing that goes on that you're just like oh that took me out of it That doesn't seem you know doable or accurate But the story stays engaging in my opinion if you're okay with mainly just dialogue like I was saying Each individual person has their own kind of stuff going on too So it's kind of like side stories, which is good for actual character development makes you care more about these people who are involved Although I will say there's one character. That's that's more involved in season one than two and Their involvement in season two. I was just kind of like it kind of feels like they shouldn't even be there And it's and it kind of feels like they should have tried a little bit harder To tie that person into the season a lot more because it it just makes you feel like they're just gonna get rid of them at some point Not like, you know, they're gonna die or anything like that, but just like they're just like They feel like this character is not interesting anymore to them At least the writers and they're just kind of like yeah, let's just you know We'll bring them in a little bit here there and you know Maybe we'll just phase this person out at some point which sucks because it's a good character and the characters Pretty important in season one. I think and I kind of just want to see more involvement in season two. So I don't know It's disturbing For some people for some people not especially if you're used to true crime stuff It can get very very disturbing they do a really good job of keeping tension in the show and Part of that comes from the soundtrack the score to it is really good a lot in the music at key times is extremely creepy and I find that a lot with Fincher stuff He does a really really good job with that and it's extremely effective in the show extremely effective Like honestly with some of this music if I wasn't even watching anything and I was just listening to it I would still feel like this is creepy like it's it's very well done very very well done. So Yeah, I mean I feel like that's kind of all I have to say about this show because I don't want to get too much into things because like I said, you know no spoilers. I would encourage people who are Interested in true crime or are just a little bit curious to go ahead and just check out the show Don't just do like one or two Episodes or anything and the other thing is maybe just give it the whole first season and start the second season Because with a lot of shows it's much slower in the beginning. They have to establish kind of the world It's it's set in the storyline that they're trying to go for and the characters themselves. So it's slower That said I do think the second season picks up more It is more engaging than the first season and it just kind of holds to what I was just saying It's you know, they just had to set a bunch of stuff up in the first season The other thing I want to say real quick is I read an article that was saying that David Fincher has stated that there are supposed to be five total seasons. I believe of This show so hopefully that's accurate Hopefully it doesn't it doesn't hit the Netflix chopping block because I know there are a lot of people who are into the show But for some reason they've just kind of been chopping a lot of their shows. I don't know I don't get it But anyway, um really interesting. So like I said, let's put some comments down down there If you have seen this I really want to hear from you what your thoughts on the show are I haven't really heard any negative opinions on the show from well, okay Maybe just a few people who are just like, oh, I feel like I couldn't get into it I stopped after the first episode or the second episode and it was just because of you know Like I was saying the slowness they have to establish things and it's like stick with it Good things come to those who wait. It's that type of thing, but um Yeah, let's talk about that your feelings. Do you like it? Do you not like it? Are you interested to see more seasons? Where do you hope it goes? What's your interest level for more seasons? When you say when you give like an actual number of seasons It's kind of great because it gives people this hope of like, oh man, there's more to come That's awesome. Like I know I can rely on you know seeing more of this show and I'm excited But then at the same time it gives you a finite end so that you know Maybe when you hit like season four, you're like, oh, no There's only one more season or when you're on season five. You're like, I know this is gonna be it and that just sucks so You know sometimes it's better to just Go sometimes it's better to just let people know ahead of time I don't know your opinions on that too if you want do me a quick favor hit that subscribe for me can help me out a Lot really encourages me to keep these videos going to you can do the thumbs up But the important thing is to subscribe so thank you very much for just checking this out And until next time keep it brew