• 2 months ago
The vertical form, full video shorts collections, presentation going around the basic concepts surrounding functors, monoids and monads. They are discussed in Haskell and Kotlin domains and the idea is to give an empirical and instinct feeling about what they are, so that we can process better our thoughts with higher level concepts to find solutions for our every day programming language challenges.

---

Documentation:

- Scribd: https://www.scribd.com/presentation/779087903/Monads-Are-No-Nomads
- GitHub - Kotlin Test Drives : https://github.com/jesperancinha/jeorg-kotlin-test-drives
- GitHub - Haskell Test Drives: ttps://github.com/jesperancinha/haskell-test-drives


---

Sources:

- https://www.dcc.fc.up.pt/~pbv/aulas/tapf/handouts/monads.html
- https://userpages.cs.umbc.edu/artola/studyaids/AbstractAlgebraPrimer.pdf
---

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

--- YouTube

A full long form video, with a wrapping powerpoint presentation is available on Youtube over here: https://youtu.be/ShGAN0dguUg

-
Transcript
00:00:00You
00:00:30Semantics are a great part of software development, they are part of our communication and they
00:00:44enable us to communicate our ideas to our peers.
00:00:48When we make software development it is always better to use small words to define big concepts.
00:00:53This way we can shorten what we have to say and clearly communicate our concepts and ideas
00:00:57to our peers.
00:00:59There are 3 words representing 3 concepts that are generally not that much used in real
00:01:03world software development teams.
00:01:06These are Monads, Monoids and Functors.
00:01:12The goal of these 3 in their adaptation from algebra to software development is to minimize
00:01:18another thing called side effects.
00:01:20All of these 4 concepts are an integral part of functional programming and if you are not
00:01:25used to them, haven't heard of them, then stick around for the upcoming videos of this
00:01:29playlist that I have called Monads, they are no Nomads.
00:01:33So let's start with Functors.
00:01:36What are they?
00:01:38Functors are essentially types that implement the function map.
00:01:45Is that the only thing that functors do?
00:01:49One thing that is important to think about when we think about functors is the concept
00:01:53of containers.
00:01:55And what are containers?
00:01:57Containers are structures, data structures maybe, that contain a lot of values.
00:02:04For example, a list is one example of it, an IDER is an example of it, an option is
00:02:11an example of it.
00:02:12If you work with Java, Scala or Kotlin these days, you already have been across these terms
00:02:18like streams, collections, lambdas, etc.
00:02:22All of these terms are related to functional programming.
00:02:25In Kotlin, for example, a list implements the function map.
00:02:29The same can be said for type IDER and the same can be said for optional.
00:02:36And if you have been working with these types, you probably have seen that mapping is very
00:02:40easy to do with them, but what you are actually doing using this map function is using functors
00:02:47and you are mostly associating functors with each other.
00:02:49The usage of functors in Kotlin helps us a lot when we want to transform, for example,
00:02:54a list of elements and make another list with transformed values with it.
00:03:00That is called a functor.
00:03:01But when we think about a list, we think about also other functions that it may have.
00:03:07For example, a filter.
00:03:09A filter, however, does not make a list a functor and the reason for that is that a
00:03:13filter can reduce the elements contained in that list.
00:03:18And because of that, not all elements get transformed.
00:03:21So that means that by having a filter, it does not automatically make a list a functor.
00:03:26The same goes for count.
00:03:28The same goes for a flat map, for example.
00:03:32Because a flat map may transform the list, but a flat map makes something unique.
00:03:39It flattens the results of all the different containers.
00:03:43A flat map will pick up all the values from different containers, associate them to a
00:03:50new container and return that container with the new values.
00:03:54But it doesn't strictly apply a transformation that changes the values of each one of the
00:03:58individual elements of the list and returns a new container with the different transformed
00:04:03elements.
00:04:04So we can think of a functor as something that has the property of associativity.
00:04:09So it can be associated with different functors.
00:04:12It implements a mapping function and this is the case that we see with other containers
00:04:18like either or optional.
00:04:21But if we think of it that way, how come let doesn't make every type a functor?
00:04:31Because let doesn't have a container context.
00:04:34Let simply returns results.
00:04:38And so a let can return a container with transformed results, but it doesn't have to.
00:04:46That's not the function of a let.
00:04:48And to illustrate that, we can find an example right over here, which I have created in the
00:04:53unit test that we are going to go through to explain what a functor is.
00:04:57In this test, I have created a tree collection.
00:05:00So before we even get into the test, let's have a look at the data created for it.
00:05:06If we go to tree collection, we see that the collection is made out of different trees,
00:05:10in this case, 10 trees, and each tree will have 10 leaves, which is this one over here.
00:05:20It's not a very green tree, it's just for testing.
00:05:24And a tree is composed of leaves, in this case, and the leaf has the properties of color
00:05:32and length.
00:05:33These are the two fields that define those properties.
00:05:37This test will first apply a transformation to the tree collection via a map, but before
00:05:43this map is called, this will happen within the context of a let.
00:05:49The let here is just returning a new container with all the different elements transformed
00:05:54by the map, and all the trees will only have one leaf.
00:06:01And this is the test that we'll check for that, and this is the assertion that we'll
00:06:06check that every tree will have only one leaf.
00:06:10But important here is that this let over here is only returning the return value of this
00:06:17map.
00:06:19And if we change let to something else, like for example, how about just the same collection,
00:06:26if we return the same thing, it will still compile.
00:06:34And how about if I just create a new collection, 1 to 10, and I say that they are trees.
00:06:45This new collection has nothing to do with the previous collection, there is no transformation
00:06:49being applied to it.
00:06:52And if we just apply map, for example, and we do a map here, and we say tree, we'll get
00:07:04a new collection of trees.
00:07:07However, when we apply map over here, we are returning a new collection of trees, but that
00:07:15collection of trees is of the same size as the tree collection that we created for the
00:07:20example.
00:07:22In this case, 10.
00:07:24And this is key to understanding functors.
00:07:29The list of trees is a functor because it implements this map function.
00:07:35No matter what we do around here, we will always return a list of 10 elements.
00:07:41If I put here, for example, a 1, it will return a list of integers with 1s in it, but
00:07:52it is still a transformation.
00:07:53We just transformed one tree to number 1.
00:07:59And now we have a list of 10 elements, which are different 10 1s.
00:08:08But this is why let does not make a list a functor, or any other type of functor.
00:08:14If we check on the next test, we'll see the actual reason why list is considered a functor.
00:08:20And the reason is because of this map.
00:08:23And now it's more visible because now we are transforming every single tree into another
00:08:28tree with the same leaves.
00:08:30It is a transformation, a kind of identity transformation, and the new list will have
00:08:37size 10.
00:08:38This next test does something even more special.
00:08:42It transforms all of the trees into new trees with only one leaf, and each individual leaf
00:08:49of each individual tree will be colored pink with a length of 100.
00:08:55And that's what we test over here.
00:08:57We test that it has size 10.
00:08:59And here, instead of for each, we can do a should, we can do a for all, which will
00:09:13test that every single tree has only one leaf, that the first leaf is not null, that the
00:09:24color is pink, and that the length is 100.
00:09:30The next test will go a bit further, and now we will test option.
00:09:36And interesting here is that I'm using option from arrow.
00:09:39We can go here upwards, and we can see that I'm using option from project arrow.
00:09:47And this option has something interesting, which is something that is also used in Scala,
00:09:54but now adapted to Kotlin.
00:09:57And what we do here is something similar, but with a different taste.
00:10:01So if we go over here and check what has been tested here, I made it a bit more complicated
00:10:08because it is very important that we get this concept right.
00:10:12So I have created this object reference named make tree empty, that is an option tree.
00:10:20But not an option tree, it is an option of a function that accepts as an argument a tree
00:10:28and returns another tree.
00:10:32And I create an instance of this option by using sum, and using as an argument a function
00:10:39that simply will make a copy of the current tree to another tree with no leaves on it.
00:10:49Once we have that make tree empty option, we can use it kind of in a function way, and
00:10:55we can map it to be able to use it by doing this, and then using it to call the function
00:11:01and perform a transformation to a tree of 10 leaves, and then check the results with fold.
00:11:09This operation will not throw any errors, but the assertions are ready to capture errors
00:11:14should they occur.
00:11:16We can of course use option to make some sort of error handling, and in this case that is
00:11:21precisely what we are doing.
00:11:23Imagine for example that the result of make tree empty is actually a null value.
00:11:28So let's change the code a bit to test that.
00:11:30If I remove here the tree copy leaves empty list and put here null, I cannot compile this
00:11:38because tree here, the return, needs to be nullable.
00:11:42On top of that, here down below, the result tree comes up as nullable, and that means
00:11:48that potentially is null, meaning that dot leaves would not work.
00:11:53But this with a question mark is not the best thing to do.
00:11:56What we should do is result tree and assert that it should not be null.
00:12:02And here we come to the point of this unit test.
00:12:06What do you think will happen when I run this?
00:12:08Will the code fail over here in this assertion?
00:12:11Will there be anywhere else in the code that the runtime will fail?
00:12:14Will it fail here?
00:12:16Will it fail here?
00:12:18Where will the code fall?
00:12:19Will the code fall in if sub, or will the code fall in if empty?
00:12:24Let's analyze that.
00:12:26We are returning null.
00:12:28And by returning null, it means that we are not returning an empty result.
00:12:33In fact, if empty here would be impossible because we are always returning either a tree
00:12:38or a null value.
00:12:40And so the only possibility is that the code falls into here, in if sum.
00:12:45If our tree has no leaves, then the return result will be a tree with no leaves.
00:12:51And that means that these assertions, these two over here, will return no error.
00:12:58But if the result is null, we will still fall in if sum.
00:13:03And by doing that, that means that the result of result tree will be null.
00:13:09And that means that the code will probably fail right over here.
00:13:13But let's see what happens when we run the test now.
00:13:18And look at what happens.
00:13:20Right over here, where result tree should have not been null, it is null.
00:13:28And this is only possible because of option, and because in this case tree will never be
00:13:33an empty object, because it's not a container.
00:13:37The container here is the option where we have placed the function that we have used
00:13:41in this case.
00:13:42That means that we've used the function, we've mapped it to receive the return value from
00:13:47a tree of 20 leaves.
00:13:49The result is an option of tree that we then fold, and doesn't matter the process of folding
00:13:56in this case, we will always fall in if sum, and this if empty will never be called.
00:14:01And that means that we can either get here a null or an empty tree.
00:14:05If I now revert the whole code and run the test again, we can be sure that the test will
00:14:13work because here, at the result tree, we will get a tree with no leaves.
00:14:19Let's run the test.
00:14:23And that's exactly what we get, a successful test that confirms that we get a tree with
00:14:29no leaves.
00:14:32But these aren't the only functors that we can use.
00:14:35And to illustrate better what are functors, I have here other examples, and the first
00:14:40one is with either.
00:14:42This either comes from Project Arrow, I have discussed this before in this channel, and
00:14:46if you haven't seen the video that I have created about higher level concurrency in
00:14:49Project Arrow, I recommend that you do that now.
00:14:53But if you have, let's have a look at how this either is implemented.
00:14:57When we call this either, the goal is to call our function and have a return value
00:15:02of either that could be on the left an error and on the right the right result.
00:15:08And we create a tree with 20 leaves and then we copy that to another tree that doesn't
00:15:13have any leaves.
00:15:16When this either is finished, the result is stored in createNewEmptyTree.
00:15:20If we fold the createNewEmptyTree, then we are going to have two different functions
00:15:26that we need to implement, a bit analogous to option, only that in this case we are actually
00:15:32checking for errors and for a successful result.
00:15:35If we have a fail, then we need to implement ifLeft, if we have success then we implement
00:15:40an ifRight.
00:15:41We need to give both arguments to fold, and then upon getting a successful result, we
00:15:47then check if the tree is null or not, it will not be null but we can check it anyways,
00:15:52and then we check that it doesn't have any leaves.
00:15:55This will be this result, which we can then use to map that tree to its leaves.
00:16:02And this is a mapping from one tree to the list of leaves, so it's still a transformation
00:16:07of one element to another element, which in this case it is a tree to the container of
00:16:13all the leaves, in this case the list of leaves.
00:16:17The return result will also be an either, but in this case that either has on the right
00:16:23the leaves of that tree, which in this case is an empty list.
00:16:26We can then fold it to check what happened, and then we can try ifLeft, that means if
00:16:31any kind of error has occurred, we can then fail the runtime of the unit tests, but this
00:16:36won't happen.
00:16:37What will happen is that it will be successful, there will be a call to the ifRight function
00:16:42that we implement over here, and here we will only check that the list of leaves that we
00:16:48have taken out of our tree is actually empty.
00:16:51So we have seen a list, which is a functor, we have seen an option, which is a functor,
00:16:56and we have seen now an either, which is also a functor.
00:17:00But then at some point we can carry this definition with us and find functions that sound and
00:17:05seem to behave like functors, but they are not functors.
00:17:09One of these examples is the andThen function.
00:17:12So for example, in this unit test I am creating two functions.
00:17:16The first function is addFiveLeaves, and the second function is divideLeavesByTwo.
00:17:21The first function will add five leaves to a tree, and the second function will divide
00:17:26the leaves by two.
00:17:28Important to notice and to repeat, there is no transformation to the original tree.
00:17:32It will add the leaves, but under the hood it will actually create a new tree with five
00:17:37leaves on it.
00:17:39The same goes for the divideLeavesByTwo, a new tree with half of the leaves as the original
00:17:43one.
00:17:44And then what we do, we use andThen to join the two functions.
00:17:49So in this case we are creating an association between the first function and the second
00:17:52function.
00:17:53And the idea is that when we call the addFive and then divideByTwo, that we do exactly that.
00:17:59We add five, we add five to a tree, or better said, we create a new tree with five extra
00:18:05leaves, and then we divide the total of the leaves of that tree and create a new tree
00:18:10with that amount of leaves.
00:18:12The result will then be for an empty tree, it will add five to an empty tree, and then
00:18:17it will divide that by two, which will be 2.5, which rounded up is three, which is what
00:18:23we expect here.
00:18:24We can then make another function association where we do the exact opposite, but instead
00:18:30of calling andThen, we call compose, which is an interesting one because it associates
00:18:36the two functions but in a reverse order.
00:18:39So in this case, we do a divideByTwo, then add five, because we are saying that we want
00:18:46to compose a function between addFiveLeaves and divideLeavesByTwo.
00:18:51In any case, the result here will be five, because we are dividing zero by two, so that
00:18:57means zero leaves still, and then we add five, which amounts for the total of leaves for
00:19:02the new tree.
00:19:03So here we have two associations.
00:19:05We've got addFiveLeaves and then divideLeavesByTwo, and addLeaves, compose, divideLeavesByTwo.
00:19:14So the question is, how is this associated with functions?
00:19:18It's not, because although we have andThen and compose, and they do associate two functions,
00:19:24neither one or the other function are containers, and the result of using these functions is
00:19:30also not a container.
00:19:33So we don't have anything special here that keeps the container, because there's no container
00:19:37in the first place.
00:19:40Why can this potentially lead to confusion?
00:19:42It's purely because the function itself may seem like a container, and because we can
00:19:48associate these functions with andThen and compose, it may give the feeling that we are
00:19:53in the presence of a functor.
00:19:55However, that is not the case.
00:19:58No container is involved.
00:19:59There is a transformation that seems to be running within a context of a container, but
00:20:04in fact, it's just values being tossed around through the two functions to get a final result,
00:20:10which is not a result inside a container.
00:20:14We just had a look at how functors work in Kotlin.
00:20:17Let's now have a look at how they work in Haskell.
00:20:20Why Haskell?
00:20:21Because Haskell was the first pure functional programming language, and it was created in
00:20:251987, and it was released to the public in 1990.
00:20:31So it has been around for a long time.
00:20:33But one of the things that Haskell does is that it clears it up as to why these principles
00:20:39were invented in the first place.
00:20:41And for that, I have created a few examples that we are now going to have a look at that
00:20:46are similar to the examples that we have seen in Kotlin, but there are going to be curiosities
00:20:51in there that are also important for our understanding of functors.
00:20:55And remember, these things that we are having a look at that are functors may also be monads,
00:21:00and may also be monoids.
00:21:03But for now, we stick to the definition of functors, and we explain them under that angle.
00:21:09So the project is located on GitHub, and it is a new project called Haskell Test Drives.
00:21:15So this is a project that I am creating to include examples made in Haskell.
00:21:20And let's first have a look at how it is built.
00:21:22These examples are organized through topics.
00:21:25There is only one topic at the moment, and it's called functors.
00:21:28And inside, we have different files that can be compiled into native executables.
00:21:32And the compiler is GHC, and GHC can be installed pretty much in the way that I have defined
00:21:37it in the make file.
00:21:39So let's look at it.
00:21:41It's over here.
00:21:42And then inside, we have an install script that has the update for APT.
00:21:48And then we only need to install GHC and cabal install.
00:21:54Once these are installed, we get GHC available in our command line if you are using Linux.
00:21:59Make sure to look for the right documentation to install GHC in your computer should you
00:22:04have a different operating system.
00:22:06I am using Ubuntu in this case.
00:22:08And in any case, if you have questions about this, make sure to leave a comment in this
00:22:13video or in the community section of this channel.
00:22:16So let's have a look at the first example.
00:22:18And the first example is called make list.
00:22:21As we have seen before with Kotlin, list is a functor.
00:22:25Why is it a functor?
00:22:26Do you remember?
00:22:27It has a map.
00:22:29It allows to create a transformation maintaining the context, and it applies to a value.
00:22:36That's it.
00:22:37And the result of these mappings is also a functor that can further be mapped.
00:22:41In the first example, what we have here is two different functions.
00:22:45The first function is add one, and the other one is square.
00:22:50We can use these functions in our mappings.
00:22:53And what we want to do here is prove that list is a functor.
00:22:57But here is the important thing.
00:22:58When we use fmap in Haskell, we need to make sure that we are using it in combination with
00:23:03a functor.
00:23:04And this is key to understanding how they work.
00:23:07And so in our main function, we see that the first line of code is a mapping to something
00:23:12called a just, and just only has five.
00:23:17This is one kind of functor that allows us to map this just five to another result, and
00:23:25the result will be a combination with this function.
00:23:28And in the second line, we see that we have nothing, and we try to add one to nothing.
00:23:34So essentially, we are trying to map nothing, which is also a functor, and mapping it using
00:23:40the function add one.
00:23:42In the third line, we are adding one to all of the values of this list.
00:23:46On the fourth line, we are squaring all the values of this list.
00:23:51And finally, on the last line, we are only printing the list.
00:23:55Let's run this program and see the results.
00:23:58Before that, we first need to compile the HS file, which is, HS in this case stands
00:24:03for Haskell.
00:24:04And let's do that by running ghc, and then the file name, which is, in this case, one
00:24:11underscore maplist.hs.
00:24:16With this command, we have compiled the file, only in this case, we haven't, because I already
00:24:21have a file available.
00:24:22But I want to show you exactly how the compilation method works.
00:24:26So in this case, I'm just going to go here to the root, and just call make clean all.
00:24:31The clean all script removes all the files in the functors directory that have been created
00:24:37using ghc.
00:24:39ghc creates intermediate files, it creates an O file, and it creates finally the executive
00:24:45native file.
00:24:47And this is everything that we are deleting in this line over here.
00:24:50And then here we delete the O files, and then here we delete the high files.
00:24:56Let's run clean all.
00:24:58Now all of the files that we have generated with ghc have been removed, and now we can
00:25:03continue with our example by going to functors.
00:25:07And in functors now, we can run ghc one underscore maplist.
00:25:13And now we see that it has compiled main, it has created an O file, and it has created
00:25:18the maplist executable native file.
00:25:22And we can also see in the middle that it has created an intermediate compile file,
00:25:28which is this .hi file.
00:25:31So let's now just run it.
00:25:33If we run the executable file one that we have just created, we see that we get as a
00:25:38result a just six, which is adding one to just five.
00:25:44We get nothing, because if we add whatever it is to nothing, we get nothing back.
00:25:49And on the other three outputs, we get the original list with the one edit.
00:25:55We get another list of the square result of every individual original element.
00:26:00And then finally we get the original values in the end.
00:26:03So what is magic about this?
00:26:05In all of these examples, we have been using functors.
00:26:10And they go very well with this fmap.
00:26:13For example, if I would just say here add five, five is just a value.
00:26:20Five is not a functor.
00:26:21So applying map on it wouldn't work.
00:26:24So this fmap over here should fail.
00:26:28If we try to compile this now, we see that it says no instance for num arising from the
00:26:35literal five in the second argument fmap, namely five, in the first argument of print,
00:26:42namely fmap add one in the statement of a do block print fmap add one five.
00:26:49The compiler doesn't know what to do with this.
00:26:51And the reason for that is that one is just a value.
00:26:55It's not a container.
00:26:56So the context cannot be maintained.
00:27:00It wouldn't make sense to have a context with value five, because in the end, what we want
00:27:05to do is present the result of this mapping after calling it.
00:27:11So this probably is the easiest way to understand functors in Haskell.
00:27:16But there are other situations that are a bit different and maybe more confusing.
00:27:21Like for example, in the case of functions.
00:27:24Remember that we just left the examples in Kotlin with a curious case of and then and
00:27:30combine that we have established that they are not functors and that they don't belong
00:27:35to these kind of paradigms.
00:27:38They don't belong to these kind of paradigms because they are not being used to combine
00:27:44functors.
00:27:46They are being used as extension functions to join two other functions.
00:27:54And that has nothing to do with functors.
00:27:56However, Haskell has a very ingenious way of using functions as functors.
00:28:04So let's have a look at how this other example is implemented.
00:28:08In the first example, we are establishing that we have a function that will multiply
00:28:13any value given to it by two.
00:28:16The second function, plus three, will add three to any single input.
00:28:22And then we create our first combined function, which is also a functor.
00:28:29The plus three by two means that we first add three to a result, and then we multiply
00:28:35it by two.
00:28:38And that's what happens with this fmap.
00:28:41But notice that I'm using fmap between two functions.
00:28:44This is something that we wouldn't be able to do with map in Kotlin.
00:28:48And that is because those functions that we are trying to join in Kotlin, they are functions.
00:28:54They are not functors.
00:28:55And so we cannot map them.
00:28:58We can use extension functions of Project Arrow to combine them together, but that is
00:29:03not the way functors work.
00:29:07And we can go even further, because here we've got a plus extra.
00:29:11So this is just an extra element that I can add to the logic behind plus three by two.
00:29:19So in this case, we add two, and then we pass on that value to plus three by two.
00:29:25But what actually happens here is that we are joining all of these functions using map
00:29:30because they are functors.
00:29:32The context maintains, and what happens is that this plus extra and this plus three by
00:29:36two, these two new functors are combined in the combined code.
00:29:44In Kotlin, the functions are seemingly combined, but they are not actually combined in the
00:29:48bytecode.
00:29:50What we get in Kotlin is something that works kind of like a functor.
00:29:56It kind of operates like a functor.
00:29:58It walks like a functor, perhaps, but it's not really a functor.
00:30:05This, however, is a functor.
00:30:08And so let's test it and see what we get as a result.
00:30:11For that, we already know we just use ghc and then example two, and then we compile
00:30:17the Haskell source file into our native command.
00:30:23It will still generate the O file, it will still generate the intermediate compile file,
00:30:29but it will give us the native code that we can now call from the command line, and that
00:30:33is to map function.
00:30:36By calling it, we see that the first result is 26.
00:30:39Why 26?
00:30:41Because I'm applying plus three by two to 10.
00:30:47What happens over here is that we add a three to 10, and then we multiply it by two.
00:30:52We get 26.
00:30:55And then the second one, we are mapping a list of one, two, three, using functor plus
00:31:00three by two.
00:31:02This is a strange concept, but it works.
00:31:06Because since both of them are functors, what happens now is that the plus three by two
00:31:11will apply to one, two, three.
00:31:14The function is used as a function and not exactly as a functor.
00:31:18As a result, we get a result of one plus three, which is four, multiplied by two, which is
00:31:24eight.
00:31:27And then the same for two.
00:31:29Two plus three, five.
00:31:31Five times two, 10.
00:31:32And then three plus three, six, times two, 12.
00:31:39And then finally, the last result, which is a 10, where we add then a two, that's 12.
00:31:46Then we add three, which is 15, and then we multiply by two, where we get 30.
00:31:53But the great thing we can see already here, these were all functors, so we were able to
00:31:58combine them all in a single line to perform certain operations that would otherwise be
00:32:04more complicated to implement.
00:32:06In the past two examples, we have seen that lists are functors, and that in Haskell, functions
00:32:11are also functors.
00:32:12But we can have a look at more examples, and one of them is with either.
00:32:17If you remember from the beginning, we were using project error to access these types.
00:32:22These are either, right, and left.
00:32:24And from that, we know that right is where we put the successful result, and left is
00:32:30where we put an unsuccessful result, which can be an error, an exception, or anything
00:32:34we want it to be.
00:32:37In our example, if we go to mapEither.hs, we can see that we have a right value that
00:32:45has been created by using a right that is of type either, of course, and it accepts
00:32:50a string as an error type, and an integer on the right side of it.
00:32:57So this means that our successful value is an integer, and our unsuccessful value will
00:33:02be a string.
00:33:04But we are not only declaring it, but also assigning it a value, which is already right5.
00:33:10And what we do after that is that we print the mapping of a function that adds one to
00:33:17the right value.
00:33:20So this means that the right value is right5, the function adds one, and so after mapping,
00:33:29we should be getting a six.
00:33:31And it's not just a six, it is a right6.
00:33:35And this makes right, in this case, a functor, and it also makes either a functor.
00:33:41And the second right value is made by using fmap of a function that adds five and the
00:33:48right value, which is five.
00:33:50So in this case, the print second right value should result in a 10.
00:33:56Finally, the left value is an error, and it is a left string that has error in the description.
00:34:03And that error is, of course, a subtype of either, that on the left has a string for
00:34:10an error, and on the right has the value integer.
00:34:14And if we try to print this by making a map between a function that adds one to the left
00:34:21value, what do you think it will happen?
00:34:24Are we going to get the left value with error anyways?
00:34:30Is this going to fail?
00:34:32Or is this going to add one appended to the end of the error string?
00:34:39What do you think it will happen?
00:34:40Let's have a look.
00:34:41And for that, we are already in functors, so in this case, we only need to compile it.
00:34:47So we just run ghc3 underscore map either, and this will generate the native file that
00:34:54we can immediately run.
00:34:56So if we do .3 map either, we can see that on the first result, we get right six.
00:35:02On the second result, we get right 10, as we mentioned before.
00:35:05So we can see in our last example that we get a left error.
00:35:09What happens in this case is that we have added one to the left value, right?
00:35:15Not exactly.
00:35:16What actually happens is that left and right, both of them, are either.
00:35:22That means that because they are either, they are also functors, because either is also
00:35:26a functor.
00:35:27And what happens in this case with either is that when we add a value to an either,
00:35:34like in this case, like when we do here plus one, it will add that value to the right value
00:35:40if there has been no error.
00:35:41If this is already a left, that means that whatever mapping we want to do with another
00:35:46functor, the combination will still result in the left original value.
00:35:50And that means that the last original value in this case is error, and that is the result
00:35:54we get, a left with an error string.
00:35:57And as we can see here, an either is a functor, simply because we are able to apply this fmap,
00:36:03and that in the end, we get another container that is another functor that we can then apply
00:36:09with another fmap with another functor.
00:36:13But there are other examples that are also very curious in Haskell.
00:36:16Another one is with IO.
00:36:18And IO may seem a bit strange if you are coming from another language again.
00:36:22It is different, and the way it works is that it establishes a set of rules, a set of operations
00:36:28that it wants to make, and every time we want to get the result out of those set of rules,
00:36:33that is this reverse error.
00:36:34This reverse error can also be used to scan results out of the console, for example, as
00:36:38we try to input data to our program.
00:36:41And what this program is doing is simply simulate that.
00:36:44That means that when we first start the program, we declare our first functor that is now an
00:36:49IO of type int.
00:36:52To declare an IO of type int in Haskell, you can do this, a return 5.
00:36:57This will return our functor that then we can map using this fmap again with a function
00:37:02that will multiply the result by 2.
00:37:05That means that when we get this result over here, we will get 5 times 2, which is going
00:37:12to be a result of 10.
00:37:14And this is interesting because as we do this with this reversed error, we extract our result
00:37:20out of the container, and that means that this result is just an integer.
00:37:24In our second call, we are creating a double result, which is another functor that is a
00:37:29result of combining the two functors, which is the function that multiplies it by 2 again,
00:37:35and the IO mapped result.
00:37:37This results in a functor, and to extract the result out of that functor, we use again
00:37:44this reversed error, and that result will be stored on result 2, and then we print result
00:37:492, and that means that we will see a 20 in the console.
00:37:55But let's try it and see if this matches with what I just said.
00:38:00We first compile our program in the same way as we did before, and now we can just run
00:38:05our newly compiled native executable file, and see that we get 10 and a 20 in the console.
00:38:14This 10 and 20 are integers, these are not functors.
00:38:17The functors we got are these two.
00:38:20We could ask ourselves, why didn't I print also the functors as I did before, the IO
00:38:26mapped result, and the double result?
00:38:28Well, these are IO functors, and IO functors in Haskell are not easily printable to the
00:38:34console.
00:38:35So if we try and do that, we will get an error, and I'll explain that in a minute.
00:38:38So I can try and print the IO mapped result, and then I can try and print the double result.
00:38:45This will result in a failure, which is this one.
00:38:50And if I show you what it is, we can see a text here that says, no instance for show
00:38:55IO int arising from a use of print.
00:38:59What this means is that these functors, the two of them that we have created, IO mapped
00:39:05result and double result, both of them do not implement this.
00:39:10And in Haskell, you need to have this in order to be able to print out results to the console.
00:39:16But we know that we have created double result using an fmap between two functors, the function
00:39:22that multiplies by two, and the IO mapped result.
00:39:25And this could have only been possible if IO mapped result itself an IO also is, which
00:39:31means it is also a functor that can then be mapped using a function that is also a
00:39:35functor that multiplies the result by two.
00:39:39Which also means that we can only see the results working by printing the result, which
00:39:44is using this reversed arrow.
00:39:48We have seen four examples about how to work with functors.
00:39:51We have seen a list example, a function example, an either example, and an IO example.
00:39:57All of these four examples were specifically made to understand about how functors work.
00:40:03In this next example, we are having a look at a custom functor that we can also make
00:40:08in Haskell.
00:40:09It is a different concept, and it is important because here we are going to see a different
00:40:12way that functors can work for us.
00:40:15In the first line of our code, we can see a safe vault.
00:40:19This is a data class that is being defined with this type safe vault, and it is deriving
00:40:24from show.
00:40:26This is to be able to print our object into the console.
00:40:30As we have seen before, without this show, we cannot print functors to the console.
00:40:36In this line over here, we are associating a functor to the safe vault, and what this
00:40:40means is that we can then use safe vault as a functor.
00:40:45We define a function that will double the value of whatever argument we pass through,
00:40:52and here we begin with the most interesting part of our exercise, and that is how to use
00:40:57safe vault as a functor.
00:41:00As we have seen here before, when we define a functor with safe vault, we also need to
00:41:06implement the fmap function.
00:41:08So how does this work?
00:41:11Here we've got a safe vault that will use the function f, which in this case is double,
00:41:20and we'll apply this function to this value over here.
00:41:25Having this in mind, it will then convert that into a new safe vault.
00:41:31This is a simple way to implement a functor, and because now we say that safe vault has
00:41:36an fmap implementation and uses that fmap to create a context to perform operations,
00:41:41we can now call it a functor because it will also generate a functor at the output of the
00:41:46call to fmap.
00:41:49In this case, when we call an fmap from a safe vault with value 10 to a double, the
00:41:55result will be a safe vault of 20, and in our main function, we can see that we are
00:42:01printing custom results to the console.
00:42:03This is where we are going to see in the console a safe vault with a value of 20 in it.
00:42:09In the second line of our main execution, we can see that we are mapping custom result
00:42:13to double.
00:42:15The value inside our safe vault that is now 20 gets doubled and turns into 40, and that
00:42:21means that outside of this function, we are going to get a safe vault with a result of
00:42:2640 in it.
00:42:27So it will be a safe vault 40.
00:42:29And then finally, we can just see an example of how double can be applied to map a list
00:42:34of 1, 2, 3, 4, 5 using the double function, and this way, we can also see that the double
00:42:41function functor can be used either with the safe vault or with a list, and that is because
00:42:47both of them are functors themselves, and so mapping in these cases always work.
00:42:53So in this case, we will get a list of 2, 4, 6, 8, and 10.
00:42:58Let's now compile the code and see it working.
00:43:02So if we run ghc5mapcustom.hs, we're going to get our native executable file, and then
00:43:13if we run it, we're going to get a safe vault 20, a safe vault 40, and a list of 2, 4, 6,
00:43:208, 10.
00:43:22And one important thing here that we have also seen in other examples is that it becomes
00:43:27more clear that functors can interact with each other, and we can perform fmap operations
00:43:32to keep the context of execution of our values and make them work together as one single
00:43:38functor, because in the end, what we always get is a functor.
00:43:42But let's make this even more clear with the second example, which is the map composition.
00:43:47Map composition is an interesting example because we are working with composition.
00:43:51That means we are working with functors that are going to work kind of like a joining of
00:43:57two different functors, but instead, we are first creating two different functions that
00:44:03will work in separate contexts, but instead, we are just saying that one function will
00:44:08be executed after the other with the output argument of the first one.
00:44:12And this is important because then we can see something that is more similar to what
00:44:16we saw in the Kotlin code with and then and combine.
00:44:20So let's begin from the top.
00:44:21In the first few lines of code, we can see that we have a function increment, which essentially
00:44:25just adds one to the input parameter.
00:44:28Then we have another implementation of square, which essentially squares the input parameter.
00:44:34And then we start with creating a maybe of type int, and that maybe is already a functor.
00:44:43And what's interesting here is that we are going to create a maybe with just five.
00:44:50And then we are going to map it using the square and the increment combined together
00:44:56as functions, not as functors, but just as functions in this case.
00:45:01So what will happen here is that we receive five, and inside the functor, five will be
00:45:07then incremented by one, and then it will be squared.
00:45:11That means we will get six and then 36.
00:45:14And only at the end, we will get a new functor of type just.
00:45:19Over here, we have a different example, and that is simply a list of integers.
00:45:24So we define our composition list result one as a list of integers, and then we apply the
00:45:29same mapping.
00:45:32So we use fmap to map one, two, three, four, five to another list that will have different
00:45:40elements.
00:45:41And obviously, in this case, it will be, for example, for the first element, it will be
00:45:47one plus one, two, and if we square that, we will get four, and so on.
00:45:53But what's interesting here is that for every single element, what will happen is that the
00:45:57combination of the two functions will receive every single value of the list inside the
00:46:02functor, which is the combination of these two.
00:46:07And only at the end, after performing the calculation, we will get the new functor,
00:46:12which is going to be the list of the transformed results.
00:46:16In our main function, we create something interesting, which we first print out the
00:46:20result of composition maybe result one, then we print out the result of composition list
00:46:25result one, and finally, we do something different, which is applying the same fmap with the same
00:46:31function combination, square and increment, and then we apply that to composition list
00:46:35result one.
00:46:36So this means that composition list result one, which is a functor that is a list, gets
00:46:41mapped to the combination of the two functions, square and increment, which combined will
00:46:45allow the creation of the new functor, which will be the result of this fmap, which will
00:46:50be the list of the resulting numbers.
00:46:53We still have four examples to go.
00:46:55So let's look at our next example, and that example is about containers.
00:46:59In this case, the specific container we look at is map, and in this example, I have created
00:47:05first a map of strings, and then a map of numbers.
00:47:10The map of strings contains a map that is composed of strings, where the keys are integers,
00:47:18one or two, and this map is created out of a list of pairs, and the first pair has value
00:47:24A, which is a string, and is associated with key one.
00:47:28The second pair has a value of B, and B is associated with key number two, and then we
00:47:33create a map from this list, and then we associate that with the map of strings.
00:47:38The same thing goes for map of numbers, where we create then a map from a list that is composed
00:47:43of two different pairs.
00:47:45The first pair has value number one, two, three, associated with integer one, and the
00:47:51second value has a value of four, five, six, associated with key number two, and therefore
00:47:56we create a map of numbers.
00:47:59The third line of our execution prints out the map of strings, and the fourth line prints
00:48:05out the map of numbers.
00:48:06The following line prints then the interesting part about this example, which is calling
00:48:11a map, and transforming the first map of strings.
00:48:15A map of strings in this case is a functor, and that's the reason why this fmap can be
00:48:20called, with the function that's associated with it, and this is this one.
00:48:25This is simply a concatenation of strings, and this means that z will be appended to
00:48:31value A and value B. In other words, the z string will be associated to all of the
00:48:37different strings, which are the different values of that map, and in the final line,
00:48:43we add one to every single value of the map of numbers, but let's run this and see this
00:48:49working in action.
00:48:50The first thing we do, we compile our example, which is number seven, seven underscore map
00:48:56containers dot HS, and then we get our native compiled file that we can immediately run,
00:49:03and when we do that, we can see the result, which is, for the first result, we have a
00:49:10printout of the complete list, which is a from list of the two different elements, the
00:49:17two different pairs, one A, two B. In the second output, we see again the map of numbers,
00:49:24one, one, two, three, two, four, five, six, and then in the third and fourth lines of
00:49:31our output, we can see the transformed maps that we have created.
00:49:35The first transformed map has now the z appended to its values, and the second transformed
00:49:40map has a one added to its values, and this makes us see how a map is a functor.
00:49:44We can also try another thing.
00:49:46Let's see if we can create a different map.
00:49:49In this case, we can create a map of numbers two, which is going to be the F map of one,
00:49:59for example, and then map of numbers.
00:50:05We can now do something different, which is we can map then the result, and then say that
00:50:18we want to print out a map of number two.
00:50:23If we now compile this file again, we should be able to see, by running it, the final output
00:50:28that we're going to get will have the first value as one, two, six, and the second value
00:50:32as four, five, nine.
00:50:34Let's see if that's true.
00:50:36The first value is one, two, six, and the second value is four, five, nine, because
00:50:39we've added one on the first instance, and on the second transformation, we added two.
00:50:43But what's important here is that we were able to make successful calls to F map, which
00:50:47means that every single result of F map is another functor, which is a property of functors.
00:50:53Bearing this in mind, we should now be able to look at more complicated examples, like
00:50:57for example, the map tree example, where we create a tree.
00:51:02This case represents an implementation of a binary tree, and what it does over here
00:51:06at the top is we declare a tree, and the tree can be a leaf or can be a node, and the node
00:51:14itself can be a composition of two different trees.
00:51:17And thus creating a binary tree.
00:51:19In order for this to work as a functor, we then need to implement at least one F map,
00:51:24and the first F map simply represents the mapping to a leaf, and the second F map represents
00:51:29the mapping to a node.
00:51:32Finally, we can see a running example of this implementation, where we first create a tree
00:51:36that is composed of a node that contains two leaves, and then at the end, we print out
00:51:41a tree that is a transformation of one added to all the different elements of it, by means
00:51:48of an F map.
00:51:51And that means that the result will also be a tree, and this tree can then be F mapped
00:51:56again.
00:51:57But let's first compile the code and see it running.
00:51:59So we first make a GHC.
00:52:02We first compile the map tree example, and then we run it like this, and then we can
00:52:06see that the tree that we have generated is a node that contains two leaves, that now
00:52:11have values 2 and 3.
00:52:13We started out with 1 and 2, and then we added 1 to every single element of that tree.
00:52:19And now we can see if this tree is actually a functor.
00:52:24We already know that it implements an F map, but does it work as a functor?
00:52:28For that, we can just print again, or for that we can just create a tree, let's call
00:52:33it tree2, which is going to be the result of using an F map to add a 3 to the original
00:52:40elements of tree.
00:52:43Then we can do a printout of the F map of tree2, and then we can print out the result
00:52:49of the transformation of this tree, where we then add a 1 to every single element of
00:52:56that tree, by means of the F map.
00:53:01So now, if we compile this example again, ghchmap3, and we now run it, we can then see
00:53:12that the last result is 5 and 6.
00:53:16The reason why this happens is that we first added a 3 to 1, that's 4, and then we add
00:53:201, that's 5, for the first leaf, and the same thing for the second leaf, where we essentially
00:53:24add a 3, that results in a 5, and then finally we add a 1, that results in a 6.
00:53:29These are different trees, and they are the result of doing different mappings.
00:53:35This is what a functor does here in this example as well.
00:53:39Now let's have a look at something a little bit more simple, and that is another example
00:53:43of maybe.
00:53:44But we have seen this example a while ago in another exercise, for this exercise we
00:53:49are going to go a bit deep and understand more how maybe works.
00:53:53That example is located on this file, 9.map.maybe, and this file is the implementation of the
00:54:01example of maybe.
00:54:03So let's have a look into it, from the top to the bottom as usual.
00:54:06So here at the top we have a declaration of a function called increment, which receives
00:54:10as an input parameter a type int, and returns another type int.
00:54:15Increment is implemented in the following way over here, which means that it will add
00:54:19a 1 to the input parameter, meaning that it will just return an addition of 1 to whatever
00:54:24parameter we put in through it.
00:54:27Over here we have the declaration of a maybe, this is maybe of type int, and then immediately
00:54:32we perform an operation in it, and this operation is via an fmap from just 5.
00:54:38We have seen before that just is also a functor, and this is a just of number 5.
00:54:44As we increment it, we increase the value that is inside the container from a 5 to a 6.
00:54:51So this maybe result 1 will be a just 6.
00:54:55Then we find maybe result 2, that is also a maybe of type int.
00:55:02And immediately we perform another operation, and in this operation we will try to increment
00:55:06from nothing, which is also a functor.
00:55:09But which value do you think maybe result 2 will hold after performing this operation?
00:55:15That is what we will see when we run this example, and we can already investigate what
00:55:19happens in the main function.
00:55:21In the main function we see that we first print out the result of maybe result 1, then
00:55:25we print out the result of maybe result 2, and then we print out an increment through
00:55:29a list.
00:55:30So we should expect over here to find a list of 2, 3, 4, 5, and 6.
00:55:36Let's run this example now, and for it we can just run GHC, and again as usual just
00:55:42compile our Haskell file.
00:55:44As we do that, we are now able to run the native executable, and what we see is that
00:55:49indeed we get a just 6, and as you probably had expected we get a nothing out of the maybe
00:55:54result 2, simply because when you have a nothing and you try to map it to something else, and
00:55:59it is just a simple operation like this, we just get a nothing back.
00:56:04Then in the end we get a full list with an addition of 1, and this is just to see that
00:56:08increment doesn't have anything special to it, it's just a function which is also a functor,
00:56:13but in this case it's used as a function just to change the values inside the container,
00:56:18which in this case is a list.
00:56:20But to check if these are functors still, we should check if when applying fmap we are
00:56:24still going to get results.
00:56:26Let's see that, and the first thing that we are going to do is to create a maybe result
00:56:323, and we will assign it the mapping operation of adding 2 to for example maybe result 1,
00:56:45which we have just seen that it has turned into a just 6, so if we add 2 we should now
00:56:51see a just 8.
00:56:53Let's see if that's what happens, and for that I will print out the maybe result 3,
00:57:02and now just compile the code, and that's a map maybe .hs, and now we can just run the
00:57:10code and see what happens.
00:57:13And as you can see, the first line remains just 6, then nothing, then 2, 3, 4, 5, 6,
00:57:18and then finally just 8 as we expected.
00:57:21So this was the last example of our series of examples about functors, but let's have
00:57:25a look at another example which is the bonus example for this topic, and that is the bonus1
00:57:31map tree.
00:57:32The bonus1 map tree is a bit more elaborated example which intends to focus on everything
00:57:37that we have learned so far about functors in Haskell, and it tries to give us a better
00:57:41perspective on how everything works.
00:57:44So let's start from top and go all the way to the bottom as usual, just like we did for
00:57:47the other examples.
00:57:48At the top we find the declaration of type tree, and tree can be a leaf or a node, and
00:57:56that node can have two trees associated with it, making by this whole declaration a binary
00:58:03tree.
00:58:04And in order for the binary tree to work as a functor, we need to create an instance of
00:58:09this functor and associate it with tree, where then we have two different implementations.
00:58:14One of them is the implementation of an aftmap for a leaf, and the other one is the implementation
00:58:18of the aftmap for a composed node, which will have on the left one node or leaf, and on
00:58:25the right one node or a leaf, or a node or a tree.
00:58:28Following that, we see a declaration of something different, and this is the built tree.
00:58:33We can see that the built tree is also an IO, and it is of type tree, which is of type
00:58:40int.
00:58:41In this case, this is also a functor, so built tree is a function that is also a functor
00:58:47that is of type IO, and it returns a tree.
00:58:51What we want from this function is a tree that will only have one sublevel, and every
00:58:55single node will have leaves associated to them.
00:58:59In the first case, we ask for the value of the left leaf.
00:59:03Think of it as a tree that on the left it has a node with a leaf, and on the right it
00:59:07has a node with two leaves.
00:59:09Every single leaf will have a value associated with it, and in the first line, we ask for
00:59:14the value of that leaf on the left.
00:59:16The way we get the values from the console in this case is by using readline.
00:59:21Readline itself is already an IO, and that way we can extract the data from that readline
00:59:27to the left node left leaf value.
00:59:30Readline is something interesting because if you remember from the examples before,
00:59:34an IO allows us to get values from a certain source and then extract using this arrow over
00:59:39here.
00:59:42In this case, we are simply scanning for a value from the console.
00:59:46The second value that we need is the right node left leaf value, which is the value of
00:59:51the leaf located on the left-hand side of the right node.
00:59:56And finally, we need the value of the leaf that is located on the right node, and that
01:00:00is the value of the leaf on the left-hand side of the right node.
01:00:01And that is the value of the leaf on the left-hand side of the right node.
01:00:02And that is the value of the leaf on the left-hand side of the right node.
01:00:03And that is the value of the leaf on the left-hand side of the right node.
01:00:04And that is the value of the leaf on the left-hand side of the right node.
01:00:05And that is the value of the leaf on the left-hand side of the right node.
01:00:06And that is the value of the leaf on the left-hand side of the right node.
01:00:07And that is the value of the leaf on the left-hand side of the right node.
01:00:08And that is the value of the leaf on the left-hand side of the right node.
01:00:09And that is the value of the leaf on the left-hand side of the right node.

Recommended