 All right, I don't know what you guys saw or missed or not, but I'll just say that this is Philip Carter. We're talking about F-sharp. He was telling me about all these amazing things. You've heard nothing of that. So let's get back to it, and maybe start a little bit over again, maybe run through that again for me. We'll go through this a little bit faster because people didn't see this. So new version of the F-sharp language came out. We love it. It's pretty great. It has some pretty cool stuff in it. So I'm going to go through that, and then I'm going to show you how to get started for existing F-sharp developers and people who are new to the language. It should be pretty easy to get started. So there's a quick list of the feature set. So we're aligning versions with some assets. And existing F-sharp developers should be very happy about this. I know that I'm incredibly happy because it fixes a longstanding confusion. We have a feature set that I'm calling span of T and friends, but it's really a whole bunch of different features. And so I'm going to go through that and show how you can do better low-level programming with F-sharp. For better performance. Exactly. We have a new keyword, match bang. It's similar to our match operator, but it's more excited. We have some relaxations and syntax. So some stuff that you might have found, perhaps, not necessarily intuitive in the past are now just not really a requirement anymore, which is kind of nice. We have better async stack traces. So for our asynchronous computation expression feature, diagnostics should be a little bit easier. So we like that a lot. We have value option of T, which is similar to option of T, except it's a value type. So it's a struct rather than the heap allocated one. And so there's that type and a little bit of stuff with it, but it's kind of more a building block for some future things that we want to do with it. And then we have some smaller goodies in the F-sharp core library, just some nice improvements, some small performance improvements, bug fixes, lots of community involvement there, and a few kind of utility functions that kind of make your life easier. Cool. So let's talk about versioning. It's everybody's favorite thing to talk about. Hopefully we made it less confusing. In F-sharp 4.1, you had F-sharp language version 4.1. There was an F-sharp.core Nougat package version 4.3.4. And an F-sharp.core binary version 4.4.1.0. Those numbers are different, especially the binary version. Everybody got that? Yeah, the binary version is something that people found quite confusing in the past, because the first four actually stood for CLR version 4, because there was a point-in-time problem with older versions of .NET Framework and newer versions of .NET Framework at the time. They're all older now, but at the time it was newer, and we needed to be able to differentiate between running on like .NET Framework 2.0 and 3.5, and .NET Framework 4.0 and higher. Gotcha. And that was a decision that was made that kind of made sense at the time, but sort of now it's kind of history and F-sharp developers with F-sharp 4.1 had to sort of inherit that history and not necessarily benefit from it anymore. Similarly, the Nougat package where the F-sharp.core binary lives in has had to rev in various ways that basically forced the numbers to become different. And it just ended up creating a confusing mess, honestly. So you're aligning that with this version. Yeah, now it's F-sharp language 4.5. The F-sharp.core Nougat package is 4.5.patch version, and then the binary version is 4.5.0.0. Great. The first two numbers are the same, and we intend to keep it that way. Hopefully this makes everybody's lives a lot easier, certainly. It's a lot easier for beginners, too, as well, right? Actually, we have a couple of questions that may fit right here. So why would I use F-sharp? Like, let's back up a second. What's F-sharp good for? Like, why would I use it? Well, it's pretty much good for everything. I mean, it's a general purpose, functional programming language. So a lot of systems that you're writing, yeah, there's state and stuff like that. So functional programming is all about, oh, I don't want to have global state, I don't want to manipulate state in place or anything like that. But most of the stuff that people write are actually functional. Like, if you think about chaining HTTP calls, like you're giving something input, you're expecting output, you're passing that output as a parameter to something else. You're creating pipelines of, okay, data goes in here, gets processed a little bit, it comes out. I take that result, I do something with it. But like, I'm not necessarily mutating stuff all the time. And so when you're building these sorts of things, using a functional programming language will give you defaults in the language that sort of make it easier to do that. Whereas when you're using something like C-sharp, although you can certainly use it for these sorts of things, the defaults of the language are like, you know, everything is mutable by default. So you kind of go towards a path of mutating values all the time. When you may be working in a context where you don't want to mutate anything. And so with F-sharp, you have to be explicit about wanting to mutate something. And so like, it's kind of that inversion, but then when you're programming certain kinds of systems, that inversion helps. And so that's part of it. That's a great explanation. So great question, guys. All right, cool. So let's talk about some more F-sharp 4.5 features. Spand of T in Friends is actually, depending on how you count, I'm gonna count it as eight new features. Okay. So there are some new types. In ref of T, out ref of T, there's this type called a by ref, which has existed in F-sharp for quite a while, but hasn't really been fleshed out. It's basically this managed pointer type, but we didn't really have good support for low level programming with that. So we decided to flesh that out completely. There's production and consumption of by ref returns, which is something that sort of came out a while ago and has evolved for quite a while here. We have extension method support for by refs. We have a lot of safety checks for using by refs and by ref types, which is kind of the thing that holds it all together because you wanna be able to do low level programming in a high level language, but the whole point of using high level language is to be safe. Right. And so we basically restrict what you can do, similar to how some of the newer versions of C-sharp like C-sharp 7.2 and 7.3 restrict usage of certain things like span of T. We basically apply very similar safety checks just with some extra F-sharp semantics on there. We have is by ref like structs and is read only structs, which I'll talk about a bit more. And then we have the void pointer type and some functions in the native pointer module of void pointer and to void pointer. And so I wanna reiterate that span of T is a by ref like type. So we have the ability to consume and produce these by ref like types. And one of the most important ones that exists in .NET Core today is span of T. So it's kind of important to sort of flesh out everything around being able to use that effectively. So that's what we're doing. So let's dive into that a little bit. By refs, we sort of this trifecta, they're kind of, they're related to one another. Algebraically, there's in ref, by ref and out ref, where in ref is a read only pointer, basically. It's saying that the basically the person where I guess I should say the caller that has a handle to that pointer can only read the value, you cannot write to it. A by ref is read write, you can do basically whatever. And then an out ref for compatibility reasons is actually has the same semantics as a by ref similar to how it exists in C-sharp. But it's there for documentation purposes because you sort of wanna indicate that, oh, I'm only writing to this pointer, I'm not really reading anything there. Gotcha. And they have some subsumption rules so you could pass in, like if you're expecting an in ref or an out ref, you could pass in a by ref network. So there's some safety guarantees when you use these which did not completely exist prior to F-sharp 4.5. So you can, they cannot be used outside the scope that they're defined in. So if I define an in ref, for example, or a by ref in a given scope, and then I try to have that reference escape that scope somehow. So like say I have a function and then I try to have the reference to that value that I defined inside of that function escape the function scope, we disallow that completely. That's actually unsound and it can lead to depending on the way you use that value a crash at runtime, even though the code compiles just fine. Gotcha, okay. So that's why we disallow that. You cannot have them be contained within a heap allocated type because this is very much related to by ref like structs which are guaranteed to be stack only. And if you try to guarantee that something is stack only but you put it inside of a heap allocated type, then you're basically creating a contradiction. So we disallow that. They cannot be captured by a closure construct because that also allocates on the heap. And then we also cannot use them to parameterize a generic type. That's also a similar thing. A heap allocation could occur. And so the usage is restricted but that means that you can be very confident in sort of the safety of the stuff that you're doing there. So I wanna show a little bit about what that code sort of looks like. So you can see some of these in action. Cool. So let's define a first function. Let's call it print. Let's give it an in ref of int and we'll go print fn percent d. This is how you do formatted print for an int. And we'll just pass in x, right? So that compiles just fine. Notice if I try to go, if I try to write to the value, so if I try to change what x is, this allows me to, this by ref pointer is read only. Cool. So I can't write to it. Now, this does not mean that other threads cannot modify that place in memory, right? So it's still there still are complications that could occur in a larger system that you have to be aware of. But it sort of says like, okay, well, you know, for the thing that's calling this it cannot modify it. And I'll create another one. We'll call it print two. And it'll be a, by ref of int. Okay, so we'll print it out just like print. And then we will square, basically make it its square and then print it again. Okay. So in this case, I can read the value and I can write the value. So if I call these, so let's go, let's create a, let's say that's 12. And let's have num two is 13. Okay. I can go print num like that. And that's totally just fine. So this is, so this right here, this is how you sort of make something a, a by ref in this case, right? So print takes in an in ref. I'm basically getting a reference to num here. Now if I try to do this, check this out. Okay, I get an error. Now the reason why is because num two is still immutable, yet I'm taking a by ref and I'm mutating the value. So, so like I, what you need to do is you need to actually explicitly make this mutable. And so the error message sort of says like, you know, you know, you're not passing the right thing. So if I turn this into a mutable value, so it could change and then I give the reference, then I can actually mutate the underlying value inside of there and pass it to a thing that expects a by ref. That's cool. So this is some sort of, you know, this is sort of stuff that you're not likely to write this sort of code in all of your F sharp programs. But certainly when you're doing something that is like very, like, you know, the small kernel of your system that is like a hot loop or something like that or anything like that, you can eke out some more performance like this. Let's say I try to define a value X and then we'll go let mutable 12, oh, sorry, let mutable Y equals 12. And then we escape Y from the scope. This is that scoping rule that I said right there, right? I defined Y to be inside the scope of whatever X is, but by saying, oh, well, effectively saying, oh, well, this reference to this value that I defined inside of here, this is actually escaping that scope so I can't do that. Similarly, if I defined this value inside of one of these functions and then I tried to extract a reference out of the definition of that function, it won't allow me to do that. So basically you have to try really, really hard to screw up your program with using these because any of the ways that you could sort of fall into one of those traps, it'll basically not compile. Cool. And so that's something that we're happy about because safety is basically one of the top reasons why you should even use a high level language or especially a functional language where safety is one of the things that's oftentimes brought up as one of the strong virtues of functional programming. So we're taking that quite seriously. Cool. So there's also a bi-ref returns, which I mentioned. So in F-sharp 4.1, we had support for consuming them, but you couldn't produce them. So this was a feature that came out in C-sharp 7.0 and it was useful if you're doing programming with like Unity or something like that where you want to, well, basically return a bi-ref and perhaps mutate the underlying values while also doing work at the same time. So we allowed, yeah, consuming them and you could sort of have fun with that, but you couldn't produce any code like that in F-sharp. You would have to write that in C-sharp. So we changed that. Now in F-sharp 4.5, you can both produce and consume them. The return value is implicitly dereferenced when you consume it. So we match the same sort of behavior that C-sharp has there. We also implicitly dereference the value inside of a for loop. This is helpful if you're looping through a span and you need to do something with each sort of offset that you have there. And then if you want to sort of pass a reference around the different calls, stuff like that, you just use that reference operator that Ed showed you. So I'm just gonna show you what some of that looks like here. So we got this bi-ref returns guy here. Let me set this as a startup project. Okay, so I have this horribly mutable code. I don't want to say horribly, this code is fine. It does stuff. It's functional. No pun intended. Yeah, exactly. So we have this type. I call it a sequence in eight or 5,000. Good name. Yes, and I have a mutable array of numbers that it holds underneath inside of it. And then I override two string. And so all I do is just basically just print out the numbers there. And then I have a method in there called find largest smaller than. So given a target, basically saying, okay, well, I want to basically go through and then find the value that is smaller than this target, but it's the largest smaller value. Do that with some very, very mutable code. Like a counter that I'm decrementing as I have a while loop. And then the key thing here is I don't return the actual value. I return a bi-ref to the value. Like in both branches in this case. So if you look at the type signature here, it's given an int to produce a bi-ref event rather than an int there. So if you want to actually use this code, it's pretty fun. So we'll go let s equals sequence in eight or 5,000. We'll instantiate one of those guys. And let's print out the sequence first. Okay, I'm using percent s. So this is formatted for strings there. And we'll go s dot. Do, do, do, do, do. Right, s dot two strings, sorry. Okay, so this is gonna print out basically just that same one, three, seven, 15, 31, that sort of stuff. Then we'll create a target and I'll just call it 16. And then we'll have the results equal to I'm using this bi-ref operator here because sort of when you work with bi-ref returns, you, the value is implicitly dereferenced to get the actual value. But if you want to work in terms of the ref, then you just dereference that actual call, okay? So we'll go find largest smaller than 16. Okay, so if you have a result, it is a bi-ref event rather than an int, okay? So then we'll go results and we'll just go, just multiply it by two. And you notice this is actually not letting me do it yet because I can't type. That was gonna say, I don't know F-sharp, but I'm thinking you, Mr. Parethzi. Yeah, well you can't talk thinking code at the same time. You're gonna find. Okay, so now we're going to call two string again. So recap, I'm just two stringing the underlying thing. I'm calling this find largest smaller than and then I'm mutating the value. So this value that it gives me back is actually a reference to one of these items in this array, this underlying array. So just go ahead and do that, debug, run this without debugging. And it should give me a value here pretty soon. Okay, so you'll notice these two sequences are different, right? So the underlying value is 1, 3, 7, 15, but then the second time I printed out was 1, 3, 7, 30. That's because I had a reference to the underlying value inside of there. So, sort of again, not the sort of thing that you're gonna be doing in everyday F-sharp programming, but if you are in a situation where you need to do like very high performance work, you literally need to be working in terms of pointers, this is the sort of stuff that you can do. Gotcha, in a safe way. Yes, in a safe way. Cool, so then we also have byref like structs, which basically it's an attribute that you can just apply to an F-sharp struct type. Can be applied to struct records and discriminated unions, we actually have a bit of an issue with the use, but the discriminated union, so I wouldn't quite use that right now, but. Anyways, what it allows you to do is sort of say, oh, well I have a value type that I've defined. I can now give it byref like semantics, right? So what that means is, okay, it cannot be allocated within another heap allocated type, so I can't have a class that then has one of these structs inside of it if it's a byref like struct, right? So I can't use it in the class or normal struct, because with a normal struct you can't guarantee that it won't be allocated on the heap. I can't do it in a non-struct record, I can't do it in a non-struct discriminated union. I cannot use it as the type of a record or a discriminated union case. It cannot be captured by a closure construct and it cannot be used as a generic type parameter. And as I mentioned before, span of T is a byref like struct. So very briefly, we'll just see what that means here. Here I have my fun little struct here and I got a bunch of red squiggles. It's unhappy. Okay. The reason why it's unhappy is because this S is a struct, it's just a struct. There's no guarantee that this won't be allocated on the heap, yet span of T is a read only, sorry, a byref like struct, and byref like structs are guaranteed to be stack only. So the way that you do that, it's pretty easy. Let's go system, run time, dots, pretty sure it's compiler service, it's either that or interrupt services, is byref like, oh, I was right. Okay. So we'll just add that attribute and the red squiggles are gone. Okay. And so what this does with S now is now I could, this thing could contain spans, it could contain any number of byref like structs, but those safety rules that I just mentioned now apply to this. So if I try to do something fancy like this, create like just a simple record type and we'll go label is of type S, this immediately does not let me do it. This type instantiation involves a byref like type, it's not permitted. So the only way to do this is if R were also a struct and a byref like struct. Got it. So sort of this, I guess you could say poisonous. I don't know, but it makes sure that you can't screw up basically. Right. Okay. We also have read only structs. So is read only is actually interesting because structs in F sharp are basically already read only just due to the semantics of the F sharp language, but that's only something that other F sharp callers could really benefit from. As far as C sharp was concerned an F sharp struct is just a regular old struct. Okay. Well, C sharp has this notion of read only structs. And so if you apply the is read only attribute to your F sharp structs, then when you interoperate with C sharp, they will be treated as if they were also read only structs. Okay. And so the this pointer, for example, you won't be able to modify it as a C sharp caller and you won't be able to modify public members, that sort of stuff. And so kind of as I said with F sharp, this is sort of already been the case, the state of the world for F sharp only solutions is basically the same. But if you need to interoperate with some newer C sharp code and they're expecting read only structs, you can do this. And so as an example, read only span of T is a read only by reflect struct. So you can combine these as well. So you could create your own read only span like my fancy read only span, if you're up for the challenge. And you can do that as well. So. Cool. We also have void pointers, which are great. It's an untyped unmanaged pointer in F sharp code. This is basically a whatever the, wherever you want it to be sort of situation because void pointers and C are whatever they are. Right, right. So this is similar? Yeah, similar. So there are some cases where you got to interop with native code where you've got to use void pointers and you could not do that in F sharp prior to F sharp 4.5. So this is really used for native interop. Yes, exactly. And so we have some native pointer functions in the F sharp core library. You can convert from a void pointer to a native pointer of T and you can convert from a native pointer of T to a void pointer. And so sort of the difference there is that one is typed and the other is untyped. So you could say, okay, void pointer is just some stuff, but a native pointer of type T is actually sort of tied to an actual type. Right? And so you could bridge the gap there pretty easily. Whereas doing that before this was a lot more challenging in F sharp and most people probably wouldn't even try it. Okay, cool. So what type of applications typically you do a lot of native interop? Well, if you're doing, sometimes some game programming can get that way. Like super high performance type of stuff? Yeah, yeah, I mean, there's, I think there's still like, there's a lot of like, oh, well I have this unmanaged DLL or this C interface that I have to call into. Okay, when you're going down into like C assembly, okay. And that's just something that sometimes you just gotta do across a wide variety of applications. And so we wanna be able to sort of allow you to at least do that. Okay, make it possible. Cool, so we have the match bank keyword. Match! Match! Yeah, you can only say it by like shouting, stands up, like that. It's like go! Yes, exactly. And only if you're Eastern European as well. I drink half my coffee, so I'm very excited about this one. So this is a community contribution by John Wastonburg who's a member of the F sharp community. John! Good job. Thank you! People really love this feature already. So we're pretty happy about it. And it simplifies pattern matching in F sharp. Okay. And so it's often used with async code that returns an F sharp option. So I'll show what that looks like. Cool. Okay. No, I'm not gonna save. Saving is for losers, okay. Okay, set as startup, there we go. Okay, so I got some code here. Okay. This is try parse int async because you obviously need to be async to parse an int. But yeah, it takes in a string and then it just calls in 32.triparse. We actually in F sharp turn that out value into tuples. So true and V then basically false and underscore. We don't really care what it is if it didn't succeed. And then we return either a sum of the value or a none. And so this, if you hover over it says given a string produce an async event option. What this forces callers to do is once they have the actual value they can't just say, oh, I have the value now. They have to explicitly pattern match and say, okay, in the case where there was a value do something and in the case where there was not a value do something and it's impossible for you to move forward before you do that. So we have this function called print and print just calls try parse int async with an SDR string that gets passed in and then it just binds that result to a value called result. But result is not an int, it's that int option. So I need to now pattern match on that result and do something in this case printed out or saying not an int depending on what it is. Okay. This is fine. Like, I mean, this is really tiny code. So like this is really easy to digest, not that difficult. But when you have larger functions and larger expressions, multiple sub expressions and things like that, the inability to sort of call this async function directly inside of here results in a lot of blocks of code where you're like, okay, here's the result. Now pattern match on the result. I see, it's messy. Yeah, it can get a little messy sometimes. There are various ways to get around that and match is one of those ways. So I'm just going to extract this guy out here. Okay. And notice that it's still not quite happy because it's saying, oh, well, I got an async event option rather than an option. So that you just need to make it match bang and all of a sudden it's happy now. And so what this does, this is basically just syntax sugar for what you previously saw. Oh, okay. So it's just a way to shorten that expression up. So it calls this, yeah, it binds it to a value and then it pattern matches on that value. Got it. So it's doing the same thing behind the scenes. Yeah, and so it's just a very, just sort of nice thing. Nice, nice cleaner thing. So I've gotten some Twitter mentions from my Sharp developers saying. So John was just really annoyed with all this mess apparently and just said, you know what, let's just put a bang on the other thing. No, he's actually really cool. Yeah, it was a really, really great interaction. It was an issue created on our language suggestions repository. And he basically said, I'll try it. And so we said, okay, can you write an RFC for it? And they said, yeah, sure. So we wrote this back and then he gave us a pull request. And then we just worked with him on that pull request and sort of helped him with some of the errors and stuff and he did it very, very well. And then we merged it in and now it's in. That's awesome. The power of open source. And yeah, and he implemented it end to end, right? From the idea all the way to actually shipping into a product that was entirely an open source thing, which is pretty cool. We should get him on the show. Yeah, that'd be pretty cool. Cool, I like that one. Yeah, it's a fun one. It's just nice, makes things nice. I like when things are cleaner. Yes. So then we got relaxations in your code. You like to be relaxed, right? There you go, yeah. So got some fancy words, upcast with yield and F sharp sequence lists and array expressions. Use of the upcast operator is no longer needed when you need to use a super type with yield. It's an edge case that was annoying. And we got rid of that. Okay. Indentation for lists and array expressions, particularly with named arguments and method calls. There is a F sharp to JavaScript compiler called Fable and a UI programming. I've heard of that one. I've taken a look at that one. They're pretty great. And a UI programming library for it called Elmish that has a particular style of programming UIs that would run into this indentation problem all the time. And so just to show you what this looks like, got my relaxations file here. So this first one is collections and subsumption relaxations. I got, so X is an OBJ list. So string is an object, right? And similarly in a list of string is an object, it's totally fine. Here, if I needed to do this yield A, I needed to explicitly cast this to object. And so that was kind of annoying. You don't need to do that anymore. Now it just still compiles. And F sharp 4.1, this would not compile. I see. So it's just less stuff. Less stuff to do. And then this indentation relaxation, something that you may find a little strange is the fact that, okay, so I have this open bracket here, but then this, instead of being over here, it's just, it's right underneath the hello. This was something that was required in F sharp 4.1. And so people would write a lot of code that looked like this and then they'd get an indentation warning. Yeah, that's always what kind of hung me up in an F sharp in the beginning is those indentations. Yeah, this sort of stuff. Well, so some of it can be pretty easy to diagnose, but this stuff was oftentimes really difficult to diagnose. And especially if you're doing Fable style programming because these sorts of expressions here would be like nested. And so kind of like you create, well, you create a UI that's sort of like the DOM of a browser. And so browser trees, you know, they kind of look like a pyramid. And you would write code that kind of looked like that, but then somewhere in there, you did this when you actually needed this. And it was really difficult to track down. So we just kind of got rid of that. So that should make some people's lives easier, especially if they're doing UI programming with Fable. Cool, very cool. Yeah. Okay. And then we got value option of T. It's like option of T, but it's a struct. It's not much more to it than that really. It's a really easy one. That's what value means. Yes, it's adding value. So let's just change this. So you know how I have this sum and none? I'll just make this value sum, value none. And now it is an int v option rather than int option. You notice this doesn't compile here because I'm expecting an option instead of a value option. I see. And we'll just modify it and there we go. It's, you use it the exact same way you use options today. It's just, it's a value type. And so the reason why we- Why don't you use a value type? Right, so there are some cases depending on how your system runs where value types can be more efficient. You always gotta measure that. But those cases do exist, they are real. And more importantly, this optional type is also used under the covers and some other F-sharp constructs such as F-sharp active patterns. And F-sharp active patterns are something that a lot of people use because it's a really, really nice way to sort of express the combination and sort of like the notion of a function that you're a pattern matching over. And so you could not create struct versions of those. And so, well, you still can't do that today but this is the first step in building that out. And so this is something that sort of again was a community, not a community contribution to the community idea. And we had a lot of discussion with people there about sort of like why this should exist, what some of the benefits are, what some of the drawbacks are. We had some community involvement in writing the spec for this. And so then it was really easy to implement. So we just implemented it. But so in future F-sharp language versions, you can expect more stuff to be built out around this so that we really sort of flesh out the idea of full F-sharp programming with value types and reference types. Cool. Yeah. And we got better async stack traces. So this- That sounds like really good idea. This one I like, yeah. It's always good to be better. It's always good to have a stack trace that makes sense, right? So in the before time, information in stack traces was basically not really usable. Okay. I'm not gonna sugarcoat it, it sucked. It did. And like you would not get your user code in the stack trace. You would not get your line numbers in the stack trace. So like what's the point of a stack trace? Yeah, exactly. Okay. A little frustrating. And so they're still not perfect. There's a lot of work that has to go into making them good. But today, now with F-sharp 4.5, they at least user code names are in the stack trace and user code line numbers are in the stack trace. So you know that helps people that are just getting started with F-sharp to debug their code, you know? Yeah, so I'm just gonna modify this. So you know this N32.triparse, this is kind of safer. We don't like to be safe. We're just gonna return N32.parse value, right? This could throw an exception easily, right? If I pass a string that's not a number, it's just gonna throw it. That's the sort of thing there. So we'll go let's, let value goes try parse int async and we'll pass in str. Notice that value is an int. It is not an option event. So you do away with all of this. And we're just going to print out the value. This is unsafe code. Like this could fail in all sorts of different ways. So let's demonstrate that. So we're gonna, so I'm gonna create an array of function calls. So this is another thing. Functions are first class values in F-sharp. So you can do all sorts of fun stuff like this. So print 12, this is gonna work, right? Print 13, that's gonna work as well. Print, Beth, that's not gonna work. Okay. Okay, and we'll go async.parallel, right? So we're basically going to parallelize all of these and async.run synchronously. And I'm going to just ignore whatever the result is because I don't really care what the end result here is. This is gonna crash. And we wanna look at the stack trace. Exactly. Yeah, we're looking at stack traces. That's so exciting, isn't it? No, but people should actually benefit from this. They shouldn't enjoy this, even though stack trace is not necessarily the most sexy demo here. So we got this unhandled exception. I'm sure plenty of people have seen something like this before. I believe the font is big enough. But something I wanna call out here. All right, so we got the system format expression. Sorry, exception, this number, string to number, parse in 32, parse. But then right here at program.triparse in async in program.fs line seven, right? And then there's some stuff in there that we kinda wanna get rid of, but like, you know, it shows program.print at line 12 and program.main in line 18. So it's still not perfect. You mean we didn't get that before, huh? No, you wouldn't get this. You would get the internal closures of what's created under async, and so it would just be useless information to you because it would say under, oh, async.bind. And you're like, okay, great. Which one of the thousand async.binds do I have? So this seems like it's usable now. Yes. Okay, that's a good thing. It is usable, and so I wanna stress it's not perfect. There's still plenty of room for improvement. And it's a difficult space, so I wouldn't expect crazy improvements happening. Really, really fast or anything like that, but it is gone from basically not really that useful to something that you could probably use today. So we're pretty happy about that. Cool, we got a comment. What you did in four or five is great. Oh, thank you. Thank you guys. Okay, so then there's some additional fsharp.core things. Just nice little goodie bag. We got these funk convert APIs. So this is for C sharp interop. If you wanna interoperate between C sharp action types and F sharp function types, you can do this funk convert.from action and front convert, sorry, funk convert.to action. And we have various overloads for them that match the overloads of C sharp actions. And so with previous F sharp 4.1, you didn't have this. And so you had to kind of create this little contraption to interoperate between the two. And so now you can just interoperate directly. People interop a lot between C sharp and F sharp? It depends. I would say that especially for, so I would say that the majority of C sharp programmers that get into F sharp and they try to incorporate it into the workplace in some way do end up interoperating. Some people are able to do just all F sharp the whole way. And so this is not something they probably ever use. But this is really beneficial because, I mean, space F sharp, it's a .NET language. There's a pretty good chance if you're working with large .NET systems, there's gonna be C sharp involved. And so we wanna make- You have an existing system and you wanna extend with F sharp. Exactly. Similarly, we have map.tryget value. This is, it's similar to try parse API as an F sharp. It takes in, a bi-raff returns a bool. We convert that into a tuple for F sharp callers. It's just a nice quality of life thing. It actually allowed us to clean up some various code paths in the compiler. So we were pretty happy about that. And some other members in the community were happy too. And then there were just kind of a swath of general improvements and bug fixes. The F sharp community in particular did a lot of bug fixes, performance improvements, that sort of stuff through this core library. I would be listing like a hundred items here if I had them all, but- That's awesome. Yeah, it's pretty great. People like Stefan Forkman, Matthias Dietrich, who are members of our community, they just, they're constantly submitting pull requests that improve everything. And like, you know, a lot of the stuff that they submit, it's not big stuff. It's just making one tiny little thing better. And then over time, it just makes your whole life better because all these little nuances that you may have disliked over time are just now all of a sudden better. And from the team's perspective, they have to prioritize other things. And so it's nice to have the community pick their own priorities on things because it's gonna benefit everybody, right? Absolutely. And another thing is that people, so like people like Stefan and Matthias, they submit really, really high quality stuff. So there's basically very little risk in taking their changes, which is amazing. Like we have extremely high quality contributors who really care deeply about this stuff. F-Sharp is a very passionate community. I do know that, yeah. Yeah, they're pretty great. So that kind of rounds out what we got for- So that's all the get new. All right, that's all the new stuff. So all right, yeah. So we actually have, I've seen a lot of questions throw by here. Like, hey, you know, I like, I love C-Sharp, but like I'm really having trouble getting started with F-Sharp. Like, you know, I like, where was one of them was like about indentations, you know, main reason why I really dislike F-Sharp. It doesn't make sense. Sounds like you're helping correct some of that. But like really like what? Like for me, I also see them not a functional programmer myself. I remember way back in the day when I was learning object-oriented programming, there was just like a light bulb went on at one point, you know? At what point did like you get that light bulb? What is that thing that like really like it started to click for you? Absolutely. So at my previous job, I needed to write a DSL. Basically like if a system gets large enough, you need a DSL. Yeah, okay. I think that's just kind of how it eventually works out. And I Googled like, oh, how do I write a DSL and C-Sharp? And I saw something saying, oh, do it in F-Sharp. And I was like, interesting. I was just kind of curious. And I saw, you know, that F-Sharp was a thing. So I decided to give it a go. And I spent like a week kind of learning the language and building a DSL parser. Oh, sorry, a parser for the DSL that I needed there. And I ended up really, really liking it. And there were some great resources available to me. I'll actually bring up one of them right here. So there's this blog called F-Sharp for Fun and Profit. It's a really, really extensive blog by Scott Willaschen of the F-Sharp community. And so like there's, you know, there's some cool stuff here, but then what's nice here is there's a 30 part series on using F-Sharp and kind of comparing and contrasting with C-Sharp. And so as I was building the thing that I needed, in this case, a parser, it could be like unit tests. It could be any kind of data manipulation thing. I just sort of worked through each of these items as I was going and within a week, I had something functional. Heck. That's awesome, okay. Yeah, and that's just one way that you could get started. But there's a few more things that you can do here. So if you want to get started with F-Sharp 4.5 today, you got quite a few ways to do it. We have this here website, aka.ms slash F-Sharp home. This takes you to homepage that we have here and there's just a real nice big button that says get started with F-Sharp. You can just click that and then it'll tell you to download the latest Donut Core SDK. Okay. And F-Sharp is fully included in Donut Core. So this is completely cross-platform. Visual Studio also distributes this same SDK. So that's what I had in my, there we go. That's what I had in my IDE, for example. All of this is just .NET Core. All of this is just installed by default. Okay, cool. I just installed something that has .NET Core with it and boom, F-Sharp is in there. You're using Visual Studio too, so which version of Visual Studio do you need? This is version 15.8, so this is the latest version. And so under that, there's some templates there. We got some Donut Core stuff, some unit test projects you can create an ASP.NET Core web app if you like. It uses an OOP API, but what's helpful with that is if you understand that API, you could kind of work with that with F-Sharp object programming and then slowly sort of work in functional things as you go. You know, the Donut standard class libraries, all of this just comes directly from the Donut Core installation. And so to prove that, I'm on my Mac here. I've been on the Mac the whole time. Tricks me. Yeah, I know. I have my repo here. So we'll go .NET-dash-info, there's a bunch of stuff gonna come out here, but I have SDK version 2.1.402. It's the 400 plus family that has F-Sharp there. So we'll go .NET new console-ling F-Sharp-O FS45 demo. So this is the same thing that you would do in C-Sharp. So I can go inside of there and then I'll open up Visual Studio Code. So Visual Studio Code here, I've installed a few plugins. Most importantly, I've installed the Ionite F-Sharp plugin. You can see it has over a million downloads. This is actually a 100% community thing, but I've been saying it's really, it's the official F-Sharp plugin for Visual Studio Code. The F-Sharp community has basically been building this out and it is updated like crazy all the time. I definitely recommend using it. And so there's some pretty neat stuff there. You can get a little tree view with this little Ionite icon that you got there. And so I got my program .fs. I can do kind of whatever I want here. Let's go ahead and take this by ref returns call, or sorry, code, and just stick that in there. And then we'll run it. It's gonna .net run, there we go. And we have that same output of 137.15, 137.30. So like this, it has F-Sharp 4.5 in there. It's just the .net SDK, just .net new, whatever you got everything you need in there. And so this, I definitely recommend Ionite. It's really, really great. It's got good tooling, that sort of stuff I can control. So if you're developing, if you're developing on a cross-platform, if your development is on a Mac, then this is exactly what you need to get started with F-Sharp 4.5. Oh yeah, this is definitely one of the easiest ways to do it. But if you're developing using Visual Studio on Windows, you're still writing cross-platform apps, right? So the running of them can go run anywhere. So you can choose your development environment and choose your target. Yeah, you can do whatever you like. If you're on a Windows machine, you can use Visual Studio. If you're on a Mac, you can use VS Code. You can use Visual Studio for Mac. If you're on Linux, you can use VS Code. Just like C-Sharp. Yeah, exactly. Okay, cool. You can do it basically everywhere. And so we have a lot of Swipey Swipey going on here. So we're like running a little bit out of time here, but I want to get some questions, though. There is a question, there's a good question right here that I wanted to address here. So has the process of making type providers been worked on to make it an easier process? That's probably my favorite feature of F-Sharp. Making type providers. No, because type providers are an inherently difficult thing to create of your own. Now, using type providers is easy, but creating a custom type provider of your own is difficult. It's basically building a compiler plugin for an arbitrary data source. So it's, well, let's take that back. Well, if this person has any suggestions, you can go talk about it. It is slightly easier. The type provider SDK has been improved. And you can use .NET standard components and stuff so you can have them run on .NET Core. But fundamentally, it is sort of a difficult space. And so that's kind of more for advanced F-Sharp programmers. Okay, got it. Any plans for supporting protected members? It looks like somebody said, nope, right there. No, definitely not. That encourages inheritance hierarchies, which we avoid in F-Sharp. You can do inheritance hierarchies, but we really, really don't want people to be doing that. Cool, well, great presentation, Philip. Looks like we want to see more training material done by you. All right, so you gave us a great resource to get started. A couple places to go, guys, right here. There was a question earlier on how to watch all of these sessions. Like you can go to our YouTube Visual Studio channel, or you can go back to channel nine and you can go watch shows. Anything like that? So we want to tell the community here about F-Sharp, like get started and start using four or five. Get started, get started, give it a shot. Just create a basic console app says Hello World. If you're not an F-Sharp programmer, just try it out. It's a lot of fun. The syntax is vastly different from C-Sharp, as you can clearly tell, but that difference in syntax really, really grows on you, especially as you start using different types that, so like an example that we have on the website here. See if I can blow this up. Creating a type and pattern matching like this in C-Sharp is like an order of magnitude more code than it would be in F-Sharp. So if you value that sort of stuff, you want to give it a go, sort of see what different programming is like, then I highly recommend checking it out. And because it's all running with .NET Core and available in Visual Studio and basically any tool you like, it's really easy to get started with. All right, well, thanks very much, Phillip. It was awesome. I learned a lot today. Hope you guys did too. And we're going to go off to the next session there. So thanks a lot. All right.