[00:00.000 --> 00:19.520] Thank you, everyone. It's so great being here. So I'm Simon and I'm a developer, a tech [00:19.520 --> 00:25.960] lead engineer at CBA Functional. And a little bit about myself. I've been doing Kotlin since [00:25.960 --> 00:33.760] 2015. I've been doing functional programming a bit longer than that. And, well, I'm really [00:33.760 --> 00:41.400] in love with both things. So we try to improve things as much as we can. And I'm also one [00:41.400 --> 00:48.880] of the lead maintainers of Aero, which is a functional library in Kotlin. So today I [00:48.880 --> 00:53.720] want to talk a little bit about functional programming in Kotlin. And there's three big [00:53.720 --> 01:00.240] topics that we often talk about, which are dependency injection, side effects, and typed [01:00.240 --> 01:06.640] errors. But I don't want to go into this comparing it to different languages like Haskell or [01:06.640 --> 01:13.600] Scala, because I only have 20 minutes of time, so I need to kind of, you know, pick my topics. [01:13.600 --> 01:19.280] So I want to talk about these three things. And I will maybe sometimes refer to some other [01:19.280 --> 01:26.480] things from other languages, but if you do not follow or know them, it's not really important. [01:26.480 --> 01:30.920] So first of all, dependency injection. I'm also not going to go into why we do dependency [01:30.920 --> 01:35.720] injection, but often when we are writing programs, you might have something like this where you [01:35.720 --> 01:40.000] have a database, where you can run some queries, you have a logger, where you can log some [01:40.000 --> 01:45.760] stuff. And then you need to now write a program that uses these two things to build some logic. [01:45.760 --> 01:51.560] So the most vanilla function that we can probably write in Kotlin might look something [01:51.560 --> 01:57.840] like this, where you have a fetch user function, it takes in an ID, it takes in both dependencies, [01:57.840 --> 02:02.560] we run some query, we make a log statement, and we return a value, right? So this is the [02:02.560 --> 02:11.240] most vanilla, pure functional signature function that we can write in Kotlin. But it's not [02:11.280 --> 02:16.080] really that great because if you want to write some other code using this function, we need [02:16.080 --> 02:22.280] to always wire and pass these parameters manually around. So you will see in your code that [02:22.280 --> 02:27.200] after a while, you're always manually passing all these parameters around and wiring all [02:27.200 --> 02:31.720] this stuff. It causes quite some boilerplate. And typically, it's not very interesting to [02:31.720 --> 02:35.840] read because we are really only interested in fetching the user with the ID, and all [02:35.840 --> 02:46.800] their stuff might be a little bit side tracking or making our code more complex. So for mentioning [02:46.800 --> 02:51.960] all the different kinds of techniques that you can do dependency injection I wanted to [02:51.960 --> 02:56.120] use or include this version is not the one that I'm going to recommend, but I'm going [02:56.120 --> 03:00.680] to cover it anyway. So for those that are not familiar with this pattern, you can write [03:00.680 --> 03:07.200] an extension function fetch user on a generic type that we call context. You can answer the [03:07.200 --> 03:16.920] context needs to extend both the database and the logger. And then inside of the method [03:16.920 --> 03:22.760] body, you again get access to the database methods and the logger methods. You can write [03:22.760 --> 03:29.120] the same function as well as before. But this is quite complex to read. It's a pattern that's [03:29.160 --> 03:34.480] probably very foreign to most people. And to actually call this method, you now have to [03:34.480 --> 03:41.720] create a type that extends both database and logger. So this is probably not a very ideal [03:41.720 --> 03:49.720] pattern to use to solve this problem. The cool thing, however, is that we can now define [03:49.720 --> 03:55.000] again the new function. And as you can see within the map function of the list, you don't [03:55.000 --> 04:01.600] have to pass the parameters around, but we have to define again an extension function [04:01.600 --> 04:09.720] on context which is constrained to having a database and a logger as a super type. So [04:09.720 --> 04:15.160] this is a bit complex. This is the solution that I do not recommend, but it works in Cullen [04:15.160 --> 04:24.280] today. So as you might have guessed, a really neat solution that is an upcoming feature [04:24.360 --> 04:30.560] in Cullen is called context receivers. And you can now annotate or mark your function [04:30.560 --> 04:36.720] with the context keyword. And you can say to call this function the context of the database [04:36.720 --> 04:41.520] and the logger need to be available. And you can only call this function with both of these [04:41.520 --> 04:48.600] types are available. And again, inside of the method body, that gives you access to the [04:48.600 --> 04:53.100] functions of the database and the logger because you have constrained your function to say this [04:53.140 --> 04:57.780] function can only be called when these types are available. So we get access to the query [04:57.780 --> 05:05.780] method and the log method. And to call this method or to make the context of the database [05:05.780 --> 05:09.980] and the logger concrete, we need to at some point in your program say, okay, I'm going [05:09.980 --> 05:14.420] to call this function with this instance of the database and with this instance of the [05:14.420 --> 05:20.980] logger. And as you can see here, this is valid Cullen in the current 1-8-0 and even in the [05:21.020 --> 05:26.740] 1-7 releases, this is valid Cullen, this is code you can write and compile today, albeit [05:26.740 --> 05:32.420] only for the GVM and you need to explicitly opt into the experimental features of context [05:32.420 --> 05:39.420] receivers. But this is a very neat solution because we didn't have to do this type dance [05:40.620 --> 05:46.020] with the extension function and the where constraints. We don't have to pass the parameters [05:46.060 --> 05:52.820] of the fetch user function, this is done automatically for us by the compiler. And this allows us [05:52.820 --> 05:59.820] to, in a very neat way, do dependency injection using context receivers. And of course, what [06:00.460 --> 06:05.020] we love about functional programming is we want everything to be available in the function [06:05.020 --> 06:09.700] signature. So here you can very clearly see in the function signature that the database [06:09.700 --> 06:16.700] and the logger are required for calling the fetch user function. So a different very hot [06:17.860 --> 06:24.220] topic within functional programming is side effects. So typically, in talking about functional [06:24.220 --> 06:28.340] program, you often say you don't want to do side effects, but we typically have to write [06:28.340 --> 06:34.100] side effects to call or write useful programs like we have to log something to the system, [06:34.100 --> 06:38.620] to the console or to a server or we need to call the database to, you know, interact [06:38.660 --> 06:44.220] with external systems. So we need side effects to write our programs, but what do we want [06:44.220 --> 06:50.380] to do? We want to track the effects to compile time, which means that you should not be allowed [06:50.380 --> 06:56.380] to nearly really call side effects wherever in your program without knowing about it. [06:56.380 --> 07:01.220] You want side effects to be composable in a safe manner and that allows you to reason [07:01.220 --> 07:07.980] about your code in a more clear way. Or it allows you to reason about where side effects [07:08.020 --> 07:15.020] happen in your program. So let's again take our two dependencies of the database and logger [07:16.100 --> 07:22.700] and here we are actually violating that rule because we are saying we have a regular function [07:22.700 --> 07:29.140] query and a regular function log and they are just performing some side effects underneath. [07:29.140 --> 07:35.580] So again, might have guessed we can mark these functions with suspend, which is a feature [07:35.580 --> 07:40.540] in the Kotlin language. And now these side effects can be compile time tracked. So what [07:40.540 --> 07:46.820] does that mean for these side effects to be compile time tracked? [07:46.820 --> 07:50.700] When now that we've marked these functions as suspend and we again take our previous [07:50.700 --> 07:56.500] method of fetch user, this function will no longer compile, right? So where the red lines [07:56.500 --> 08:00.620] are it will fail to compile. It will see a compiler matter saying the suspend function [08:00.620 --> 08:06.860] query and log should only be called from a coroutine or another suspend function. So [08:06.860 --> 08:11.580] since that the fetch user function was not marked as suspend, we cannot call this other [08:11.580 --> 08:17.740] suspend functions in the method body, right? So that is to say that our side effects that [08:17.740 --> 08:22.700] we now mark as suspend are now compile time tracked, right? So our compiler is now tracking [08:22.700 --> 08:27.540] for us that these functions can only be called from another coroutine or another function [08:27.540 --> 08:34.540] is marked as suspend. So simply by marking our fetch user function [08:34.540 --> 08:40.460] also as suspend, we propagate that, you know, the fetch user function is side effecting [08:40.460 --> 08:44.900] in its method body or in its function body. And now anybody that calls the fetch user [08:44.900 --> 08:50.580] function also needs to state that it performs some side effects. [08:50.620 --> 08:58.340] Some other languages, there's often used an IO type which appears in the return type [08:58.340 --> 09:04.940] and you can compare it to a callback. So for example in Java we often use callbacks for [09:04.940 --> 09:10.020] these kinds of operations, right? If you call the database often you have to provide a callback [09:10.020 --> 09:16.260] as we also saw in the previous talk and then the callback will say it will either result [09:16.260 --> 09:23.180] to the successful value of t or a failure of type trouble, right? And the suspend system [09:23.180 --> 09:27.620] does the same thing for us through a technique that is called continuation passing style. [09:27.620 --> 09:34.220] So the compiler automatically takes care of all the heavy lifting. So just to give you [09:34.220 --> 09:39.540] an example, if you would have to manually rewrite this function using callbacks and [09:39.540 --> 09:46.460] continuation, it would look like this. It's quite horrible. It's not nice to read because [09:46.460 --> 09:50.820] these are actually the only lines that we cared about. We saw that the code exploded [09:50.820 --> 09:56.180] to almost double in size. We have this nesting of callbacks. We get this tree hierarchy in [09:56.180 --> 10:01.820] our code. It's not nice to read and it really obscures about what we were actually doing [10:01.820 --> 10:08.500] in the code. So thanks to the calling compiler we get this super nice syntax and everything [10:08.500 --> 10:13.500] is extremely optimized in the runtime. So there's really not any penalties that we have [10:13.500 --> 10:19.580] to pay for this nice syntax. And you can do all this awesome stuff thanks to the calling [10:19.580 --> 10:25.580] compiler in a very efficient way. So really, really neat. And it's very similar in spirit [10:25.580 --> 10:34.580] to this context receiver that we already saw for DI. What is actually really neat about [10:34.580 --> 10:40.660] suspend in Cullen, and this is a solution that has not really been solved in any other [10:40.660 --> 10:45.940] language that I know about, is that we have this map function on our list. And the map [10:45.940 --> 10:52.660] function on our list is of course not suspending. It's a pure function. It goes over every value [10:52.660 --> 10:59.540] in the list and maps it from type A to type B. But we can call this suspend function inside [10:59.540 --> 11:05.880] as long as our fetch all function is also marked as suspend. So this constraint of side [11:05.880 --> 11:11.900] effects travels through this map function to the list. And the calling compiler is able [11:11.900 --> 11:17.740] to track where this is valid and I'm not going to go too deeply into what it is. In this [11:17.740 --> 11:23.940] case or in most cases because the map function is in line so the compiler knows that it can [11:23.940 --> 11:30.220] replace the code of the map function inside of the body of fetch all and that through [11:30.220 --> 11:36.540] this mechanism this constraint of side effecting is allowed to pass through. So the compiler [11:36.540 --> 11:42.820] takes care of all of this and we don't have to know or learn any new method names to combine [11:42.820 --> 11:48.700] suspend with any other code. So this is very neat. It allows us to combine these patterns [11:48.700 --> 11:58.060] in very elegant ways. And then another thing that we often care about in functional programming [11:58.060 --> 12:03.500] is typed errors. More specifically again we want to track at compile time what are some [12:03.500 --> 12:09.020] of the expected errors. And we will see in a little bit what I mean by expected errors. [12:09.020 --> 12:12.980] So this can be errors that you care about in your domain. Things that you can deal with [12:12.980 --> 12:17.700] that you can recover from. And you also want to track them at compile time as I mentioned [12:17.700 --> 12:26.500] before. We want all of these things to appear in our type signature. So in the case of our [12:26.500 --> 12:32.660] fetch user function we can very simply mark our user return type as nullable. So we add [12:32.660 --> 12:39.260] a question mark at the end and by that we can basically state that this fetch user function [12:39.260 --> 12:46.220] can be absent of results. So in some cases a user might not be found for the ID and in [12:46.220 --> 12:52.580] that case we can return null and this is in columnist type safe. But there is many, many [12:52.580 --> 12:59.540] more errors that we can encounter besides just saying okay there was no value available [12:59.540 --> 13:06.060] for what you were looking for. So instead of fetching a user let's try inserting a user [13:06.060 --> 13:11.580] and we're going to insert the user with a name and email. And we can now have an error [13:11.580 --> 13:17.860] that says okay this user is already available in the database. So the user already exists [13:17.860 --> 13:24.220] and we can now define an error which does not have to extend troble. So we have a simple [13:24.220 --> 13:29.740] data class that says here is the error for this name with this email address and in this [13:29.740 --> 13:37.380] case I've enhanced also the error with the underlying Postgres SQL error so that I don't [13:37.380 --> 13:42.220] induce any information because we don't want to discard any important information that [13:42.220 --> 13:47.700] you might need later on. But now we need to make this type appear in our type signature [13:47.700 --> 13:56.380] so a very traditional way of doing some functional programming is using the IDER type and you [13:56.380 --> 14:01.180] can then say there is results in either the user already exists or the valid user. But [14:01.180 --> 14:08.100] we've seen all these nice things in Kotlin. We've talked about suspend which allows us [14:08.100 --> 14:15.140] to do callback based programming without having to use callbacks. We have these context receivers [14:15.140 --> 14:21.860] that allow us to inject dependencies without having to manually wire it and don't have [14:21.860 --> 14:26.620] to explicitly pass them in the parameters of the function. I don't want to have this [14:26.620 --> 14:31.340] IDER in my return type. I want something more elegant, something that is very similar to [14:31.340 --> 14:39.780] the nullable type or the context receiver. So how can we do that? In Errol, a library [14:39.780 --> 14:47.820] that I'm working on, we have this type that we call race. And we can put the race also [14:47.820 --> 14:54.180] into the context receiver basically stating that this function has the capability of resulting [14:54.180 --> 14:59.180] in a user already exists error. And now we can see that in the return type we are simply [14:59.180 --> 15:05.780] returning a user. So we don't have a need anymore for the IDER wrapper. We are just stating [15:05.780 --> 15:10.700] in the context of our function that if you are calling this function you need to be aware [15:10.700 --> 15:18.980] that at some point a user already exists error might occur and you need to deal with it. [15:19.980 --> 15:26.580] Errol offers a bunch of very nice DSLs to, for example, wrap our query method and we [15:26.580 --> 15:34.540] can say, okay, I want to catch the Postgres SQL exception from our query statement. And [15:34.540 --> 15:38.500] in case of the Postgres SQL exception is a unique violation, so that means the user [15:38.500 --> 15:45.300] already exists in the database, I want to erase this error of the user already exists. [15:46.020 --> 15:52.820] So we can now erase this typed error into the context saying, okay, the user already exists [15:52.820 --> 15:58.780] so somebody needs to deal with that at a later point. And I'm also retrawing any other errors [15:58.780 --> 16:04.300] basically saying any other errors is something that I cannot recover from. It's something [16:04.300 --> 16:09.420] unexpected, something that you're not going to deal with at a later point in time. And [16:09.420 --> 16:16.340] what your criteria are for this are of course up to whoever writes the code or however you [16:16.340 --> 16:25.260] want to model your code. And then if you want to call this function, you can again provide [16:25.260 --> 16:29.500] the dependencies of database and logger and you can say at the edge of the world or wherever [16:29.500 --> 16:36.860] you want to need to call this function, run the effect, right? And this provides the context [16:36.860 --> 16:42.940] of arrays that we have before. And what is actually interesting here is Kotlin is able [16:42.940 --> 16:48.460] to infer all these types so we don't have to explicitly provide any type arguments because [16:48.460 --> 16:55.540] it knows the error that might occur is from the user already exists type. So here it knows [16:55.540 --> 17:00.220] that the error that we need to recover from is the user already exists. And then we can [17:00.220 --> 17:06.700] say, okay, fold over this method and either print the error or print the inserted user. [17:06.700 --> 17:12.820] Right? So this is a very simple example in the API that is available for this method which [17:12.820 --> 17:18.220] is much, much larger. And what is also neat what I really like about Kotlin is we have [17:18.220 --> 17:24.260] these special DSL sketch and arrays and they actually show up as special functions in the [17:24.260 --> 17:28.980] ID. So here the catch method and the erase method show up as pink stating that they [17:28.980 --> 17:34.820] are doing some special kind of DSL functionality that belongs to the erase capability in the [17:34.820 --> 17:42.140] context. So I mentioned the error in this last set of slides. We've typed error so what [17:42.140 --> 17:50.580] is the goal of error? It offers this DSL based functional programming style of dealing with [17:50.580 --> 17:54.780] things. And the goal of that is we want to get rid of a lot of complexity of functional [17:54.780 --> 18:02.100] programming, things like map, flat map, monotransformers, wrappers in the return types, etc. Right? [18:02.100 --> 18:07.260] And we do this so that we can provide an idiomatic Kotlin syntax for working with functional [18:07.260 --> 18:11.820] programming. And what is also really neat is it is actually Kotlin multipath from ready. [18:11.820 --> 18:16.780] So all the talks that we saw this morning, in all of those things you can also already [18:16.780 --> 18:25.340] use error in this style of programming. It offers a couple of more DSLs. For example, [18:25.340 --> 18:30.100] the Saga scope. So for people that are working with the Saga pattern on the backend, we saw [18:30.100 --> 18:36.980] that backend is an increasing industry in Kotlin. So when you're working with the Saga [18:36.980 --> 18:41.820] pattern, again, you don't want to have any return or wrappers in the return type. So [18:41.820 --> 18:46.540] we have the Saga DSL that allows you to wrap any action with a compensating action, meaning [18:46.540 --> 18:52.160] that if something goes wrong in the program, the compensating action will run compensating [18:52.160 --> 18:59.940] the action. Similarly for resource safety, we can say, okay, I'm going to install some [18:59.940 --> 19:04.660] resource and whenever you're done using the resource, you need to automatically call this [19:04.660 --> 19:10.260] release function with the log statement and the auto-closable function, which offers some [19:10.260 --> 19:18.780] special syntax for GVM functionality. So what do I love about functional programming [19:18.780 --> 19:25.380] in Kotlin? We can do it in a very elegant way using DSLs. And all of these DSLs are [19:25.380 --> 19:31.580] composable. So it means that you can nest these DSLs in a safe way. They will cooperate [19:31.580 --> 19:36.220] with each other. They will do the right thing. They're all type safe. And this offers you [19:36.220 --> 19:41.260] a very low threshold for getting into functional programming in Kotlin. You don't have to learn [19:41.260 --> 19:46.580] anything about map, flat map, special monads, monadransformers. All of these things are [19:46.580 --> 19:54.900] not needed because you can very elegantly nest and compose these DSLs together. Seems [19:54.900 --> 20:00.860] that I'm right on time. Just five minutes left for questions and thank you so much [20:00.860 --> 20:01.780] all for your attention.