• 2 months ago
Project arrow gives us a lot of out-of-the-box features that are otherwise not available in the standard kotlin library. This video is about arrow optics. This is the video that produced all the shorts of the playlist I have created about arrow optics. In the playlist you can find immediate answers to some issues you may be facing with Project Arrow's optics. This video explains that in detail and with a lot less music.

Have a good one everyone!

---

The playlist is available over here:

- https://www.youtube.com/playlist?list=PLw1b-aiSLRZjTbVJrStx756Sgq-5_3dlz

---

Source code:

- https://github.com/jesperancinha/asnsei-the-right-waf

---

As a short disclaimer, I'd like to mention that I'm not associated or affiliated with any of the brands eventually shown, displayed, or mentioned in this video.

---

All my work and personal interests are also discoverable on other different sites:

- My Website - https://joaofilipesabinoesperancinha.nl/
- Reddit - https://www.reddit.com/user/jesperancinha
- Credly - https://www.credly.com/users/joao-esperancinha/badges
- Pinterest - https://nl.pinterest.com/jesperancinha/
- Instagram - https://www.instagram.com/joaofisaes/
- Facebook - https://www.facebook.com/joaofisaes/
- Spotify - https://open.spotify.com/user/jlnozkcomrxgsaip7yvffpqqm
- Medium - https://medium.com/@jofisaes
- Daily Motion - https://www.dailymotion.com/jofisaes
- Tumblr - https://www.tumblr.com/blog/jofisaes

---

If you have any questions about this video please put a comment in the comment section below and I will be more than happy to help you or discuss any related topic you'd like to discuss.

If you want to discover more about my open-source work please visit me on GitHub at:

- GitHub - https://github.com/jesperancinha
Transcript
00:00You probably have already come across a typical problem that we as software developers usually
00:14come across in that when we are working with deeply nested data classes, we usually find
00:19operations like cloning very complicated, even when using Kotlin's own data classes
00:25copy function.
00:26The copy function works very well for fields in the same data class node, however, that
00:31is not the case for deeply nested data class structures.
00:34For these situations where we need to perform operations in deeply nested data structures,
00:38Project Arrow offers us the possibility to use Lens and Prisms.
00:42Lenses allow us to create copies or modified copies of the original objects using one single
00:47line pieces of code, regardless of how deeply nested the original objects are.
00:51Prisms are also optics that allow us to additionally create, modify, or simply return instances
00:57wrapped in right or left from type either.
01:00And to see how optics work, I have created an example for it, but first, we need to know
01:05how to set up our project in order to get compiled classes.
01:10And for optics specifically, we need to create extra classes automatically.
01:15The code has to be automatically generated, and one of the ways to do it is to use the
01:20KSP plugin, and the KSP plugin needs to be declared in our dependencies along with the
01:27Arrow stack and along with the optics library.
01:31And what optics will do is create extra classes for us that we can then use in our code when
01:37we use classes annotated with optics, and we can look at an example of that over here
01:43in our the village of the people service that uses different types that are stereotyped
01:48with optics.
01:50So when we put optics here and here and here and here in these kind of deeply nested structures,
01:56what will happen is that optics will look at this, the KSP plugin will look at these
02:01annotations and it will create these classes on the build folder under generated code,
02:08under KSP, and then if we look at main, we see that we have all of these extra classes
02:12with us.
02:13These are decompiled classes that our IDE can interpret, and if we look at them, we
02:17can see different code structures that look slightly complicated and different than our
02:23usual code, but what it, what they allow us to do is to use arrow optics with the functionalities
02:31that I've mentioned before.
02:33But to understand this better, we need to look at our own source code.
02:37But before we dive into the example, I need to show you the structure that we are using
02:42in that example.
02:43The first thing we have is a data class person that has a name, that person has an age, and
02:49there's also an address.
02:50Age and address are also types, and age only has age as a field, which is an integer, and
02:59address is slightly more complicated because it has a field street, which is also a type,
03:06and city is also a type.
03:08So that means that street also has a name, which is a string, and a number, which is
03:15an integer.
03:16City has a name and has a country, and both of them are strings.
03:22This all means that person is a deeply nested class that has, and this all means, and this
03:28all means that person is a three-level deeply nested data class.
03:32And with this one, we can see how optics can work for us.
03:35So if we go into the main function, this is our example now, what we do here, we create
03:41first an address, and then we create a person, and that person is Barbara.
03:46And Barbara is of age 19 and lives on this address, Supreme Street, and the city is Flower
03:55Power DC, and the country is Hold'em Up.
03:58What we can do with optics now, knowing this, is we can give Barbara an extra year.
04:04And so Barbara can have one more year, and this can create a modified copy of the first
04:10person instance, which will now have an extra age, but will not change the original one.
04:15The way optics work is pretty much like copyworks.
04:19There's no change to the original instance.
04:20We just use the copies of the instances to work with.
04:23So this would be Barbara one year later, and this will be a new address.
04:27We now create just a simple new address.
04:30In this upper bit of our code, we can see that we have created a person named Barbara
04:35Stonewater, age 19, and with this address.
04:38But now we want to change the address, and therefore we create a new address, and we
04:43set it up over here.
04:45And this happens with the use of optics.
04:48And in this case, we are using the lens address so that we can set the new address to Barbara.
04:55This is not the actual use case for this situation, because address is on the second level of
05:01our nested data class.
05:04What will be interesting to see, how do we change the street of this address?
05:09By changing the field name of street.
05:12And if we think about that, using copy from Kotlin's data classes, this would be very
05:18complicated to implement.
05:19However, with optics, we can simply go straight into the name of the street, and use the optics
05:24of street, which comes out of address, which comes out of person.
05:29And then we can set it up with a street named mutated street.
05:33And therefore, the instance of Barbara, or should I say, the changed instance of Barbara,
05:40will have this name in her address in a new street.
05:44So we create a new instance of street, and that instance of street is then applied.
05:49So that lens is setting the name to that street.
05:54So everything is being recreated, the whole deeply nested class is being created, we create
05:58a copy of the original user.
06:01And therefore, the new user with the same name of Barbara, and with the same address
06:07in the same scene of this address, by changing the field name of street.
06:12And if we think about that, using copy from Kotlin's data classes, this would be very
06:17complicated to implement.
06:18However, with optics, we can simply go straight into the name of the street, and use the
06:24name of the street, and use the name of the street, and use the name of the street, and
06:28use the name of the street, and use the name of the street, and use the name of the street,
06:33and use the name of the street, and use the name of the street, and use the name of the
06:39street, and use the name of the street, and use the name of the street, and use the name
06:45of the street, and use the name of the street, and use the name of the street, and use the
06:49name of the street, and use the name of the street, and use the name of the street, and
06:54everything is being recreated, the whole deeply nested class is being created, we create a
06:58copy of the original user, and therefore the new user with the same name of Barbara, and
07:06with the same address, and in the same scene number, the same city, the only thing is that
07:10the age is new.
07:12And this is a copy of the first instance.
07:14The third logline is the new address, and that address will be applied to the new copy
07:19of Barbara, which will have this new address.
07:22So we have here this one, and we can see that the new Barbara has that street.
07:28But up to this point, there's really nothing new and nothing that much different or more
07:32efficient than the simple copy of Kotlin.
07:35The difference is this one, and here's where we can see the power of using arrow optics,
07:40which means that we will change the name of the street, which is located on the third
07:45level nesting, and we change that specifically with this set of the name of the street in
07:54the address for this person.
07:56And simply using the set nullable, we get a copy with that new mutated street address,
08:03which we can see here, it is the same person with the same name, the same age, the same
08:08street as the first object, the mutated street, the same number, the same city, the same name,
08:15of the city, and the same country.
08:17And this has been made very easily with just one line of code, which is this one over here,
08:22by using the optics of name of the street of the address of person.
08:27This is optics in a nutshell, but we still talked about prism.
08:32So let's have a look at one example of prisms in our test code.
08:35So this is the test code for the village of the people service.
08:39And here, I have created a sealed account.
08:42In this sealed account, I have created two nested classes, a checking account and a savings
08:48account.
08:49We have two types of the sealed class account.
08:53These two are subclasses of account, and they are declared inside of account.
08:57If we look at this example specifically, we can find here a checking account prism.
09:04And the way to create this prism is by invoking the constructor of prism, and then overriding
09:11two of its methods.
09:13One is get or modify, and the other one is reverse get.
09:16Think of it this way.
09:17The reverse get will get our original account, or will simply get us a modified checking
09:25account.
09:26The type will not change, and we will know that we will get a checking account, which
09:30is what we declare here as this type.
09:33But we declare also that this prism will operate with any kind of account.
09:37And that's where this get or modify method comes in.
09:40The way it's implemented right over here is like this.
09:43When the account is of type checking account, we return a right account.
09:48And this means that the code is going well, and that the account here is what we expect,
09:53which is kind of the philosophy of using either.
09:56But if the account is not a checking account, and rather any other account, in our case
10:00it could only be a savings account, then we will use a left to return this either.
10:07So having our prism defined, we can now try to use it.
10:11We start by simply creating an account.
10:15So having our prism defined, we can now try to use it.
10:18We start by simply creating an account, and the account has a balance of 100.
10:21100 euros, 100 dollars, it doesn't matter for our example.
10:24It has 100.
10:25And then we try to use the get or modify method.
10:29We have created a checking account, and by saying that it is a checking account, it means
10:35that our result will be considered successful, so we will get a right.
10:40And because we have a right, we can use get or null in this case, and check if the balance
10:43is 100.
10:44So this test will run successfully up until this point, and then we move on to the updated
10:49account.
10:50The updated account instance is created by using the prism, and by using the modify method
10:54of this prism.
10:55What it will do, it will modify this account, and it will copy the balance of 200.
11:01When we do that, we create a new account that will have a 200 in balance.
11:05But this is not anything special.
11:07What is special then, is that when we do a get or modify updated account, and we do a
11:12get or null, then the balance should be 200, because we are getting the correct result.
11:20So let's try now to create a savings account like this.
11:26Our savings account also has a balance, and also has an interest rate.
11:29So if we do this, we need to also define the interest rate, which we will say 2 in this
11:35case.
11:36If we now use this prism, something different might happen, or not.
11:40We can get a new checked savings account this way, but this will give us a left.
11:44And that means that if we do, if we call the prism, and we do a get or modify of savings
11:49account, this will compile, but the result will not be an account, the result will be
11:55an error.
11:57So if we get our savings account, the checking account prism will allow us for us also to
12:01control which kind of input parameters we want to use when creating or modifying an
12:06account.
12:07It knows, in this case, that it will only work with checking accounts, and not with
12:11savings accounts, but accounts, and not with savings accounts.
12:16But let's go on and see the example.
12:18The first thing that we do, we create a checking account with the balance of 100.
12:23It doesn't really matter what sort of currency it is.
12:25It's just the balance of 100.
12:27Then we apply the get or modify to account.
12:31And at this moment, our get or modify implementation doesn't do a lot.
12:35It simply returns an account when that account is a checkings account.
12:40So let's do that.
12:41And when we do that, we see that the account balance is 100.
12:47And that's what we are checking here with this assertion.
12:50In the second case, we update the account.
12:53We modify, which is another out-of-the-box method, and we can use that to simply copy
12:57another balance to a new account that we made here with this updated account instance.
13:04But here, there's also nothing new.
13:06And when we use the get or modify updated account, and we invoke the get or null method,
13:13then with the get or null method, then we see that we get a balance of 200, which means
13:19that our updated account went through the prism and nothing particular happened.
13:25But it could have had, we could have changed our instance or simply created a new one or
13:31changed the account balance.
13:32It depends on what we want the prism to do.
13:35We also know that in the reverse get, we did nothing to the checking account, but the reverse
13:39get will get our original instance.
13:41But now let's create a savings account.
13:43And we create a savings account with a balance of 100.
13:46The savings account is mandatory also to hand in our interest rate, and this will be
13:512.0 in this case.
13:53And if we get that savings account checked through the get or modify savings account,
13:58what would you expect to happen as a return value?
14:01We would probably get a left, right?
14:03We would get a left of savings account.
14:05So that means that the get or null should be null, because that's what left does when
14:11we use either.
14:13And if we try to do it a little bit more readable from the console, we can do a fold and then
14:19print out error.
14:20If an error is printed with the return result of left or if we print out the return result
14:25of left, it'll print right.
14:27And so if we do that, we'll get a right of saving.
14:30And we can also use either, if we do a fold, we can print out left, and if we do a fold,
14:35we can print out right.
14:36And if we do a fold, we can print out right, and if we do a fold, we can print out left.
14:42we can print out a success result with a return value for right. If we run this unit test right
14:48now we will see that all of the assertions will run successfully and as a last log we should be
14:55able to see an error being printed with the left value which is our savings account. So let's have
15:01a look if that is correct. We see that all the assertions ran okay and we see already here that
15:06the savings account is being logged in as an error with a balance of a hundred and an interest rate
15:12of two.
15:21So
15:41as a short disclaimer I'd like to mention that I'm not associated or affiliated with
15:44any of the brands eventually shown displayed or mentioned in this video.

Recommended