[00:00.000 --> 00:11.560] Hello, so I will speak about two animations in Haskell using Gloss, Lens and State. [00:11.560 --> 00:17.360] I am Julien DeHos, and I am an assistant professor in computer science, and I use Haskell mostly [00:17.360 --> 00:21.720] for teaching functional programming. [00:21.720 --> 00:26.440] Haskell is not the most widely used language for implementing animations, but still it [00:26.440 --> 00:32.040] has some interesting tools, such as library bindings like SDL2. [00:32.040 --> 00:38.280] We also have some entity component system implementations, which is a classic technique [00:38.280 --> 00:44.440] for developing games, and we also have functional reactive programming, which is a technique [00:44.440 --> 00:50.040] for implementing complex user interfaces, for example. [00:50.040 --> 00:55.560] And you can find some cool projects developed in Haskell, for example the effect process, [00:55.560 --> 01:02.000] which is a game available on Steam, that has been open sourced recently, and also the [01:02.000 --> 01:05.840] weanimate library, which can make quite impressive animations. [01:05.840 --> 01:14.720] In this talk, I will show how to implement several animations on concrete examples using [01:14.720 --> 01:20.760] functional programming, and how to improve this code using some features of Haskell, [01:20.760 --> 01:29.240] like data type, release evaluation, students library and the state and one other. [01:29.240 --> 01:33.240] So first, let's look at a very simple example, let's say... [01:33.240 --> 01:36.760] I want to know, can we get to a little louder? [01:36.760 --> 01:37.760] Oh, okay. [01:37.760 --> 01:39.040] It's a little hard to understand. [01:39.040 --> 01:40.040] Okay. [01:40.040 --> 01:47.560] So as a first example, let's say we want to draw a disk on the screen with a fixed radius. [01:47.560 --> 01:52.200] To do that, we can use the Gloss library, which is a classic library in Haskell for [01:52.200 --> 01:56.120] implementing animations and 2D graphics. [01:56.120 --> 02:04.240] This library provides some functions for drawings, primitives, for handling user events, and the [02:04.240 --> 02:10.160] Gloss library also provides some main loops that will run the main application. [02:10.160 --> 02:14.520] So basically, all we have to do is to write some unlawful functions, which say how to [02:14.520 --> 02:21.440] run the scene or how to run the user inputs, and then we pass these functions to the main [02:21.440 --> 02:26.040] loop and that's all, we can run the program. [02:26.040 --> 02:28.040] So let's do that. [02:28.040 --> 02:32.120] For this first example, we don't have any particular data, we just want to draw a disk [02:32.120 --> 02:38.160] with a fixed radius, so there is no data to remember for describing the scene. [02:38.160 --> 02:43.920] So we can write a type, which represents the model of our application, but here we don't [02:43.920 --> 02:48.400] need anything, so we can say it's the unit type, which means no data. [02:48.400 --> 02:55.040] Then we have to write a function that renders the scene, so this function should take a [02:55.040 --> 02:58.400] model and return a picture. [02:58.400 --> 03:02.760] Here we use the solid circle function, which is provided by Gloss, to draw a disk on the [03:02.760 --> 03:10.000] screen, and we say we want a disk with 50 pixels as the radius. [03:10.000 --> 03:16.040] We also need a function to under user events, that function should take an event and a model [03:16.040 --> 03:18.240] and return a new model. [03:18.240 --> 03:25.920] This is a very classic way for modifying data in functional programming. [03:25.920 --> 03:33.800] We can't mutate a variable because it's a side effect and pure functional programming, [03:33.800 --> 03:36.560] we can't do that using pure functions. [03:36.560 --> 03:42.640] So we just take the current model and return a new model, a copy of the model, which contains [03:42.640 --> 03:44.800] the modifications. [03:44.800 --> 03:49.640] For now, the scene is static, so we just return the same model. [03:49.640 --> 03:59.200] And finally, to handle time, we just need a float, the elapsed time, the previous update, [03:59.200 --> 04:04.320] and the current model, and we return the new model with the modification. [04:04.320 --> 04:11.200] Once again, the scene is static, so for now, we return the same model. [04:11.200 --> 04:14.360] Now we can write the main function. [04:14.360 --> 04:20.720] We just have to set some parameters, for example, the initial value for the model, and some [04:20.720 --> 04:26.600] parameters for the window, the background color, and the format of the animation. [04:26.600 --> 04:32.080] Then we can call the play function, which is a main loop provided by the Gloss library, [04:32.080 --> 04:36.240] and we just pass to this function our parameters and our under function. [04:36.240 --> 04:40.200] This is a very classic way to do in a functional programming. [04:40.200 --> 04:45.840] We have functions that we can pass to other functions, and we can organize the code like [04:45.840 --> 04:46.840] this. [04:46.840 --> 04:52.880] So we get something like this, we can run the program, it's really impressive. [04:52.880 --> 04:56.640] Nice. [04:56.640 --> 05:00.080] And now let's add some animations. [05:00.080 --> 05:05.920] So let's say we want to refresh the scene every second and change the radius using a [05:05.920 --> 05:08.040] random number. [05:08.040 --> 05:12.640] So to do that, we can use a pseudo-random number generator. [05:12.640 --> 05:20.200] We need to model our scene differently, so we write a type, which is model here, which [05:20.200 --> 05:26.640] has two fields, first the current radius of the disk, and the random number generator [05:26.640 --> 05:28.720] that we can use to update the scene. [05:28.720 --> 05:31.280] So this is a record type in Haskell. [05:31.280 --> 05:42.760] We have two fields, which have each of them as a name, and we can then use the function [05:42.760 --> 05:43.760] here. [05:43.760 --> 05:47.800] So the name of the field is also a function that can access to this field using the model. [05:47.800 --> 05:54.520] So here we get the radius of the model, and we use that as the radius for drawing the [05:54.520 --> 05:56.320] disk. [05:56.320 --> 06:00.760] Of the under time function, all we have to do is to generate a new radius. [06:00.760 --> 06:09.240] So we take the generator inside the model, and we call this function to generate a new [06:09.240 --> 06:11.320] radius. [06:11.320 --> 06:18.320] Since we cannot mutate the generator, we have to return a new generator for the next random [06:18.320 --> 06:19.320] generation. [06:19.320 --> 06:25.000] So this is why we get a new radius here and a new generator here. [06:25.000 --> 06:33.800] And that's it, we can build and return the new model, which is the result of the function. [06:33.800 --> 06:36.600] We need to update the main function. [06:36.600 --> 06:40.440] We have to get a random number generator. [06:40.440 --> 06:48.360] We can do that with this function, which gets the standard number generator from the system. [06:48.360 --> 06:55.000] And we can also generate a first random number for the first radius of the animation. [06:55.000 --> 06:59.240] And the model is built, is constructed here. [06:59.240 --> 07:11.680] We get something like this, which is not so much impressive, but there is some animation. [07:11.680 --> 07:21.720] So this is a very classic way for generating random numbers, but in Askel, we can do differently. [07:21.720 --> 07:28.400] Since Askel has lazy evaluation, we can define an infinite list for all the radius of the [07:28.400 --> 07:36.200] animation, and Askel will compute the numbers when it needs them. [07:36.200 --> 07:43.480] So instead of the generator, we can use here a random list, an infinite list, and that's [07:43.480 --> 07:44.480] all we need. [07:44.480 --> 07:52.080] We will consume the elements in this list for having new reduces. [07:52.080 --> 07:54.440] The unmet time function can be in fact like this. [07:54.440 --> 07:58.120] So we have here the infinite list. [07:58.120 --> 08:05.720] And we can just get the first element for the new radius, and the rest of the list will [08:05.720 --> 08:11.920] be used for the next update of the scene. [08:11.920 --> 08:15.800] In the main function, we have a function to get an infinite list. [08:15.800 --> 08:22.000] So instead of the randomR function, we just have to call the randomRS function. [08:22.000 --> 08:27.240] And this gives us an infinite list of random numbers, and we don't have to under a random [08:27.240 --> 08:36.400] generator explicitly. [08:36.400 --> 08:41.560] Let's say we want a ball that moves inside the window, and bounces against the border [08:41.560 --> 08:51.520] of the window, and can show the result. [08:51.520 --> 08:58.040] So we want a ball that moves inside the window, it can bounce against the border of the screen, [08:58.040 --> 09:06.280] and if I hit on Turkey, the scene is initialized with a random velocity and a random position [09:06.280 --> 09:08.840] for the ball. [09:08.840 --> 09:11.840] So how can we do that? [09:11.840 --> 09:19.080] We need more complex types, so we can first describe a ball as a position and velocity. [09:19.080 --> 09:25.360] These fields are 2D vectors, and now the model is just the current ball and the infinite list [09:25.360 --> 09:33.360] of the other balls we can generate randomly as we did before with the radiuses. [09:33.360 --> 09:38.960] These types are more complex than before, because we have a model that has a ball, and [09:38.960 --> 09:44.360] a ball has two fields which are 2D vectors. [09:44.360 --> 09:51.760] So these vectors have x-coordinate and y-coordinate, so we have nested types which is a bit more [09:51.760 --> 09:55.040] complex to use. [09:55.040 --> 10:00.840] We can handle this type with a scale using standard record syntax, there is no problem [10:00.840 --> 10:01.840] with that. [10:01.840 --> 10:04.600] The syntax is just a little bit more complex. [10:04.600 --> 10:09.920] So here we get the ball field of the model, and here for example we return the same model [10:09.920 --> 10:17.240] as the argument, but we change the ball field with these balls here, which has been computed [10:17.240 --> 10:18.240] before. [10:18.240 --> 10:26.160] All the other fields of the model doesn't change, we still copy them, in fact. [10:26.160 --> 10:32.280] So this function updates the scene, I have implemented it in two steps. [10:32.280 --> 10:38.440] So first we move the ball and then we compute bounces against the border of the window. [10:38.440 --> 10:43.400] So let's look at the update bounces function. [10:43.400 --> 10:49.120] We have to compute the collision with the border of the windows, so we take a ball as [10:49.120 --> 10:53.520] input and we return the ball after all the collisions have been computed. [10:53.520 --> 11:01.880] To do that, we can use the record syntax as did before to change only the field that needs [11:01.880 --> 11:02.880] some modifications. [11:02.880 --> 11:10.680] But in fact, it's sometimes simpler to fully reconstruct a ball, so that's what I did here. [11:10.680 --> 11:18.120] I have detected a collision with the left border, and I have to return this ball so [11:18.120 --> 11:23.320] I can set explicitly what is the new position vector and the new velocity vector. [11:23.320 --> 11:30.120] In fact, there is only two fields which are different, the x-coordinate of the position [11:30.120 --> 11:35.840] and the x-coordinate of the velocity. [11:35.840 --> 11:44.120] So to avoid reconstructing the ball, we can use a library in a scale which is length and [11:44.120 --> 11:45.920] which can simplify this code. [11:45.920 --> 11:53.800] So the length library enables us to access and modify nested types so we can go deeper [11:53.800 --> 11:57.480] inside the type to just add a small modification. [11:57.480 --> 12:04.400] To do that, we need to construct lenses, lenses are just functions that can access [12:04.400 --> 12:05.720] to a data type. [12:05.720 --> 12:10.880] And when we have these functions, we can use all the functions an operator provided [12:10.880 --> 12:13.720] by the lens library. [12:13.720 --> 12:16.040] So let's do that. [12:16.040 --> 12:22.400] We can build these functions, these access functions using this function make lenses, [12:22.400 --> 12:24.120] and that does everything for us. [12:24.120 --> 12:29.240] So we just have to call make lenses for the ball and for the model. [12:29.240 --> 12:30.240] And that's it. [12:30.240 --> 12:36.440] We can use all the operators provided by the lens library. [12:36.440 --> 12:38.680] This can look like this. [12:38.680 --> 12:46.040] So here I return the model with two modifications, the first modification which is applying this [12:46.040 --> 12:55.480] function to the ball field and the second modification here where I apply the update [12:55.480 --> 12:58.240] lenses function to the ball field of the model. [12:58.240 --> 13:07.640] And finally, the model with these two modifications is returned. [13:07.640 --> 13:09.680] We have more than that. [13:09.680 --> 13:15.000] For example, for the update lenses function, instead of reconstructing the ball, we can [13:15.000 --> 13:20.360] now just getting deeply inside the type to apply some changes. [13:20.360 --> 13:27.880] For example here, I set this value to the X field of the position field of the ball [13:27.880 --> 13:29.600] and finally the ball is returned. [13:29.600 --> 13:32.080] And then I can change another modification here. [13:32.080 --> 13:36.640] I apply the negate function to the X field of the velocity field of the ball. [13:36.640 --> 13:44.560] So I can change several modifications and go deeply inside the type to make some modification, [13:44.560 --> 13:46.920] setting a value or applying a function. [13:46.920 --> 13:49.960] So this is quite interesting. [13:49.960 --> 13:53.680] We can still improve this code. [13:53.680 --> 13:56.440] As you can see, we take a ball and we turn a new ball. [13:56.440 --> 13:59.120] So it's just updating a ball. [13:59.120 --> 14:06.880] And to do that, we have computed here several steps which corresponds to the collision between [14:06.880 --> 14:08.880] all the borders of the windows. [14:08.880 --> 14:13.760] In fact, we are modifying a ball, but we can't do that in pure functional programming. [14:13.760 --> 14:20.040] So we have to use intermediate variables that store the modification after this collision [14:20.040 --> 14:21.040] and this collision. [14:21.040 --> 14:29.440] So the code is quite cumbersome and we can improve that using something in Askel, which [14:29.440 --> 14:32.760] is called the state monad. [14:32.760 --> 14:36.360] So the state monad is a very well-known monad in Askel, it's a very classic monad. [14:36.360 --> 14:42.560] It's just a context where we simulate mutating a state. [14:42.560 --> 14:48.400] So each action inside this monad is an access or a modification of the current state and [14:48.400 --> 14:54.880] we can get the final state or another result, we can do that also. [14:54.880 --> 15:00.280] And that works well with the lens library because the lens library provides a stateful [15:00.280 --> 15:05.280] version of its function and operators. [15:05.280 --> 15:06.280] So let's do that. [15:06.280 --> 15:09.080] We can change another function like this. [15:09.080 --> 15:16.040] Instead of applying several modifications, we can just execute the state actions defined [15:16.040 --> 15:17.040] here. [15:17.040 --> 15:18.920] So this is the function. [15:18.920 --> 15:24.640] We have to, this function takes a first parameter which is the previous ball and when we have [15:24.640 --> 15:29.000] applied all the action, the state action, we get a final state which is the final ball [15:29.000 --> 15:33.720] that we can use to update our model. [15:33.720 --> 15:35.960] Let's see the update monad's function. [15:35.960 --> 15:40.800] So instead of taking a ball and returning a ball, now it's clear that we are in a state [15:40.800 --> 15:48.800] monad and this is a state action where the current state is a ball and we can return [15:48.800 --> 15:55.520] a value but here we don't need that so the function returns a unit. [15:55.520 --> 16:02.320] That means that every action inside this function is now an action, a state action. [16:02.320 --> 16:08.320] So reading the state, modifying the state and so on. [16:08.320 --> 16:15.200] For example here we can access the postfield of the current state which is a ball. [16:15.200 --> 16:22.920] Here we can set this value to the x field of the position field of the ball or applying [16:22.920 --> 16:29.280] a function on the x field of the velocity field of the ball which is the current state. [16:29.280 --> 16:36.920] Since the state monad is a monad, we can use all the features available for monads such [16:36.920 --> 16:44.680] as the denotation so we can change several actions like this and we can also use some [16:44.680 --> 16:51.200] functions provided for monads such as the went function. [16:51.200 --> 16:57.840] As a result, the code is a little bit more simpler and it's clear that this is a state [16:57.840 --> 17:04.680] action that we have a current state which is modified according to the code and then [17:04.680 --> 17:11.400] we get the final state and this is checked by the compiler. [17:11.400 --> 17:18.080] So to conclude, we have seen that functional programming and ASCEL using a functional programming [17:18.080 --> 17:22.040] and ASCEL we can implement animations and this is very natural in functional programming [17:22.040 --> 17:27.160] since we just have to pass some function to other functions like a main loop and we can [17:27.160 --> 17:31.560] decompose and organize our code like this. [17:31.560 --> 17:40.080] We use infinite list to generate random numbers so we don't have to use random numbers explicitly. [17:40.080 --> 17:44.480] We just consume the elements of this list. [17:44.480 --> 17:51.760] We also use the lens library to access or modify nested types and we can go deeply inside [17:51.760 --> 17:54.360] these types. [17:54.360 --> 18:00.480] Then finally, we simulate mutable state using the state monad so we can modify variable [18:00.480 --> 18:04.040] and get the final result. [18:04.040 --> 18:11.600] So all of this is still based on functional programming so we just manipulate pure functions [18:11.600 --> 18:17.400] and static typing and this is quite easy to read and less work run since we have no side [18:17.400 --> 18:22.800] effects function only depends on its argument and produce the same result if the arguments [18:22.800 --> 18:24.680] are the same. [18:24.680 --> 18:30.120] And all of this is checked by the compiler. [18:30.120 --> 18:35.120] So this code, this state and the code shown here are available at this link and you can [18:35.120 --> 18:42.200] find some information in the documentation of the libraries and see things that sit for [18:42.200 --> 18:43.200] me. [18:43.200 --> 18:44.200] Thank you for your attention. [18:44.200 --> 18:45.200] Thanks to the organizer. [18:45.200 --> 18:55.400] Thank you, Julia, and there's five minutes for questions. [18:55.400 --> 19:08.200] If you have a question put up your hand, I'll bring the mic. [19:08.200 --> 19:32.280] Do we know what the performance of class is like for complex applications like could [19:32.280 --> 19:39.920] you write a complex QI in Gloss? [19:39.920 --> 19:45.760] Do we know what the performance of Gloss is for complex display? [19:45.760 --> 19:55.520] Gloss is based on OpenGL so it's not that slow but I don't know for very complex animations. [19:55.520 --> 20:03.680] I believe some projects use SDR and it seems they have no problem of performance but I [20:03.680 --> 20:17.360] have no experience more like that. [20:17.360 --> 20:23.480] In the play function it pretty much makes the whole program pure with no I.O. [20:23.480 --> 20:28.920] What if you do want to do any I.O. in an application? [20:28.920 --> 20:36.520] So the Gloss library provides both two interfaces, one which is purely functional and another [20:36.520 --> 20:38.400] where you can do I.O. [20:38.400 --> 20:48.920] So there is a version where you can do that. [20:48.920 --> 20:50.920] Any more questions? [20:50.920 --> 20:53.920] Yes. [20:53.920 --> 21:03.400] Can you explain the operators used for the lenses? [21:03.400 --> 21:06.400] There's many, many operators. [21:06.400 --> 21:10.400] Is the person signed? [21:10.400 --> 21:11.400] Yes. [21:11.400 --> 21:12.400] Okay. [21:12.400 --> 21:17.240] So there is two versions of these operators, one which is purely functional so you just [21:17.240 --> 21:22.600] take your data structure that it can access and return the value. [21:22.600 --> 21:24.480] So this is such operators. [21:24.480 --> 21:32.560] So that means we apply a modification, so the ball zero is returned after this modification. [21:32.560 --> 21:35.280] So this is what this operator means. [21:35.280 --> 21:38.080] Here it's for accessing another field. [21:38.080 --> 21:41.960] So it's an X field of the position field of the ball. [21:41.960 --> 21:49.840] And this operator says we set the value in this field and this operator says we apply [21:49.840 --> 21:52.640] a function on the field. [21:52.640 --> 22:00.000] And the stateful version is the same but we have an equal sign instead of the tilde. [22:00.000 --> 22:03.280] Like a get and a set. [22:03.280 --> 22:06.640] Yes, we can say like this. [22:06.640 --> 22:12.360] We can say that. [22:12.360 --> 22:14.360] Any more questions? [22:14.360 --> 22:15.360] Okay. [22:15.360 --> 22:18.360] Let's thank Julio. [22:18.360 --> 22:19.360] Thank you. [22:19.360 --> 22:41.100] Thank you.