Welcome everyone! This video may give you memories of one video I posted last year. That video, located here: https://youtu.be/CrCVdE2dUQ8, was about this live session. For different reasons, I wasn't able to post this video on my channel earlier and finally got a confirmation from the meetup organizers that I could go ahead and post this video on my channel. They kindly allowed me to post this video to which I am very thankful for. In this video there are some very slight sound improvements, but for the rest the video is in its raw form. Barely no editing and certaintly no video splicing during my presentation. The video is only the section where I present, where I talk about the mysteries surrounding Kotlin, which, are no mysteries at all, once you know what they are and how to work with them. Coming from a Java world, Kotlin may sound and feel an upgrade, but Kotlin is something else that can feel bizarre and there could be many questions about that. This presentation is about just that. Check further below in the description for the slides and further information about this video and make sure to watch the video of last year on the link above, should you have more questions about this presentation. I hope you enjoy the live version and until the next video be sure to stay tech, keep programming, be kind and have a good one everyone!
---
Chapters:
00:00:00 Start
00:00:30 ERROR FIX Filmed at JetBrains HQ Amsterdam by the NLKUG crew and kindly shared to me by NLKUG This is only my part of the Metup.
00:00:36 Introduction
00:01:45 Nullability
00:05:52 Inline and Crossinline
00:10:36 Tail call optimization
00:14:22 Data classes and Frameworks
00:19:13 Delegates and other use-site targets
00:24:24 What's next
00:25:04 Q & A
00:32:33 Thank you
00:32:48 Resources
00:32:53 Source Code
00:34:17 End Credits
00:34:31 Disclaimer
---
Source code:
- https://github.com/jesperancinha/kotlin-mysteries
---
More info and slides:
- https://www.scribd.com/presentation/726367924/Decoding-Kotlin-Your-Guide-to-Solving-the-Mysterious-in-Kotlin
- https://www.slideshare.net/slideshow/decoding-kotlin-your-guide-to-solving-the-mysterious-in-kotlinpptx/267506251
- https://www.meetup.com/dutch-kotlin-user-group/events/300229414/
- https://hackernoon.com/solving-the-mysterious-koitlin-how-to-decode-kotlin
---
Soundtrack:
- https://soundcloud.com/joaoesperancinha/slow-guitar-1-jesprotech-1
---
Related videos:
- https://youtu.be/AiP2_icXpAk
- https://youtu.be/e980a9RRHmU
- https://youtu.be/Nu4py7xpf0k
- https://youtu.be/oPGWHVsf-j0
- https://youtu.be/ZTuXgNE61Xg
- https://youtu.be/w4kkcz1gJkQ
- https://www.youtube.com/watch?v=S3k6C1XaYr8
- https://www.youtube.com/watch?v=0MJartdpoT4
- https://youtube.com/shorts/wqL_1imGhaY?feature=share
- https://youtube.com/shorts/JBWEJaxlVYY?feature=share
- https://youtu.be/CQXIuyxMD_I
- https://youtube.com/shorts/VIITIP4-WWU?feature=share
- https://youtube.com/shorts/vr8lVaF4EQw?fea
---
Chapters:
00:00:00 Start
00:00:30 ERROR FIX Filmed at JetBrains HQ Amsterdam by the NLKUG crew and kindly shared to me by NLKUG This is only my part of the Metup.
00:00:36 Introduction
00:01:45 Nullability
00:05:52 Inline and Crossinline
00:10:36 Tail call optimization
00:14:22 Data classes and Frameworks
00:19:13 Delegates and other use-site targets
00:24:24 What's next
00:25:04 Q & A
00:32:33 Thank you
00:32:48 Resources
00:32:53 Source Code
00:34:17 End Credits
00:34:31 Disclaimer
---
Source code:
- https://github.com/jesperancinha/kotlin-mysteries
---
More info and slides:
- https://www.scribd.com/presentation/726367924/Decoding-Kotlin-Your-Guide-to-Solving-the-Mysterious-in-Kotlin
- https://www.slideshare.net/slideshow/decoding-kotlin-your-guide-to-solving-the-mysterious-in-kotlinpptx/267506251
- https://www.meetup.com/dutch-kotlin-user-group/events/300229414/
- https://hackernoon.com/solving-the-mysterious-koitlin-how-to-decode-kotlin
---
Soundtrack:
- https://soundcloud.com/joaoesperancinha/slow-guitar-1-jesprotech-1
---
Related videos:
- https://youtu.be/AiP2_icXpAk
- https://youtu.be/e980a9RRHmU
- https://youtu.be/Nu4py7xpf0k
- https://youtu.be/oPGWHVsf-j0
- https://youtu.be/ZTuXgNE61Xg
- https://youtu.be/w4kkcz1gJkQ
- https://www.youtube.com/watch?v=S3k6C1XaYr8
- https://www.youtube.com/watch?v=0MJartdpoT4
- https://youtube.com/shorts/wqL_1imGhaY?feature=share
- https://youtube.com/shorts/JBWEJaxlVYY?feature=share
- https://youtu.be/CQXIuyxMD_I
- https://youtube.com/shorts/VIITIP4-WWU?feature=share
- https://youtube.com/shorts/vr8lVaF4EQw?fea
Category
🤖
TechTranscript
00:00Thank you for listening.
00:30I think...
00:39Okay, everybody, so please give a round of applause to João.
00:44So, yeah, hello everyone, so I'm going to give a presentation about five different issues
00:56that I found while working in Kotlin at work, actually, and things that led to a bit of
01:03confusion and less productivity at work just because we didn't know what was going on.
01:11So, I will talk about five different topics.
01:15One is about nullability.
01:17The other one is about inline and cross-inline.
01:20Then about tail call optimization in Kotlin.
01:24And then data classes.
01:27This was a real headache at work when working especially with the Spring framework and with
01:34the annotations that it provides.
01:36And finally, something about the delegates and how can that work with annotations.
01:43So, the first thing is that Kotlin promises a guarantee of null safety and another thing
01:50is that although we can use nullable members in our classes, we really shouldn't whenever
01:57possible.
01:58So, we should stick to using immutable data whenever possible.
02:02That sometimes is not compatible with our requirements at work.
02:10But what we have found is a peculiar thing about Kotlin, and I will explain now with this example.
02:18So, I have on GitHub a repository that has all the code that we can use to test this.
02:25And in it, I have created a database using a Flyway script.
02:29So, when you start Spring Boot, it will run this.
02:31It is a simple table.
02:33And what I want you all to focus right at the beginning is that all of these columns
02:39that I am creating are not...
02:46are nullable.
02:48Sorry.
02:50And what that means is that we can...
02:55we can put null values in them.
02:59And this is something that we see frequently in production.
03:02And now we want to apply code to it.
03:04We want to program this in Kotlin.
03:06So, usually we do this.
03:08We create another class.
03:09We created an entity for it.
03:11And this is, of course, considering the repositories and all the things that we need to get the data to it.
03:17And here we do all this mapping.
03:21And this works fine.
03:24And another thing that I've done in the code is insert two car parts.
03:31One...
03:32Yeah, the database is about car parts.
03:34The whole project is about car parts, by the way.
03:38And I'm inserting two different car parts.
03:41One is just a screw.
03:42And the other one is a car part without a name.
03:46It's just a null value.
03:47So, I put that in the database.
03:49And then I created this test.
03:55And this is specifically for the repository.
03:58But this...
03:59What do you think that will happen in this test?
04:02Hmm?
04:04A compile error.
04:06Anyone has a different opinion?
04:08A warning?
04:09A warning?
04:10Yeah.
04:17Yeah.
04:18It is indeed a noble, a useless check.
04:20Oh, yeah.
04:21I didn't say this before.
04:22Every single one of these examples, I have a practical example that I want to make.
04:25But it will take a lot of time.
04:27So, when I'm done with the theoretical part, if there's time, then I will do the practical part to see this in action.
04:34But this test will run without a single problem.
04:37We will get a warning in IntelliJ that it's useless, that it's not doing anything.
04:43But this will work.
04:45The name, which is supposed to be not nullable, gets a null value.
04:51And we can see this with another simple example.
04:54Because I'm trying to make a point here.
04:57And it is this example, where I'm specifically using reflection to set a null value to this car part DTO.
05:06So, now here, I'm separately without using spring or anything like that.
05:10And I'm just saying, hey, I want to get the field.
05:15And then in this field, I'm going to set a null.
05:19And there's a difference between what I'm saying.
05:21I'm saying set and I'm not saying assigning.
05:23Because null safety does protect us from assigning a null value.
05:28But this is a set.
05:30So, we are setting a null value in something that is not supposed to be nullable.
05:36And so, this code also works.
05:41So, this created problems for us because we were getting nulls and we were just relying on the null safety that Kotlin provides.
05:49So, this is the first example of something that caused issues for us at work.
05:56Second example is inline and crossing line, which can be used in combination with each other.
06:01Inline, I don't know who knows what inline is.
06:05Yeah.
06:06Lots of people.
06:08And we all know what crossing line is.
06:11Less people.
06:13Yeah.
06:14You know.
06:15Yeah.
06:16So, I hope I also know because I think Italian people right over here know a lot more than I do.
06:26So, this created also a bit of confusion because we were using crossing line just to make the code compile, but not really understanding what it actually is about.
06:34And so, why does this even matter?
06:36So, I created a few examples to exemplify this.
06:39This is the first one.
06:40And here, I'm just simply creating a higher order function called call engine crossing line.
06:46This basically is analogous to just starting a car.
06:49And this call engine crossing line makes these print lines and it's being passed on here.
06:58I'm not sure if you can see the arrow.
07:00Yeah, you can.
07:01Okay.
07:02But I'm not using it anywhere inside this code and I'm returning here.
07:06I'm doing here a local return to introduction.
07:09And nothing, the compiler isn't complaining.
07:14IntelliJ is not complaining about this.
07:16And we can do this very simply.
07:18And this, if we check on the decompiled code, we can see that the call engine crossing line has been inlined.
07:31And there's a call to a function in it, which uses the function that we passed through.
07:40And we see that in the decompilation, we also get this invoke with what we have put in the call for introduction.
07:50So, nothing special here.
07:53But then I made another example where I'm calling this start manual inside of it.
07:59And IntelliJ forces us to use cross inline.
08:02And without it, it doesn't compile.
08:07But the thing is, we are forced to use cross inline.
08:13The code doesn't compile anyways without cross inline.
08:16So, why do we need to put cross inline?
08:19Why does this affect the code and couldn't IntelliJ just or the compiler figure this out on its own?
08:26Well, in this case, we can argue that.
08:31Because we will never be able to make a non-local return, which is actually what the cross inline is about.
08:37So, we can see here that I'm returning to introduction.
08:40This is a local return.
08:42A non-local return would be return loop, for example, in this case.
08:47Or return call engine crossing line.
08:50This will be a non-local return.
08:51What crossing line is doing is saying to the compiler,
08:54those returns are not possible.
08:57But yeah, here we might say that it's not useful.
09:02But if we follow this example, this other example that I've created,
09:08this is a bit different.
09:10It's just like going to a store, making a purchase, and then leaving.
09:14And I created here a goToStoreInline function.
09:20And with it, the compiler is letting me return to main, which is a non-local return.
09:30But if I return to main, I never leave the store, because this print never happens.
09:36And if we check the decompiled code, we see that this instruction never gets to the code.
09:42So what crossing line does to us is making sure that we only make local returns.
09:49So this means that we can kind of protect our code from making the wrong thing.
09:55And so the decompiled code now does have walkout because, of course, we return a goToStore.
10:05But what crossing line actually does for us that is very useful is two things.
10:11It makes the code more readable, and it protects us from making mistakes.
10:18But we are developers, and there are some developers that will just remove crossing line
10:22and do something just to make the code compile.
10:25And maybe in that case, it will not work.
10:27But that is the goal of this crossing line.
10:30And I can't tell you how many times we've been putting crossing line and removing it.
10:35Anyways.
10:37Then there's this tail call optimization.
10:42So this is something that has been created since the late 50s.
10:46And it was introduced in programming languages in the mid 70s.
10:50And the idea is that we can optimize tail recursive functions into something else that works better.
10:59And that is a transformation from our code into the bytecode that can occur with tail recursive functions.
11:08So what is the catch?
11:10Well, the thing is that I will show this in this example.
11:16So in this example, I have created data classes that represent the car, a complex part, and a part.
11:24And all of these different data classes contain the weight.
11:30And the recursive function that I created will sum all the weights and just give us...
11:37The only goal is to give us the weight of the car.
11:41And so the way this function is implemented, maybe we could have implemented differently,
11:48but just for the example, is that it is a tail recursive function,
11:52because the last call to the recursive function is the original call to the function.
11:57And so it's a bit difficult maybe to explain, but this total weight here is...
12:04When we call this, we are calling this function.
12:06When we're calling this, we're calling this function.
12:08So it is still recursive because of that.
12:10And when we apply tailrack, we are essentially just saying to the compiler
12:14that this function is still recursive, and it's up to the compiler to decide
12:18how it is going to transform this in the bytecode to make it more efficient
12:25and to make it eventually faster.
12:28But also to prevent one thing, which is stack overflow.
12:33Because the idea is to...
12:36I don't know if it happens in all different kinds of optimizations,
12:39but in most of the optimizations that I've seen, what it does is just transform
12:43the recursive calls into iterative calls, which run no risk of throwing a stack overflow exception.
12:50And there's another curious thing about using tail recursive functions,
12:54is that we can use immutable variables.
12:58And so we don't need to rely on a mutable variable to add values to it.
13:05And this is probably better explained in this bit where I do the decompilation.
13:11And in this case, we can see that what happened in the transformation
13:16is that it transformed that function into an endless while loop
13:21that will return once the list of parts is empty.
13:26And it has an accumulator that will, in every iteration, have a value added.
13:32So because there's only one stack frame, this will never generate a stack overflow exception.
13:38And you can see that the code is horrendous when we decompile it from the bytecode.
13:44But this is something that if we would do this in Kotlin or in Java,
13:48we'd probably be enormously criticized for it.
13:51But it is highly efficient.
13:54So tailrack, what does for us, and where it became clear,
14:00is when we realized that this was just transforming a beautiful code
14:03into an efficient code underwater, and the code just becomes more readable.
14:08But of course, without tailrack, this would be a very inefficient code.
14:13Not this one, but the one I showed before.
14:16You get the point there.
14:17Okay, so the other thing, and this was the biggest headache that we've had in our project,
14:24was working with data classes and frameworks.
14:27In this case, the Spring Framework.
14:29Which one of you has worked with Spring Framework?
14:34Yeah, almost everybody. Quarkus?
14:37Nice.
14:39And Ktor.
14:42Okay, whoever worked with Ktor will never find this problem, I think.
14:46So, yeah, I'm not commercializing it.
14:50But it is true because I think Ktor doesn't use annotations?
14:55Or uses...
14:58No, yeah.
15:01Depends for what.
15:02Okay, then maybe.
15:04But in any case, the Spring Framework is something that's used widely today,
15:09and what I'm about to show was a bit of a headache.
15:14So, here we have an example.
15:16This is an entity that I've created with some validations on it.
15:19So, I'm saying not null for some reason.
15:22It's not necessary in Kotlin.
15:24But it's here just for the example.
15:26And there's size and there's min.
15:28Who thinks that this table will work?
15:36Okay, nobody thinks that this will work.
15:38You think it will work?
15:41It should.
15:42Yeah.
15:43Yeah.
15:44With plugins?
15:45Yeah.
15:46Yeah.
15:47With plugins?
15:48Yeah.
15:49Yeah, okay.
15:50With the Spring plugins and that.
15:51Okay.
15:52It will not work even with plugins, because there is something which is use site targets.
16:07And in Java, what we used to do with annotations was just apply them physically in different locations in a method or in a field or in a property, because we would have that.
16:26But in Kotlin, in data classes, you only have class members.
16:30So, if I say here, apply size to name, where is that?
16:34Is that in the constructor?
16:36Is that in fields?
16:37Where is that?
16:39So, Kotlin does have a rule for this.
16:43And I will show that in a minute.
16:46But what I did for this to work was to add here field.
16:51And the reason why this now works is because of rules that Kotlin has.
17:00And in my experience, the problem is that we were just constantly Googling this stuff and going to Stack Overflow to all the wrong places to see this.
17:09We didn't come across the documentation with Kotlin, which says that if you don't specify a use site target, the target is chosen according to the target annotation of the annotation being applied.
17:20If there are multiple applicable targets, the first applicable target from the following list is used.
17:27So, if we have a look at one of these annotations where I had to put fields, we can see that they are targeted by method, field, annotate type, constructor, parameter, and type use.
17:38So, which one will be chosen by Kotlin?
17:42Which one do you think it will be chosen by Kotlin in this case to be applied to?
17:48Parameter.
17:49Yeah, it will apply to parameter.
17:52And the thing is that the Spring framework uses AOP to apply validations.
18:00And it doesn't provide good support for parameters.
18:04And because of that, this gets applied to a parameter.
18:08And we can see here in the decompiled code that min and size are now applied as a parameter for the constructor, and this will never be used.
18:18And so, the validation will never work.
18:20And if you're working in a kind of project where unit tests are less relevant or something like that, then you will not see this.
18:30And when you realize it, then it's a problem because where does the error come from?
18:36And so, when we apply this field prefix, this use side target, before the annotation, then we are forcing Kotlin to choose the field of the class to apply this annotation.
18:54So, all of that work.
18:58And actually, this was so simple.
19:01And this works for all the other kind of use side targets that we can choose to different kind of annotations.
19:08In this case, I'm just using validation, but this could be for anything else.
19:11And then, finally, something special that I've learned with delegation, and that is because in Kotlin, one of the use side targets is delegation.
19:26And we try to use delegation in our code, but it wasn't really working.
19:33It wasn't doing what we wanted to do, and I wanted to have an example to figure out how the delegation use side target actually works.
19:42Does it work on delegates?
19:45How does it actually work?
19:48And so, I've created an example, in this case with horns, but I've created an interface horn with a car horn and a wagon horn.
19:56I'm not going to pronounce those sounds, but it's kind of just for the example.
20:00And I've created annotations for the example, and I've created a sound delegate with only a get value.
20:09And then, I've applied these annotations with a delegate use side target on the definition of these fields, which are being created using a delegate.
20:26And so, where do you think that this delegate annotation will be applied to?
20:36Will it be on horn or the sound delegate?
20:44Because we have here, we are creating wagon horn.
20:46This is actually a wagon horn, but it's by sound delegate, so it comes from the sound delegate.
20:52Where do you think this would be applied?
21:00Yeah.
21:02Yeah, it's kind of obvious.
21:03In this case, it's kind of obvious because, yeah, and then we see it here, yeah, that it applies to sound delegate, as we can see here.
21:13And this is kind of, in this case, it's obvious.
21:17It's just a bit of a warm exercise.
21:21And we can see, but we can see also another thing, which is the get wagon horn is here as well.
21:25And so, this was interesting to me because I've also found other people on other forums that kind of got stuck here.
21:35And this was a very strange result to have.
21:40And so, I continued and I created another example.
21:44And this example is a part name DTO and an impossible part name DTO.
21:50If we look at the impossible part name DTO, we are applying two delegates and we are saying they're not blank and the size must be 12.
22:00But when we do that, what happens is that we are applying the not blank and size to a sanitized name delegate.
22:12Maybe I could have chosen a better name for the delegate.
22:16And what happens here is that how would this work when we are serializing data this way?
22:24It won't work because not blank and size, these are things that have to be applied to string.
22:32So, we will get an error here about the mapping not working correctly.
22:36This one here does it well and it applies these validations to the actual getter of the value that we are trying to put out via the delegate.
22:52And so, with these two information, I've created this example.
22:57And this is something that maybe we shouldn't do in Spring, but I did it anyways.
23:03It is just creating a delegate, creating this field current date using the delegate inside a service and then injecting the service.
23:14The reason is because I want Spring to apply its so-called magic and that I could see the result of what is happening.
23:23And what I saw here is that I have created a delegate.
23:28I created a local date time validator constraint.
23:33It is a manual constraint that I can apply with delegate.
23:41And one thing here that I want to mention, this delegate site target apparently is only applicable when we have the target field applied to our annotation, which does make sense.
23:55And so, what happens here is this result, which is the delegate has this local date time validator constraint and the date has to be in the past.
24:11So, we have two validations working together.
24:14One is going to validate the delegate itself and the other is going to validate just the date.
24:24And so, oh, not yet.
24:25Not yet.
24:26Not yet.
24:27And so, yeah, I can't change it like that.
24:30But I think we have time still for the examples or not.
24:3310 minutes.
24:36Yeah.
24:37I will, yeah, I will start from the, yeah, which one do you want to see as an example?
24:47Anyone?
24:48If I have a benchmark on the options, it's very good.
24:53If I have a benchmark, I have, no, I don't have a benchmark.
24:58We're not ready for now.
25:03I don't have it for this session.
25:06But, yeah, because you want to see the stack overflow being generated.
25:11Or...
25:12So, if you were buying it, it could be not more time, but like how much more off the time you have to wait?
25:22Oh, the most important thing is not so much the optimization because it's already tail recursive.
25:28So, it's already very efficient on its own.
25:31The best thing that you have with the tail call optimization is actually the avoidance of stack overflow.
25:39Because there's no stack frames.
25:41So, time and space is pretty good already with tail recursivity.
25:45It's just that you don't generate different stacks in one inside the other, inside the other, inside the other.
25:51So, at some point, it just blows up.
25:53That's what we are trying to avoid with tail rec.
25:56So, I did mention efficiency.
25:58It can be a part of it, but it's not the actual goal.
26:02Yeah.
26:03So, the question remains.
26:05Anyone wants to see...
26:07Tail call optimization?
26:08Yeah.
26:09Tail call optimization?
26:10Yeah.
26:11Tail call optimization?
26:12Yeah.
26:14Tail call optimization?
26:16Yeah.
26:17It will fail.
26:32Tail rec can be used.
26:34Huh?
26:36Tail and compile time.
26:38So indeed, there's another benefit to using it.
26:43It will tell you if your function is applicable,
26:47if your function is still recursive.
26:49And then the compiler will do the rest.
26:51It will check if it can optimize it, how it can optimize it.
26:56But that process, I think, people from JetBrains
26:58can tell you better than I can because I
27:01don't know the details of that.
27:03But yeah.
27:04The compiler cannot figure that out by itself.
27:12It needs to have the tail.
27:15You need to apply it first.
27:17And then it will see if your function is applicable or not.
27:23So IntelliJ will immediately tell you this is not tail recursive.
27:31And then the rest is up to the compiler.
27:33I think I'll try to answer it on behalf of IntelliJ,
27:47because I think this is what it is about.
27:50In many languages before Kotlin and I think Scala as well,
27:53but then I'm not entirely sure, you didn't have the choice.
27:56It was automatic for you.
27:58Kotlin, yeah.
28:01I think so.
28:01Not tailrack, but the tail call optimization.
28:05But you can decide if you want that or not.
28:09So if you have concerns about what is going to happen in the bytecode,
28:14you can decide not to use it.
28:16You can decide not to use it.
28:17That's why, yeah.
28:18Yeah.
28:19Yeah.
28:20Yeah.
28:22Yeah.
28:23Yeah.
28:25Yeah.
28:26Yeah.
28:27And doesn't require that we can recognize
28:29that we can decide if it doesn't matter.
28:30Yeah.
28:31That we can decide if we can do something.
28:34Yeah.
28:38And .
28:41You might want to see if something happens.
28:46Some of the ..
28:51You might want to see ..
28:55Yeah.
28:58But .
29:02Yeah.
29:04Yeah, so I want to show something else.
29:26I think I've, did I stop the, oh yeah.
29:33How did I do that?
29:34Let me go back to it, this one, and then, yeah,
30:03but I, I think I closed the, the slide one down.
30:13One second, sorry for this.
30:14Yeah.
30:26So, yeah, yes, yeah, yeah, yeah.
30:53Yeah.
30:56Okay, so what I wanted to do with this presentation was to help understand better the Kotlin language and avoid colleagues of mine not to go through these problems, which they sound, they now sound very basic to me.
31:11But when I go back to that time, they were really annoying and difficult to solve just because we didn't know better.
31:18And maybe because we need to think more about important things like not fighting the Spring framework or Quarkus.
31:27People usually say that they are evil and they are magic.
31:30I don't think they are.
31:31I do think that Ktor is a great development.
31:34So, but a lot of the things that we call magic in Spring are not really magic, we just don't know better.
31:42And sometimes we try to find the information in the wrong places, like, for example, for Kotlin, I found out for myself that it's much better to go straight to Kotlin Lang instead of going to Google or something like that, because we are going to get a lot of tips and advices.
31:56But people are going to say, just use field and it works.
32:01And then if you run into another problem, you will apply field and it just won't work.
32:07Nothing is perfect.
32:09And I think that Kotlin falls also in that category.
32:12Sorry, JetBrains.
32:13But yeah, but we have seen already that there are constant developments in Kotlin.
32:18And I think I am pretty much a fan of Kotlin.
32:21I do like to criticize it also, but I think that's what keeps my interest on it.
32:28And so that's a bit of the message I want to bring with this presentation.
32:33So thank you.
32:35And I have made the repo with all these examples is available on GitHub.
32:46So it is questions.
32:49And these are the resources that I used.
32:52And the repo, I put this image on it.
32:55And it's essentially with my user handle.
33:00So I don't know how to pronounce this even in English.
33:04JetBrainsinga, something like that.
33:06And then slash KotlinMystery.
33:07So there are all of these examples that you can use to test these things out.
33:14And that's the end of the video.
33:25Yeah.
33:26So thank you so much, João.
33:27Guys, we don't really have time for questions now.
33:29We're going to have a break of five or ten minutes.
33:32So you can go to the toilet, grab another beer.
33:35And when you come back, we're going to have the talk from Yerun.
33:39And you can also connect with João and Yolanda and Yerun as well outside while we wait for the...
33:45Not with Yerun, actually, because he's going to be setting up the thing.
33:47Sorry.
33:48Yeah.
33:49And also, João didn't say it, but he has a YouTube channel with a lot of great videos about Kotlin.
33:54Around 100 videos, right?
33:56That's how I came across him.
33:57And that's why I invited him today.
33:59So if you guys are interested, you can also check him up on YouTube.
34:03Yeah, that's my part.
34:08That's why I'm doing it.
34:09Yeah.
34:10Okay.
34:12And see you all in five to ten minutes.
34:15I just want to say thank you to JetBrains and the Dutch Kotlin User Group
34:20for allowing me to share this video on my channel.
34:23And until the next video, be sure to stay tech, keep programming, be kind and have a good one.
34:30Bye.
34:31I've updated my disclaimer to better reflect my position.
34:34This video is not sponsored and I have not received financial compensation for its content.
34:39Any brands, products or companies mentioned are for informational purposes only.
34:44While I may participate in various programs, meetups and conferences, all opinions expressed here are my own.
34:51And the next video, I will see you in the next video.
34:52The next video are gonna be sleeping, I'll see you in the next video.
34:53Am I?
34:54Am I?
34:55Am I?
34:56Am I?
34:57Am I?
34:58Am I?
34:59Am I?
35:00Am I?
35:01Am I?
35:02Am I?
35:15Am I?