What do you want to learn?
Skip to main content
by David Mann
Start CourseBookmarkAdd to Channel
Table of contents
For a function to be pure, it means three things. It can't have any side effects, meaning nothing outside the function changes. It must always return the same output for a set of inputs, which kind of implies that it has no external dependencies. So, for example, it can't make use of a value not passed into it as a parameter to perform part of its work, because that external value could change, which would mean that the function is likely to return something different. Let's go take a look at some examples of pure and impure functions. So this code here, is it a pure function? What do you think? Well, yes. It passes all of our tests for pure function, so it's a pure function. That was pretty easy. How about this one? It only passes one of our three tests. It has a dependency upon the external variable c, and so it won't give the same output if c were to change. So this one is not pure. This one? This one's similar to the last, but this time its external dependency is on func1, so this one isn't pure either. Are you sensing a pattern yet? This one isn't pure because it has side effects. It changes the value of c, which is outside of the function. So it fails. Also notice that it doesn't return anything, which isn't technically enough to make it an impure function, but if pure functions don't return anything and can't change anything outside of itself, why exactly are you calling it? It's just wasting cycles. How about this one? This one isn't pure because it also has side effects. It changes the console. Technically that's an external dependency as well. Again, notice that it doesn't return anything, a good sign that you might have a purity problem. Last one. You've probably guessed that this one isn't pure, but why? Yeah, it has a dependency on something outside of itself, func1, and can't be sure that func1 doesn't cause a side effect. So that counts as a side effect for this function. So not pure here, either. Hopefully those quick examples help you understand pure and impure functions. To wrap things up, just a few final thoughts about pure functions. Pure functions not having any side effects means that they can't be used to change anything in the user interface, namely the DOM. That would be something outside of the function. That means you need to separate out any UI updates and keep them separate from your pure functions. As I alluded to, a pure function that doesn't return something doesn't make any sense. It can't impact anything outside of itself, and so if it doesn't return something, what's it doing? In addition, whatever's calling the pure function should always make use of that return value in some way. If it doesn't, what was the sense of calling the function to begin with? Now note that using the return value could simply mean storing it for later or checking if it's undefined in branching appropriately, et cetera. It doesn't mean that it needs to immediately do something with the returned value. One more thing along these lines, and this isn't a hard rule of functional programming, it's just a thing I like to do and something you'll see in lots of functional programming. I never return undefined or null from a pure function. If the function could in certain circumstances return a widget, I like to always have it always return a widget, never undefined or null. I'll then have something like an is valid property on the widget that tells the calling code whether that widget is okay to work with. Again, not a strict tenet of functional programming necessarily, but a good practice I think. The only variation on this is if I'm working with a library that supports maybes. In that case, I'll use the library to return an object or maybe.nothing or maybe.none, depending on what the library calls it. We'll touch more on maybes in the libraries that support them. The last thing about pure functions is that they're extremely easy to test. They take all of their dependencies as parameters, have no side effects, and always return something, so writing tests is easy.
Higher order functions is one of those big scary terms for a really simple concept. A higher function is simply a function that either takes another function as a parameter or returns a function as its return value. That's it. Pretty simple once you know that. In fact, because it's so easy, rather than jump over to our demo machine, here are a couple examples of incredibly simple higher order functions. The first simply takes in a function and a parameter z and returns the result of evaluating the function and passing in the fixed string dave and the value of z. The second returns a function that takes a single parameter and logs to the console the word hello and whatever that parameter is. The result in this case is simply hello dave with an exclamation point being logged to the console. This is functionally equivalent to a single function like this. With simple examples like this, it's a little hard to see why we care about higher order functions, but we'll see why when we get to currying in just a moment.
Currying – A Spicy Look at Functions
Currying is the act of taking a function which accepts 1 to n parameters and producing a collection of 1 to n functions, which each take 1 parameter. Looking at this simple example, if we even add function, which simply returns the sum of two parameters passed to it, we can curry that and produce a function in which we've eliminated one of the parameters by essentially hard-coding one value. This new curried function add 5 can now be used to add 5 to any value, as you can see here, with the expected output of 6. Overly simplistic examples such as this are useful to explain the basics of an idea like currying, but I think they fall short when trying to really grok something. Understanding the basic concept of currying is easy once you've looked at an example like this, but to really get it you need to see something a bit more real-world, so in just a moment we're going to look at some real-world examples of currying in our demo. But first a quick side note about an often confused and similar term. This is really just for the purists out there, so it's not something to be overly concerned about. Partial application is executing a function with less than its full set of expected parameters, and hard-coding one or more of those parameters. But nothing about partial application says the new function can only have one parameter. Contrast this with the definition of currying from the last slide in which we said that we end up with a series of functions each taking only one parameter. Here's an example of partial application that's similar to the example we used for currying. Our sum function takes 3 parameters. Sum5 is a partial application of that, which takes 2 parameters, and sums them along with the hard-coded value of 5. This is partial application, not currying. As I said, nothing to be overly concerned about, just tuck it away in the back of your head that there is a difference. Now let's go take a look at some examples of currying. Taking a look at currying, we're going to start from where we left off in the composition.js. All I've done is taken all of that code and copied it over into this file curry.js. So we still have our filter functions, filter equals, and filter not equals, we still have compose. I'm going to get all of these filters out of the way, and then we're going to go through and build our own functions that we're going to manually curry. So we'll start with an isMarried function. It'll take a value and an array, and it will then call into filter equals, pass in the property of married, whatever value was passed into isMarried, and then whatever array was passed into isMarried. So now we've gone from three parameters on filter equals to two parameters on isMarried. So we've removed one of those parameters. Now we're going to create another function here that we'll call married. That's going to take a single parameter, just the array, and it's going to make a call and do isMarried, pass in a value of true, and whatever array it was given. Similarly, we can create another function here that takes an array, calls into isMarried, and passes in false. Now when we're going to write this to the output, we can call that married, and all we need to do is pass in the single array parameter because we've manually gone through and curried from filter equals with three parameters to isMarried that takes two parameters to married or single that take just one parameter. So that's showing going through and doing manual currying of our functions.
Immutable – Can’t Touch This
Closure Without Grief
Closure sounds like something developers need when they break the build. The final step of the grieving process. But that's not what we're talking about here. In this case, we're talking about function closure. Or to use big fancy words, lexical scoping. In any event, the idea is way simpler than the terms make it seem. If you have a scope, for example, a function, in this case represented by our orange square, which defines a variable, here the triangle, then any function also defined inside the orange square, such as the blue circle, can access the triangle. So far that's just pretty standard block scoping. A function can access any element defined by its parent. Closure comes in when you take the inner function and reference it from outside its parent scope. So now we have a variable outside the orange square that references the blue circle. Even though we're outside the scope of the orange square now, we can still access the triangle, even though it isn't defined inside our circle function. The reference to our circle function forms a closure. It wraps the entire lexical or execution environment of the circle function and carries it along with the function. Translating this shape example into code would look like this. I've named the elements to correspond to their shape counterparts to help everything click together. This is going to be used in currying among other places, so let's take a look in our demo. Taking a look at closures, we're going to use the same code that we used inside the slides, so we have our orangeSquare function, we have our outside variable that points to or gets the result of orangeSquare, and then we just call that outsideVar function. And you can see over when we run node, we get hello from inside. If I were to try to uncomment this console.log for triangle, you're going to see we get a reference error, triangle is not defined. That's because it's enclosed inside orangeSquare, I can't get at it from outside. The only way I can really access it is by calling the outsideVar, which has a closure around orangeSquare, so it has access to triangle. Otherwise, triangle is not available to me.
A Visit to Fantasy Land (the Spec)
Choosing a Library
It doesn't make any sense to use all of these libraries at the same time as there is so much overlap between them. So, how do you choose which library to use or which libraries are complementary and can be used together? That's what we're going to cover in this module. We're doing this before looking at the individual libraries, so perhaps you can save a little time and focus in at least initially on the libraries that look the most interesting and most applicable to your situation or needs. Primarily I'm going to be considering these aspects as I compare and contrast the libraries. The functionality offered by each library, how active they are both in terms of commit history, but also active issues, responsiveness to questions, et cetera. Some of the libraries are not very active on the commit history, for example, because they're essentially mature and don't change very often. But they're still very active in terms of answering questions and resolving issues. We're going to look at interoperability to see where holes in one library might be able to be filled by capabilities from another library without causing problems. And finally, I'm going to consider how easy it is to get started with a given library. A lot of this is going to be the documentation for the library, but it also factors in the availability of third-party blog posts, perhaps providing walk-throughs and such, as well as the responsiveness of the team behind the library to questions and issues. I'll say this right up front, this is a decision you have to make for yourself or as a part of your team. I'm going to try really hard not to let my biases or preferences come through. They will, of course, but I'm intentionally not making an out-and-out recommendation for a given library, primarily because I have no idea what your situation or needs are. I'm going to present the information as neutrally as I can, and then it's up to you to decide. But here's the thing. None of these are bad libraries. They each have different strengths and weaknesses, but none are bad. Also, remember this is my take on things. As much as I'm not expressly stating an opinion, the simple fact that I'm highlighting certain things in each library means that I'm focusing on the things that are important to me. Different things are going to be important to you.
The Big Overview Slide
How Do I Choose?
Getting Started and Common Operations
Immutable Data Structures
Taking a look at Immutable.js in Visual Studio Code, you can see we have a couple of things set up here already. I'm requiring immutable. I'm also requiring a people.json file, which is the data we're going to use through most of the demos in this course. We take a look at people.json, it's just a listing of information about people, sample information created for the application my company builds, but we just grabbed some of that sample data and are using it for, like I said, most of the demos inside this course. Back over in Immutable.js, we're setting up, clearing out the console, giving us some space. We're setting up a variable called output. This is what we're going to be writing out to the console. Down below that we're just logging out that out variable and then giving us some space again so we can see the results of each test run. So inside here is where we're going to be putting our code, and I've got some snippets set up that I'll be just pasting in and then we'll talk about them. Starting with some information just to get a sense for how we work with the different data structures inside Immutable, you can see we're just, we're not using the people.json for this yet, we will be in just a bit, but we're just setting up a list of a and b. So we're taking an array and converting it to a list. I'm going to start up node using nodemon, and then I just go ahead and save, and it's going to give me what my output is. So you can see it's just a list of a and b converted from that array. I can do the same type of thing for a map, so you can see what a map looks like. Now I've mentioned before about nested objects, and here is where that starts to come into play. You can see when I look at my map now I have a, b, and then c as an object. So it doesn't go and create anything, it doesn't touch that nested object there, so we don't get d 123, we get a with a value of b and c with a value of an object, so that object is not immutable. If we take our people array, you can see how that gets a little bit harder to work with. We now have a list of immutable objects. So in order to be able to get those nested things to be immutable, we need to use this fromJS. So now when I go and run this, it's a little hard to see, but everything has been converted to those immutable data structures, list and map. Starting up at the top here, you can see we have a list, and inside that list I have a collection of maps. So here's the first map, then we have another map, and a third map, so you can start to get the idea, but then also inside those maps we have some nested lists. So givenNames is an array, so it is a list inside our map. Some of the people are going to have a nested map inside their map, so we have family here as a nested map. So you can see how by using fromJS we're able to get that immutability all the way down, everything inside there is converted into an immutable data structure. If we want to take a look just a little bit less data, and you can see here's what just one person looks like, because I'm using the first function on my immutable data structure. If I want to start to work with that, this is where I was saying that we can't just use the dot or the bracket notation, we have to use get. If I want to get the surname of the first person, then I call iPeople.first.get, and then I pass in the surname, and then I can pass in a default as well. So if there is no surname listed on the first person, I'm going to get none in parentheses. Same type of thing when we're working with maps and we're going to be going down into following a path down to the property we want to get to, we use get in. In this case, we pass in an array of path names or path values, so we're going into the family map and getting the fatherId property. And again, I can specify a default, but in this case, the fatherId in the family map for the first person has a value of 2, so I get that value instead of none. So that's a quick look at how we would go about working with some of the immutable data structures and creating some of those structures. We're going to be taking a look at that further as we work through the rest of the demos in this module.
Filtering on Top-Level Properties
Starting to work with some of these immutable data structures, we can go in and start to apply filtering. So you can see here we are working on our iPeople data structure that we had created earlier. And we're setting up this filterEq that takes a propName and a propVal and it returns a function that takes a data parameter and then applies just a regular filter to it. Pulls out anything where propName equals propVal. So we can set up that filter. We can also take this one step further, and this is manually currying these to set up filters for particular properties. So here you can see that I'm filtering, passing into my filterEq property name of married and a value of true, that's going to give me something back, a function that expects now to just be passed in data. So I can now use that married function to say give me just the married people. And there are the people that are married. So that's a way that we can start to apply some of the common operations that you typically need to do on datasets using the immutable data structures. It's not that different from anything we've seen working with arrays or anything we will see later working with the other libraries.
Filtering on Nested Properties
Continuing to look at filtering, we can also start to apply filters on paths. So we can work with those nested immutable objects. Here we set up a function filterPathEq, we pass in propPath as an array and propVal, and we return a function that takes a data parameter and applies the filter using itm.getIn and passes in that array path. So this is using getIn and a path and array of strings that takes us down to the property we want it to filter on. So we can set up a filter for fatherIdIs where it's going to return us a function that takes an ID and call that filterPathEq, passing in the path down to fatherId, family, fatherId, as an array of strings, and then the ID we want to filter on. So then we can take that one step further and say we frequently want to pull up the person whose fatherIdIs2, so we can set up a function that supplies the ID, and now all we have is a function that we need to pass in our dataset to. So we can call fatherIdIs2 and pass in our dataset and we're going to get back just the person whose fatherIdIs2. So again, taking a look at filtering, this time working off of nested maps inside our immutable data, we can still go in and do filtering on those and set up the functions to filter on specific property values nested inside our immutable data structure.
So far we've just been looking at filtering our immutable data structures. So how do we go about updating something? First thing we're going to need to do is actually find the item that we want to update. The index to that item inside our list is going to be part of the path, so we need to get the index of the item. So here you can see we set up a way to get that index. We're looking for, again, the person whose fatherIdIs2, so we're using the findIndex function on our immutable list iPeople, and it's going to give us back the first person whose fatherIdIs2. So now as I said, that's part of our path to the particular item that we want to update, because we want to get just that one thing now. So to actually do the update, we have a couple different ways we could do this. And remember that what we get back is going to be a different structure because we do not make any changes to our iPeople, we're going to be getting an iPeople2 as a different structure, and it's going to be a different list of people, and that will contain our changes. So we set up iPeople2, we do a check to see if the index is -1. If it is, we just get iPeople back unchanged, so iPeople and iPeople2 are going to be the same at that point. But if it's not -1, then we're going to use the setIn function on our lists, remember how I said we need to pass in the index, so that's the beginning of our path. That tells us which item inside the list are we going to be changing. So we pass in the index, and then we want to go to family and fatherId and change that to 321. So now if I take this and go ahead and run it, and I'm going to use that fatherId as 2 on iPeople2, I get back an empty list. Because iPeople2 doesn't have anybody with an ID of 2 for their fatherId because we've changed it. And that change is stored in iPeople2. If I take that same fatherId as 2 and run it on iPeople, my original list, then I'm going to get the person back, because that has not been modified, that's the idea behind immutable data. Now I want to call out also before we go any further that we have two options for changing a value. We've been looking at setIn, which allows us to specify the actual value that we want to change, so we're completely overriding what's there. We also have the option of doing updateIn. UpdateIn is going to give us the ability to work with the value that was there originally and apply some function to it. So here you can see that we're taking the value that it's there, 2, and we're adding 319 to it, that gets us to our same 321 value. And I did that just so we could use the same functions that we have set up here. But two different ways to go about changing an item inside our immutable. If we're just going to overwrite whatever is there where you setIn, if you want to use what's there to calculate a new value, then we can use updateIn and apply a function. We pass in that function that allows us to determine what the new value is. So continuing on, if we want to take a look at our actual change, we need to go into iPeople2. And that's where we see the changed value. So we've gone and made a change to our data structure, but we get back the changed structure. We saw above that the original structure is not changed. And that's the idea, like I said, to immutable data. Once we're done working with iPeople, if we don't use it anywhere else, the garbage collector is going to pick it up. So we don't need to worry about wasted memory. So that's a look at some of the basic operations for Immutable.js from Facebook. Like I said, if you wanted to dig into this, if this looks interesting, you want to take a look at that unofficial documentation before you start to take a look at the official documentation. The official documentation will make a lot more sense once you've gotten some experience that you can get through the unofficial documentation.
Ramda is the first of the truly functional libraries that we're looking at. We're going to get through all of the introductory material as quickly as we can so we can spend more time looking at some common operations in our demos to really help you get an understanding of what this library is all about. But I do want to spend just a little bit of time on some of the introductory stuff, just so that the code makes more sense later. Ramda builds itself as a practical functional library. It consists of over 200 different functions that cover a lot of useful capabilities. Information about Ramda is available at ramdajs.com. Ramda is essentially built on four pillars. If you know and understand these, you're well on your way to understanding Ramda. After this it's really just a matter of getting comfortable with the API. Those four pillars are immutability. All Ramda functions return a copy of data structures provided as parameters. The original structures are left unchanged. Automatic currying, though Ramda also provides a manual curry function if you need it. Side-effect free. As we discussed in the functional 101 earlier, Ramda's functions have no external dependencies and don't make changes to anything outside the function itself. And last, all Ramda functions are data-last, meaning that the collection or array of data they operate on is passed as the last parameter. This helps to facilitate the automatic currying, and as you'll see it's a powerful approach to simplifying your functional programming. When I was first learning about Ramda, two things that I read on their website resonated with me. This is the first, the API is king. We sacrifice a great deal of implementation elegance for even a slightly cleaner API. I like this because it's something I've long believed. The external face of your code is far more important than the internal workings. A really powerful API is one that's easy for implementers to use. Simplicity in the API is far more important than simplicity, cleverness, or elegance of the internal workings. And this is the second. This one struck a chord because I agree with the idea that adherence to an ideal or a spec is something worth striving for, but the real world has to intrude, and in the real world, no one actually cares how closely you adhere to any spec if your code is ugly, bloated, and slow. So Ramda strives for performance. A reliable and quick implementation wins over any notions of functional purity. If you haven't guessed, I like Ramda. I've used it on several projects. That doesn't mean I have blinders on about it, though. Like any library, Ramda has its strengths and weaknesses. Here's a look at what I see as the strengths and weaknesses of Ramda. Realizing that these are entirely subjective, let's start with the strengths First is the sheer breadth of the library. It has something north of 200 functions currently and covers just about everything I wish it would. Despite that, it still feels very focused. There isn't really anything I look at in the API and say it's an oddball, something that doesn't fit in with the rest. Consistency. The team has done a good job of maintaining the internal consistency of the library, which makes it significantly easier to learn despite its breadth. Once you get started with the library, getting comfortable with new functions is an incremental process, as everything is put together similarly. And last, the Ramda team itself. Like most, if not all, successful open source projects, there's a core group and then a larger group of supporters. The team is responsive and the project is actively maintained. And now the weaknesses. First, partially due to its breadth, the learning curve for Ramda is a bit steep, especially if you're simultaneously coming up to speed on functional programming. Another reason for the steep learning curve is the documentation. Don't get me wrong; the documentation is incredibly thorough and easy to work with once you're comfortable with Ramda and functional programming. Until then, it's a bit muddy. It's just API documentation, all of the different methods, but no real getting started type documentation that walks you through the process. Instead it relies on a collection of blog posts, some written by the core team members, but it still feels a little disjointed. There are also a couple of missing elements that I wish it included. I talked about these a bit back in the choosing a library module, but to recap the biggie here, there's no support for maybe. Sure, you can kind of work around it, but the benefits of maybe are just so strong that I wish Ramda included it instead of pointing you to Sanctuary. There was an implementation in the Ramda fantasy project, but it's since been abandoned. I keep thinking I'll submit a PR in my spare time, but I never quite seem to get around to it. The Ramda documentation itself is available at ramdajs.com/docs/. There's a couple of cool things I want to point out, so let's jump over and take a quick look before we start digging into our common operations. If you browse to ramdajs.com/docs/, this is what you're going to see. This is the Ramda API documentation. You can see on the left-hand side there's a list of all of the different functions available, and you can type in and filter if you wanted to look for something. And go in and take a look at that particular function. The other thing I like about the documentation is that it has a REPL available for you to examine each individual function. So I can choose, for example, looking at the add function, I can run it here, and it's going to use RunKit to show me the output, so the output is 17. Or if I want to go in and take a look at that in a way that I can go through and edit it, I can open it in the REPL. And that's going to evaluate that code and show me the output. And this a live dev environment, so if I wanted to change something, I can go ahead and do that. And you can see that things get changed over in the output window. So this is a really nice way as you're going through and taking a look at the documentation to be able to bring it up in a live dev environment and play around with it to start to get a feel for how the different functions work.
Getting Started and Common Operations
Before looking at the common operations for Ramda, we need to get it installed and configured in our environment. Fortunately, that's really easy. Install with yarn or npm. In Node set it up for a require or in a browser environment a simple script tag. And then you're all set to go, including IntelliSense. If you're using TypeScript or Flow, there are typings files available, too. Real quick, the other nice thing to know about Ramda is that it has a decent ecosystem. Whether that's in the form of separate projects like Ramda-adjunct, a collection of sample functions using Ramda in the Ramda Cookbook, the type definitions I just mentioned, or a nice documentation page that tries to guide you to the appropriate Ramda function, essentially grouping them by functional area. We're going to take a look at those functional areas, there's about a half a dozen of them, and the 200 functions in Ramda can be broken down and slotted into those different categories. Over the course of the next few demos, we're going to take a look at the examples from each of the buckets, but we're going to focus mostly on the top three, lists, objects, and logic. We'll see examples from the bottom ones, but again, the focus is mostly on the top three. Okay, now we can dive in and start looking at Ramda in action. Let's go!
Over in our demo environment, you can see I've got a file set up for Ramda here. And at the top I'm just requiring Ramda into a constant r. I'm pulling in all of the people, data, that we're going to be working with throughout all of these demos. It's just a simple JSON file with some sample demo information that we've generated for our application that my company is building, and you can see it's just information about people. We have a couple of regions set up just to clear the console in between runs and set up our output variable, and then at the end here just to log out whatever the value of that output variable is. So all the code that we're going to be writing is going to get dropped right in the middle here, and all it's going to do is write out to our console whatever we've put into that output variable. So let's go ahead and drop our first code sample in. In this case, we're just setting up some basic generic filters, filterEq and filterNEq. We're making use of the Ramda filter, as well as the propEq to go and grab values and filter on values that are passed in. So we pass in a propName and a propVal to the filterEq and it's going to give us back a curried function that all we have to do at the end is pass in the dataset that we want to filter across, and it will give us back any of the items out of that dataset where our property equaled the value that we specified. Slightly different for the not equals, we have that R.complement thrown in and that's going to flip the results of whatever function is passed into it. So in this case, it gives us the not equals where propName is not equal to propVal. Otherwise, propEq and propNEq are essentially the same. We have a quick little example of using that, and you see we need to pass in a couple of different things. We pass in our properly name married, we pass in a value of true, and filterEq gives us back a function that we then just need to pass in our dataset to. Now, we could include that dataset inside the filterEq call if we wanted to. We'd have to tweak a couple of things to make that work, but the idea behind currying is that we want to get to the point where we have these generic reusable functions. So that's what we're going to be building as we go through the demos in this section. So here we pass in people to the function that gets returned from filterEq, and if I set up node I'm going to use nodemon to keep restarting node as things chance. You can see we're starting with our no value, that's what was set up as the initial value of Alt. But if we come over and just save this, it's going to give us all of the people who are married, give us all of their records. So you can see everybody who comes out has a married value of true. That was 6 out of our 10 records. Next we're going to add some more specific filters.
Filtering, part Deux (Curried Functions)
So here we set up a couple of filters that use the filterEq and one at the end there uses the filterNEq. And you can see in this case we're passing in the property and the value that we want to filter on. We haven't passed in our dataset, so these are going to give us functions back that all we need to do is pass in the dataset and we'll get the filter that's been applied to that dataset. So in my example down at the bottom here, I'm passing in people, which is my dataset, to the married function, which I set up as filterEq with a property name of married and a property value of true. So if I go ahead and save this, nodemon's going to re-run, and I get my married true, the same six that I saw before. But now what if I want to flip that and say I want to take a look at unmarried people? Probably go ahead and save it, and now I get just the four records that represent unmarried people. Same thing if I wanted to take a look at just the women, save that, I get five women. I want to take a look at people who do not have a DNA test. And I get the five people without a DNA test. So you can see we're starting to build these, starting with the very generic function, and then we're building functions off of that that all we need to do is pass in some properties to, and we'll be able to start building out reusable, small, kind of very specific functions that we can pass any dataset into and get back a filtered result based on whatever filter function we're using, married, unmarried, women, men, etc.
Sorting Data (and More Currying)
Adding in some sorts, you can see we've just defined a couple of generic sort functions here, sortByProp and sortByPropDesc. Each one just takes a property name, uses the R.sortby and R.prop where we pass in the property name that we want to sort by. Our .prop will then give us the value out of that property name, and our .sortBy will sort by that property. In the first case, it does ascending. In the second case, we want to do descending, so we need to use compose, which we haven't looked at a demo of that yet, that'll be coming up in a moment. But basically this just gives us the ability to stitch together different functions that we've created. So we still have the R.sortBy, R.prop, et cetera, that we had in sortByProp, but then we have R.reverse, and R.reverse is going to flip that order. So that's going to give us the descending. So if I wanted to sort people by whether or not they're married, I can do sortByProp and just pass in the name of the property I want to sort on, married, and then I pass in my dataset. Go ahead and save that. It's going to give me all 10 of my people, but we're starting with married true down at the bottom here, and then if I scroll up to the top, we start to get into all of the not married or the married false. If I wanted to change this and flip that order, just go ahead and do that, and you can see down at the bottom here I'm getting married false, and now married true is up at the top. So you can start to see how we're building these very small, very specific functions that allow us to start to build functionality by stitching them together. That's one of the benefits of using composition and functional programming is this focus on very specific small units of functionality that you then build or tie together.
Although we saw a quick look at compose in the sort, let's go and add in an actual compose example now. So compose is going to evaluate from right to left, and I didn't mention that before, but I kind of touched upon it in the way I walked through the demo. So here we're going to use R.compose, but we're going to in the first case for marriedByAge, the first thing we do is actually the married at the end there. Compose is going to evaluate our functions from the right side back to the left. So it goes through and it applies the married function, and then it's going to apply the sortByPropDesc age and married by age is then going to give us obviously all of the married people sorted by their age descending. Similar type of thing for menWithDnaTest, it's going to apply the withDnaTest first, and then once that filter is done and has been applied, it will then call the men function and pass in the result from withDnaTest into the men function, and we'll get just men who have DNA tests. Now kind of taking it one step further, we're composing a number of different functions where we're getting married men with DNA tests by age. So again, compose starts at the end, it applies the men filter, then married, then withDnaTest, and then sorts by property age. So that's what we're going to take a look at, if I go and save this, we get the two men who are married, those that have DNA tests sorted by age. If I wanted to flip this, I go ahead and do that. If I wanted to change this to withoutDnaTest, this, let's see, I have just one, so the sorting doesn't really do anything here, I have one married man that does not have a DNA test. So this is composing a number of different functions together to get results out of our dataset that apply to all of the filters that were applied.
If having compose work from right to left seems a little offputting to you, then we can go in and instead use pipe. Pipe is the same as compose, except that it works left to right. So here we're getting the same thing, marriedMenWithDnaTestByAge, that's number 2, and we're using pipe. And you'll notice here that our arguments are flipped. Exactly the same as what we had before, but we do men first and then married and then withDnaTest, and then we sort by age. So the results are just like what we saw before. The only difference is that we're evaluating left to right for pipe instead of right to left for compose.
Retrieving Top-Level Property Values and Defaults
When we're working with our datasets, sometimes we're going to end up with objects that don't have a particular value. So, what's going to happen? We've seen this already where we're using Ramda to go in and get items and just pulling them out of our dataset. Here we're just using R.map to go and get a list of all birthplaces. Now, some people don't have birth places listed in our data. So you can see the second item there we have undefined. You can also notice down at the bottom Chicago is listed twice, we had two people who are born in Chicago. Well, we want to clean that up a little bit. We want to make it unique, we want to get rid of the undefined and put in something perhaps that's a little more user friendly, so how are we going to go and do that, because by default, we're just going to get back whatever the value of that property is, which includes undefined. So let's go in and take a look at a different example where we're using a function out of Ramda called propOr. This gives us the ability to grab a value, but if it's undefined, then we're going to be able to replace it with a default value. So here instead of undefined, we're going to get this not specified. So you can see it's just a little more user friendly, the undefined for user, A, it's not a string, so it may be harder for us to work with if we're trying to put it into the UI, and B, the user is not necessarily going to know what undefined is. So here we can specify a default where if a property isn't given a value, we can use R.propOr to specify a default in that case.
Retrieving Nested Property Values and Defaults
Similarly, if we're working with objects and we try to get something out of an object property that's nested, we're going to run into a problem with undefined. If we go in here and first try to get a value out of family.motherId, well, if motherId is sitting on family, we're fine, but if family isn't defined, we're going to get an error. So if I go ahead and run this, you can see we've got the type error, cannot read property 'motherId' of undefined, and that's because some of my people don't have a family property with a motherId off, they don't have a family property at all, so it's going to throw an error. So instead what we can do, let me comment this out, and uncomment these, we can use a function out of Ramda called path where we pass in an array of strings that is the path down to whatever property we're trying to get. And Ramda will handle the undefined, if one of those values isn't defined, it won't try to continue on and get to the properties that are further down in that path. So if family is defined, great, it'll go get motherId. If family is not defined on an instance in our data, then it's not going to go on and try to get motherId, so we won't get an undefined error. You'll see we do get a list of undefines here, but that's better than an error. We can take this one step further and use pathOr that like we saw with propOr is going to give us the ability to specify a more user-friendly value. So, same type of idea, but we're using pathOr instead of just path. We still specify the array of strings that give us the properties walking down to the one that we're interested in. If I go ahead and save this, you can see I still get the same value, 1, 3, 7, 9, but then I get not specified for anything that doesn't have a family and therefore doesn't have a motherId.
Finally, we're going to tie a lot of what we've seen using Ramda together going back to the birthplaces data point, so pull out a list of birthplaces that we could present in the UI that are much easier for a user to work with. So the first thing we're going to do is we're going to get rid of any duplicates. We have this function set up, uniquePlaces. It's going to use the Ramda compose. First it's going to get allBirthplacesOrDefault, so that was the function we set up up here to take out any undefines and replace it with not specified. Then it's going to pass that because we're using compose, it works right to left, so we go from allBirthplacesOrDefault, passes that into the unique function of Ramda, and gives us back then just a unique list of places. Next we set up just a filter not using Ramda, just using a regular arrow function that's going to give us back a Boolean, whether or not val is equal to not specified, and we're going to use that in the Ramda filter in just a moment. So then we set up our places list, again, using compose, so we start at the end. We're going to use uniquePlaces. Then we're going to take the results from uniquePlaces and pass that into R.filter using that isSpecified that's going to give us back a Boolean. So if it's equal to not specified, it's going to get dropped out at this point. So R.sort is then going to get just the birthplaces, unique birthplaces, that have a specified value. And sort here, we're not using our sorts that we set up earlier because those were set to work on objects, and at this point all we have is a string. We just have a list of strings, so we're just going to sort whether or not A is greater than B, and that's going to give us back a list of sorted birthplaces. So if I go ahead and save this, you can see I get my birthplaces sorted. Now just to show you what things look like at each step of the way, let me just go and take some of these out, and we start with our unsorted list of just the uniquePlaces, so the duplicate Chicago has been dropped, but we still have not specified in there. So let's take out just the sort, and now we get rid of both Chicago the duplicate and the not specified, but we haven't sorted anything. So we'll put in the sort back in, and that gets us back to our fully user ready list of birthplaces. Might present this in a pick list or something similar where they can see the list of places where people have been born sorted alphabetically without any duplicates, without any undefines or not specified. So that shows you how we can kind of tie together all these little, very specific functions, into something that gives us really a lot of expressive power to build what we need by stitching those functions together. And that gets us to the end of our look at some of the capabilities of Ramda. As I said in the slides, there's over 200 functions in this library. We took a look at maybe a dozen of them. So we barely scratched the surface. But it should be enough to give you an idea of what Ramda is all about and how it works to build these small, reusable functions. I highly encourage you to go take a look at Ramda if this seems like something that fits well with your approach to programming. I think it's a very well thought out library, and I think it's got a lot of really useful capabilities built into it.
Getting Started and Common Operations
Getting Folktale installed and ready to go is pretty standard. There are some additional instructions for using Webpack, Browserify, and more on the website, but the basic setup is a typical include the package, require the pieces, and start coding. Folktale is broken out into nine distinct areas, which makes you think that there's a lot more to the library than there really is. The fact is, though, that a lot of these areas are pretty small or entirely experimental. We're going to focus on maybe, result, and validation. Concurrency, FantasyLand, and data modeling are still experimental. Core/Lambda, Core/Object, and Conversions are partially experimental, or fairly sparse. Again, once the experimental label starts going away, this is going to be more compelling, but for now the maybe, result, and validation implementations are outstanding in making our code more composable. For each of these we're going to take a look at some slides, and then we're going to jump into some demos.
So Call Me Maybe
The next element we're going to take a look at is result. You can think of result as a replacement for if else statements. It's essentially the same as a maybe with result.ok in place of just, and result.error in place of nothing. So map, chain, orElse, getOrElse, and matchWith are the same as what we covered for the maybe. But result also includes two new capabilities, merge, which lets you simply ignore whether the result contains an ok or an error, and just grab whatever value is returned, and a mapError, which is analogous to map. It allows you to run a function on the error and have the return value from that error replace the value originally wrapped inside the error. Like map, you need to be aware of the possibility of nested results when using mapError. Let's go take a look at using a result in some code. The Folktale result is an interesting little construct because it gives us the ability to essentially replace an if/then structure with something that's more composable. Taking a look at our code here, for this particular example, I'm not actually using the people.json because I wanted to come up with an example that showed how we can continue executing, even when things fail. Or make it all the way through if everything works successfully. So, a little bit of a stilted demo here, but still I think gets the idea of the result across. So we have a function at the top appropriately called mayFail, generates a random number, and 9 times out of 10 it's going to return the ok. One time out of 10 it's going to fail. So when it returns okay, it says, made all, iterations successfully. If it fails, then it's going to tell us which iteration it failed on. Now when I come down here and take a look at the call, we have out = mayFail, so that calls it the first time, and then we have a series of chain functions that call mayFail again. So we call that function repeatedly; essentially I'm waiting for it to fail. Or get all the way through and succeed ok. Now remember that the chain is only going to execute on an ok result. So once we hit that first failure, it's going to skip down to the matchWith. So let's go ahead and save this. In this case, it failed on iteration 2. I'll just continue saving until we see, there we go, so it made it through all five iterations successfully. So the first time through on line 17 it made that call and succeeded, and then because that one succeeded, it called the first chain on line 18, and that one failed. So at that point, we had a result.error coming back from our mayFail, so the chain on 19, 20, and 21 don't execute, and we skip right down to the matchWith and we have our error. You can see if I refresh this, again, it failed on the second iteration. Let's see if I can get it to the point where it, there we go, all five iterations. This time it hit the first call to mayFail on 17,; 18, 19, 20, and 21 each execute because each one before it returned a success, and then the matchWith on line 22 executed and we had an ok that had come back from the mayFail on 21, so we get our value back, and the value in this case has made it through all 5 iterations successfully. So as I said before, that gives us the ability to replace a noncomposable if else with a much more composable structure. We can continue to tie these together knowing that it's going to be okay whether our calls succeed and return an ok or fail and return an error.
The last piece we're going to look at from Folktale is validation. Validation is very similar to result, except that result is a gatekeeper. When something fails using result, you immediately get moved down the failure path. Validation, on the other hand, is a collector. It collects all of the failures and allows you to react to them collectively. We'll see this in just a moment when we get to the demo, but first let's take a look at some of the functions available. Map, the same as what we've seen before and still success only. MapFailure, also still the same and failure only. MatchWith, both success and failure, same as what we saw already. And now the two that are unique to validation, first concat. Concat lets you combine validation, success, or failure. When combining failures, both are included in the final result. Combining two successes, you get just the final one included. Combining a success and a failure, only the failure is included. And last, collect. When you've defined multiple validation functions, collect allows you to combine all of the results into a single set. Now collecting concat might seem a little confusing, so let's go take a look at these in the demo and we'll get a better understanding of how they work. Taking a look at Folktale's validation, we're going to do this in three different snippets because there's a fair amount of code to run through this. Starting with setting up some of our validation functions, we're going to be checking for minimum length and no spaces. So min length first takes in a field name minLen and a value, and if the value passed in is greater than the minimum length, then we're going to return a validation.success and just return the value. If that validation fails, if value doesn't have a length, it's less than the minimum length or equal to the minimum length, then we're going to return a validation failure with a message of whatever the field name is, must be longer than whatever number of characters. So either success or failure gets returned from our validation function. No spaces is a little simpler, we take in the name of the field and value, we check to see whether or not the value contains a space. If it does not, then we return a success and just return the value. If it does contain a space, then we return a failure with the message their fieldName cannot contain a space. So that's setting up the functions that actually do the validation itself. Now we'll build up some validator-type functions, clean these up a little bit here. We start by checking if the password is valid. It's going to receive a password. And then we call into Folktale's validation capabilities. We start with a success, because remember, I said before that when you're combining validations, if you have a success and you add another success to it, the first success gets thrown away and only the second success gets passed along. If you have a success and you add on a failure, then the success gets thrown away and the failure gets carried along. If you have a failure and you add another failure or concatenate another failure onto it, then both failures get passed along. So we start from a success, we can concatenate onto that, the result of our call to noSpaces for the password. So that's going to come back as either a success or a failure. Regardless of what comes back, we concatenate onto the end of that, our call to minLength where we check the password. So that's going to determine whether or not password valid is going to be a success or a failure. Same type of thing for checking if the name is valid, we start from a success, concatenate onto it whatever the result of our noSpaces check is. So now we can go into the next snippet where we actually go and set the value of our out variable, which is what gets written onto the screen. We start by calling validation.collect, and this is going to allow us to get all of the results from passwordValid and nameValid, both of those checks, the results will be combined, and given back to us as one collection. So if I go ahead and run this, by saving, you can see we get back a success. And the value that gets displayed is in this case just the username. But if I go and change this, say, by putting a space into our password, you can see now I get back a failure that says the password can't contain a space. Well, now let me start putting multiple failures in where we have a space, let's do this. Now all of our validation fails, so we get back an array with three strings in it. Password failed on two cases and the name failed on one. Well, it's not very useful, you can't do much when you have everything wrapped up inside that failure, so let's see what things would look like when we uncomment this matchWith. So now we get a failure message and the valid is each of the strings that was returned as part of our failure. So if I start making things valid again, you can see the strings start to disappear. We're down to just one message, and now if I take the space out, then we'll go to success. So that gives us the ability to collect successes and failures as we go through and then deal with the collection of all of the results at the end. And that's the power of working with the validation. Very similar to the result, but like I said in the slides, the result is more of a gatekeeper. As soon as something fails with the result, you go down the failure path. Whereas with a validation, you continue to check your different conditions until you get to the end and work with everything at the end. So that wraps up our look at Folktale. We looked specifically at the maybe, the result, and the validation. There are other aspects to this library. A lot of them are still marked as experimental, so I didn't cover them because I wouldn't recommend them for production use, but this is one to keep your eye on. There's a lot of good things that I think are going to be coming out of this library in the not-too-distant future.
Getting Started and Common Operations
Over in Visual Studio Code, we have our fkit.js file open. We start by requiring FKit into a constant F. Then we get our data by requiring the people.json file. We've used this in other demos as well, it's just a collection of information, demo information, exported from our application. It just has information about people, but it's a nice collection of a little more real world data that we can work with. Inside our supporting regions here, we're just setting it up to give us some space, some white space in the console, and then set up our output variable. Below that we simply log out the output and some information about how many records we found, and again, give us a little bit of space. Code that we're going to write is going to go right in the middle here. Taking a look at our first snippet for FKit, we're going to set up some filters, so we have a filterEq and a filterNEq, equal and not equal. We're using FKit's curry function. It takes a function that expects three variables and passes that into FKit's filter. We then make use of FKit's compose, which we're going to take a look at a little bit later to get the property name using the function f.get, pass in the property name that we're looking for, and compare that to F.eq with the property value that was passed in. So compose ties those two together and causes the value of the get to be passed in to be used as the item to be compared with F.eq. And then we have our data at the end, which is the people data that we're going to be using as our dataset. Similar for filterNEq, except that instead of F.eq on line 12, we're using F.NEq to get the opposite. So we could set this up as a simple call on line 15, storing it in our out variable. FilterEq we're going to get everybody from the people collection who have a value of married set to true. We'll go ahead and save this and startup node with nodemon. We see the output is just all the married people, so the six records from our dataset where married is set to true.
Filtering, part 2 and Partial Application
In our next clip, we're going to go and set up some filters building on the equals and not equals filters that we had set up. And here we're using some partial application to only pass in two of the parameters. So in conjunction with the F.curry that we have up on lines 8 and 11, this is going to allow us to wait until we're ready to pass in the data, and then the function will actually execute. So we have married looking for a value married is true, unmarried, filterNEq married to true, women, men withoutDnaTest and withDnaTest, all specifying the property name and the value that we want to use for our filter. So you can see we have our example down here, withDnaTest is going to give me everybody from the people dataset that has a DNA test. If I go ahead and save that, nodemon's going to rerun things for me, and you can see I have the five people from my dataset that have a value in their dnaTestId field.
Next up we're going to add some sorting capabilities. You can see here we have a pretty standard sort function beginning on line 28 running through line 34, just comparing the whatever property name we pass in, comparing the a and b, returning 1, 0, or -1, depending on those values. So pretty basic sort function passed into FKit's sortBy. Same type of thing for sortByPropDesc, except that we've flipped our 1 and our -1, so we'll sort by descending there. If I go ahead and save this, we'll get all 10 of our records, but this time the bottom we have our unmarried people and at the top we have our married people. So it's sorted all the true for married and then all the false for married.
As we start to add more and more functions in here, you can see how things work in a functional mode of programming where we're building these tiny little functions that do one thing and then we tie them together to introduce the functionality that we need. And the way that we typically tie those together with FKit is with compose. Much like some of the other libraries we've looked at, compose is used to say take the results of these functions and pass them, basically chain them together. So here we're setting up a married by age using F.compose, and there it's going to first run the married filter, which we had set up above, take the results from the married filter, and pass it into sortByPropDesc and pass in the age property. So it'll sort our married people by age. Same type of thing for men with DNA test, and then kind of a big example, marriedMenWithDnaTestByAge. We're using compose, starts at the end, it says give me all the men, then filter that for men who are married, then filter that for married men with a DNA test, and sort by property age. So if I go and save this, we're going to get our two people who are married men with a DNA test sorted by age. If I flip this, we'll get the same results, but now they're by age descending. So you can see how we have these pretty generic functions at the beginning to do generic filtering and then filtering on a particular property, generic sorting based on a passed in property name, and we can tie them together using compose to introduce the functionality we're looking for. And again, compose, the thing you need to remember about that is it starts from the right and works its way to the left.
Continuing on, we're going to start working with individual properties, so here we're just getting all birthplaces, so each person record has a birthplace property, and we're just generating a list using F.map to apply this function, this arrow function to each of the items that we pass in in people. And that's going to give us back just an array of birthplaces. So when I save, you can see I get a list of the 10 birthplaces. But you'll notice a couple things. First of all, it's not sorted. We have an undefined in there, which means that somebody doesn't have a value for their birthplace, and we also have Chicago listed twice at the end. We've got two people born in Chicago. We might want to clean this up a little bit. So, let's go ahead and take a look at how we can use some other capabilities from FKit to go and clean up this list.
The next snippet we'll put in is going to set up an allBirthPlacesOrDefault function. Now we're going to use allBirthPlaces as the first function that gets executed from our compose. So, F.compose starts at the end, applies the allBirthPlaces filter to give me just that array of birthplace names, and then it uses F.map to go through and look for any place where the value or the birthplace value is undefined. And in that case, it replaces it with not specified. So now we've been able to specify a default value that's going to be a little more user friendly than undefined. The user is not necessarily going to know what an undefined is. So if we go ahead and save this, you can see that now we have not specified instead of undefined. So this has given us the ability to, like I said, specify that default when we need to. We still have a little work to do on this list, so let's go ahead and take a look at how we can make everything in the list unique. We'll get rid of one of the two Chicagos.
So the next snippet we're going to take a look at is going to give us the ability to get rid of duplicates in our list of birthplaces. We set up a function called uniques, and we're going to use FKit's curry and also a function called nubBy, and if you're curious, that comes from Haskell. And essentially nubBy is going to give us the ability to pass in a comparator function. So it's going to loop through everything in our dataset that gets passed into nubBy and pull out anything where this comparator function returns true. So if a is the item already in the list and we try to add in another item where we're comparing another item b that's the same, b is going to get excluded. So we set up uniquePropVals, again, using curry. We're going to curry a function that takes a function and then a collection of data, and will return to us the value when we apply that uniques function from line 69 to that function and that data. Down on line 72, we call uniquePropVals, we pass in the results that we got from allBirthPlacesOrDefault. On line 73, we're using nothing particular to FKit here, just a basic arrow function to give us anything where the value is not equal to not specified. So anything where that is true is going to be returned to us when we use the F.filter down on line 74. So we take our uniquePlaces as the first function that gets evaluated in our F.compose on line 74, the results of that get passed into our filter, and anything that is specified will remain in the list when the filter is done. We now have a function that we can pass, it's been curried down to the point where we can pass just our dataset into it. We store that in our out, and then that will get written out to the console. I go ahead and save this, and you can see not only did we get rid of not specified, we also get rid of the duplicate Chicago, IL. So an interesting way of tying these functions together getting all of our property values, all of our birthplaces, and then pulling out the duplicates, and then pulling out the ones that have a value of not specified, and returning just the list of those items that have been specified.
Nested Object Properties
The next thing we're going to take a look at with FKit is how do we handle nested object properties that may or may not exist? So here let me start by actually commenting out this code for just a moment, and putting this code in place. So here basically just to simple map, we're going to iterate over the collection that gets passed in and try to grab all of the mother IDs from the family property inside each item in our dataset. If I go ahead and save that, I get an error, because not every person specified in my people dataset has a family property. And if it doesn't have a family property, I'm going to get that error when I try to get to val.family.motherId. So this is the problem that we're solving here. Let me undo these changes. And now what we're doing is using the getIn function that FKit provides and we pass in an array that is the path to the property we want to get. So, instead of family.motherId, pass an array family and motherId, and we could continue on if there were additional path entries to get down to the item that we're looking for, or the property that we're looking for. We wrap all that up in our map so that it gets applied to every element coming in the dataset, and then we can just go ahead and save, and now you see we're going to get the four instances where we have a value and the six instances where we don't. So that's how we can go through and without having to put in a lot of null or undefined checks, we can get values out of nested properties.
Nested Object Default Values
The last thing we'll take a look at for FKit is similar to what we had seen earlier working with properties where we want to be able to specify a default value. So this is similar to what we saw in the last clip where we're using getIn and passing in the array of property names to walk through the object to get down to the property that we're looking for, in this case, family.motherId. But we also have inside the compose we put in another F.map that goes through and looks for anything that's not defined, so it's going to get those undefines, and replace them with not specified. So this is another way that we can specify a default if a particular object inside our collection doesn't have nested properties that we require. So I go ahead and save this, and you can see that instead of undefined I've now got the slightly more user friendly not specified. So that gets us to the end of what we're going to demo for FKit. As I said, a nice little library, it's got a lot of nice useful functions. I like the composability, I like the currying that it gives us, using it for filters is very straightforward. So there's a lot of power in this little library; I think it's something you probably ought to take a look at.
Getting Started and Common Operations
The initial process of getting started with Sanctuary is pretty typical. Yarn or NPM to get the bits, require the library, and start coding. There's also one other additional step to configure Sanctuary that we'll take a look at in the first demo in just a moment. Sanctuary has a somewhat dizzying number of categories for the functions and capabilities it provides. There's obviously no way we're going to cover all of these in this overview of the library, so we're going to focus on filtering, which Sanctuary has under Fantasy Land, composition, and maybe. We'll also cover that extra configuration step I mentioned, so let's go take a look at the demos.
Setting Up the Environment and Filtering
Getting started with Sanctuary, there's one additional step that we need to go through that we didn't have to do for any of the other libraries. And we just need to configure our environment. The reason for this is that Sanctuary includes a pretty extensive runtime type checking capability, which is really nice for development, but probably not something you want running in production, because it's just going to take extra cycles, it's just extra overhead at that point, but while you're doing active development, it gives you the ability to get some more detailed error messages, among other things. So when we're setting this up, we start by requiring and env or environment from Sanctuary. We then have our standard require to get our people data; we'll take a look at that in just a second, that's not Sanctuary-specific. And then we call create to set up the Sanctuary environment. And we'd have to pass in two parameters, checkTypes is true, that's going to enable the runtime type checking, so we have that on while we're doing development, and then we set the environment equal to the default environment coming out of Sanctuary. If we're going to go into production, we probably want to change this to false to disable that runtime type checking. Like I said, it's just extra overhead. Once we have that set up, our Sanctuary environment is now initialized and ready to go. Real quickly looking at the rest of what we have going on here, our people file is just a json file with information about made-up people, fictional information. We're going to use that as our dataset for doing our queries. We also have a couple of collapsed regions here, we're giving it some space on the console, we're setting up our out parameter, and we're setting it to no output by default, that's going to be what's written out to the node console. And you can see here is where we write it out, and again, just give us some additional information about the number of records, and then some space to be able to see the results a little more clearly. Once I go over here I'm going to start up node with nodemon, point it at my Sanctuary file, and it's just going to go ahead and run, and I get my default no output. So now we're ready to start putting our snippets in. We'll start with some basic filters as we've done with some of the other libraries. So we set up a filter to filter on a particular property, looking for a particular value. We use the Sanctuary filter function, and essentially just an arrow function that says each item we're going to include it if the propName is equal to the propVal. Similar type of thing for not equal, except we do the item propName not equal to propVal. So that's going to set up a function using the Sanctuary filter that we can then just pass in the information that we want to sort on, so we pass in married, and we're looking for a value of true, but then we also pass in the people or our dataset. So now we're going to get back, if I save this, nodemon will run, and we'll get back the people from our people dataset who are married, who have a married value of true. If we take a look at our next set of functions, or filter functions, we can take that one step further and set up specific targeted functions for individual properties. So we can say give me a function that's going to accept then just the dataset, and it's going to apply a married equals true filter to it using filterEq and hard-coding in married and true. Same thing for married an false to give me unmarried. We can do it on the other properties as well, non-Boolean properties. So we can say give me everything where the gender is equal to female, and that's going to be a function that I'll call women; the opposite for men. Same type of thing for without a DNA test or with a DNA test. For the with a DNA test, we are using the filter not equal to get everything that's not blank. So now if I want to get the married people out of my dataset, I can use that married function, and all I need to pass in is the dataset. Go ahead and save and I get those six records. If I want to get just the men, save it, and I get just the men. So we're starting to build, the same as we've done with some other functions, we're starting to build out these curried functions using some of the capabilities of Sanctuary to allow us to filter our dataset.
Next we'll take a look at doing some sorting. So we'll put a couple of sort functions in. We have sortByProp. It's a function that takes a property name and then it sorts by the collection that's returned from S.prop and we pass in the property name. So S.prop is going to go through all of the items inside the collection that gets passed to it, and it's going to pull out the values for that property name. SortBy is then going to take that collection and sort it. In this case, it'll sort it alphabetically ascending because we don't specify a sort function. Similar for sortByPropDesc, we're going to take a propName, but then we need to do a little additional work, so we need to compose. We're going to take a look at compose a little bit later in more detail, but it's going to allow us to apply multiple functions in sequence. It's going to start on the right-hand side, which is the same as what we saw for sortByProp, so we've got the collection of values out of that property name, sort them, and then it gets passed into S.reverse, which flips the sort order, as you would expect. So that's going to give us sortByPropDesc. If I go ahead and save this for a sortByProp and pass in a value of married, and my dataset of people, I look over here I start on the bottom with my married as true. As I scroll up towards the top of my list, I start to get married as false. So they're sorted by that married value. If I wanted to see this go the other way, go ahead and save it, I get the same 10 records, but now I have married false at the bottom and married true up top.
Moving on, we'll take a look at Sanctuary's compose capabilities, starting with the actual compose function. Now things are a little bit different than what we had seen in, for example, Ramda's compose function. When we're composing with Sanctuary, it takes two parameters. So we can compose two functions at a time. So we can set up a marriedByAge where we're composing our married function, again, starting from right and coming to left for compose, so we compose the married function and our sortByPropDesc we pass in the property age. That's going to give us back married people sorted by age descending. We can set up another dual function composing. We can say give me the people with DNA tests and then filter that additionally by passing in the men filter. So it'll first apply the withDnaTest, and then it'll take the results of that and pass it into men and give us then just the men with DNA tests. If we want to move onto doing multiple function composition, we need to nest our compose functions. So marriedMenWithDnaTestByAge we need to do S.compose, and then inside that we do another S.compose, where we put together withDnaTest and marriedByAge and men. So the way this is going to be evaluated is we'll first evaluate the last function in the compose, so men, and then we'll take the results of that and pass that into the initial compose, which will filter for DNA test and then sort married by age. That'll give us married men with DNA tests sorted by age. If I go ahead and save this, we can see that's what we get. We have two married men with DNA tests and they're sorted by age, starting with the oldest and working our way down.
Sanctuary's pipe function is going to be similar to compose with two distinct differences. The first is that pipe is going to operate left to right, and the second is that it allows us to pass in an array of functions and it will operate on all of them. So whereas compose was limited to two functions at a time, pipe we can pass in an array of however many we want. So in our example here of marriedMenWithDnaTestByAge2, we're calling S.pipe and passing an array of marriedByAge, withDnaTest, and men. Each of those filters will be applied in that order. So we'll get married and sorted by age, in this case descending, that's the way that function is set up, and then it will take the results of that and pass it into withDnaTest, and then it'll take the results of that and pass it into men, and the output we're going to get is going to be similar to what we saw before, but we did it in without nesting our compose calls. So we have our married men who have DNA tests sorted by age descending.
The last thing we're going to take a look at for Sanctuary is working with their maybe monad. So we'll paste in our code. It's going to be a little bit different from what we saw when we worked with the maybe in Folktale. There we talked about how we were creating a maybe. Here we're going to work with how we get data out of a maybe that Sanctuary provides to us. So we're going to set up a couple of filters. We're going to set up an age filter using the filterEq that we had set up earlier. We're going to use Sanctuary's curry, specifically curry2, to take 2 parameters required by a function and distill it down to just 1 that we can then call and pass in our data. That will give us the first element coming out of our collection of data that matches our filters. We set up an is68 using that curried function first, and we pass in a predicate of isAge using the filter we set up on line 49 and say give me anybody whose age is 68. The function first, defined on line 50, is going to essentially just extract the first element out of the array of people who are 68 years old. We set that up as our out variable. And the last thing we need to do is pass in our people because that's the way we have the currying set up. Is68 people is going to give us the first person from our people list that it encounters who has their age set to 68. So if I go ahead and save this, it's going to give me a just with the nested object inside that is my person. Well, that's great, but I want to get to the actual information. I don't want to work with the just, I want to get the information that was returned from my function. So to do that, I'm going to make use of some additional capabilities in Sanctuary. Specifically I'm going to say I'm going to use the fromMaybe function to open up that just and pull the information out. Specifically what I'm going to be looking for is the surname property. So I use S.prop, the property I want is surname, and it's going to get it from ultimately is68, passing in my people dataset, but the fromMaybe is going to pull the person object out of that just and allow me to work with that. Because if I tried it without the S.fromMaybe, then S.prop wouldn't be able to get at the data inside the just. I would get an error that said that the object doesn't have a surname property, because just doesn't have a surname. It's the user object that's inside my just that has a surname property. Now fromMaybe also gives me the ability to specify a default. So if there is no surname on whatever comes out of my is68, then I'm going to get a surname of none. If I go ahead and save this, I'm just going to get that one record. This can be the first person who had an age of 68, just their surname. So that's working with the maybe from a slightly different angle than we had seen previously. Setting up a maybe would be similar to what we saw in Folktale, but this gives us an idea of going in and pulling the information out to be able to work with the actual information once that maybe has been unwrapped. So that's a look at working with some of the capabilities of the Sanctuary library. There's a lot of capabilities in Sanctuary. If this peaks your interest, I would say it's worth your time to go ahead and start to dig into it. If you're looking for more information on Sanctuary, the portion in the slides gave you the URL to go and start looking at their documentation, which does include a REPL, so you can actually do some of the coding live right in their website; you don't need to necessarily set up your whole separate environment if you just want to kick the tires a little bit.
Getting Started and Common Operations
Maybe, Either, and Validation
First let's take a look at the maybe monad. Conceptually this is exactly like the demos of maybe I did in the module on Folktale, but there are some slight syntactical differences. A maybe is essentially a wrapper around either a value or nothing. The benefit is that you can always return a maybe from any given function and then let the calling code unwrap it and act accordingly, on either the value or the fact that it's a nothing, or in Monet terms, none. In the example here, we're calling a function that returns something or else undefined. Maybe this is a call to retrieve a DOM element, so we can't easily control the return value. From our function, though, we can control the return value. If rv has a value, we wrap it in a Maybe.Some and return it. If rv is undefined, we return a Maybe.None. Whatever receives this return value can be guaranteed to get a maybe back, it just needs to unwrap the maybe to get to the result. A related monad is either. Like the maybe, it will always contain one of two values. This time, though, they're considered right, which is the non-error condition, or left, which is the error condition. Either.Right is the same as Maybe.Some for all intents and purposes, and Result.Left is equivalent to Maybe.None. The difference is that for either left can have a value, typically an error message. Maybe.None can't have a value, it just is. Like maybe, either is conceptually identical, but syntactically different from Folktale. So you can take a look at the Folktale demo clips or on the Monet website if you want to see it in action. In the code here, using the same getSomethingOrUndefined function we mentioned in the previous slide, we're going to return rv if it isn't undefined, or else the error message stating that rv is undefined. This is a composable way to handle errors that you can't readily do with a try catch. Finally, we'll cover the monad-like validation structure. This one is monad-like for some obscure technical reasons that really aren't relevant, but we can lump it in here with our monads anyway. Like Folktale, Monet's validation is going to either contain a success or a collection of failures. It's also similar to either, except that it can contain an accumulated list of failures. So essentially you can chain a bunch of validation tests together. If they all succeed, the resulting validation structure will contain the final success. If any fail, the validation will throw out any successes that had already been recorded and begin accumulating failures. Even if later functions succeed, once there's a failure in the validation, subsequent successes are ignored. Again, you can see conceptually identical examples of this in the Folktale demo, and then look at the syntax on Monet's website. In this quick example on the screen, we start with a success and then apply our validation functions. If both validationFunc1 and validationFunc2 succeed, we'll have a validation.success. If one or both fail, we'll have a validation.fail, containing the information about each failed validation function.
Compose, Curry AndThen
The other functions part of Monet is where we find the compose method. Compose is like we've seen before, it allows you to combine functions so that the output of one function is passed in as the input for the next function. Other functions also include curry, which allows you to start doing partial application of your functions, and then AndThen function. We've seen the first two of these already. We talked about them in generic terms and then in terms of the libraries that support them, so I'm really just going to go into a little more detail on the last one. Fortunately, it's pretty simple. AndThen is like pipe from Ramda. It evaluates the list of functions that you're trying to compose together, but it does it from left to right instead of what compose does, which is right to left. We saw that with the pipe function in Ramda, and like I said, it's essentially compose, but the order of the operations is flipped.
Heads and Tails - Immutable Lists
To get started with Monet, the first thing you're going to do is require the library here we stored in a constant M. You could also require the individual pieces if you're not using the whole library. The next thing we're going to do is bring our data in, as with all of the other demos working with this people.json file, just some sample data about people. You can see there's, well, there's 10 people in here and we've got some information on first name, given name, surname, age, et cetera. So it just gives us a little bit more real-world data to work with. We can start to query and work with that information. Back in Monet, we've got a couple of collapsed regions here. You can see we're clearing out the console. We set up an out variable, that's what we're going to end up writing out to the console. For this particular demo, I'm setting up a couple of new people. We'll be looking at inserting those later, so you can see it's just individual records about people that we're going to insert into our Monet collections. Down at the bottom we just have another region that logs out whatever we've put into that out variable, and then gives us a little space on the console. So that's kind of the getting started pieces for this demo. I also wanted to start with a look at how Monet structures their immutable lists, because it's a little bit different from other things that we've seen and it may not be exactly what you expect, and it's important to understand this in order to work with the library. So each list, each immutable list in Monet, is made up of the first element called head, and then everything else in the list, which is the tail. So in our examples here, we have an immutable list that contains the numbers 1, 2, and 3. In the first example, 1 is the head and 2 and 3 are the tail for that first immutable list. But then 2 and 3 are actually separate immutable lists. And you can see I have it kind of broken out where in the second immutable list, 2 is the head and anything below that is the tail. So as you build out an immutable list, what you're really doing is saying I've got the head object and then I've got everything else, which is the tail. And then for the third element in the list, we have 3 as the head and nil as the tail, so that indicates that we've gotten to the end of this overall list. So each list is going to have a head and then either another list, another immutable list, which is the tail, and potentially contains nested immutable lists, or it's going to contain nil, which indicates the end of that immutable list. So we'll be working with some of this as we go through and taking a look at how we work with Monet.
Creating Immutable Lists
With that out of the way, let's go and create our first immutable list. So there's a couple of different ways we can go about doing this. If we have an existing array, such as our people array that we pulled in from the people.json, we can do the Monet or M.List.fromArray and pass in that array. If we wanted to get that back again to a regular array, we would just call toArray on it. Now let me just show you what that looks like if I save that and then launch node with nodemon, point it at my Monet file, that's just going to give me back the contents of my people.json file. If I uncomment this and save it again, we're going to see this is what an empty list looks like. So it's just a list with no objects in it. So we could now go and start to put things into that list now that we've created it.
Filtering Immutable Lists
Adding Data to Immutable Lists
When you're working with the immutable data structures provided by Monet, the head is the first item, and then the tail is everything else. One of the implications of that is you can't easily inject something into the middle of that. Now you could by tearing the whole immutable list apart and rebuilding it with that thing in the middle, but by default you're going to be just adding things to the beginning, so adding a new head, or you're going to be attaching a new tail onto the end. So let's take a look at how we would go about doing that. First we're going to add a new head at the beginning. So we set up a consList and we use our people list we call the .cons function, and pass in just a single newPerson1 we want as the head of the resulting list. Now, pplList is not changed by list. Remember, it's an immutable structure. So although newPerson1 is the beginning of my new list, pplList is unchanged. So if I go ahead and save this, we have this new person added at the beginning, and then the rest of what was originally pplList is the tail. If I were to come over here and say just toArray, so here is the original pplList with Henry Louis Jones as the head. You can see I have 10 records. If I take that out again, I now have 11 records starting with the new person that I pre-pended, and then here is Henry Louis Jones, the beginning of the tail. If I wanted to attach something onto the end, I need to attach a whole new tail. Remember I said earlier how the tail is another immutable list. If I go ahead and put in this next snippet, you can see we're going to, again, append onto pplList, but we need to create an immutable list first. So we're going to create a new immutable list out of the array containing newPerson2 and newPerson3 using that M.List.fromArray. And then we're going to just take all of that once it's been appended onto pplList and store it in my appList. So if I go ahead and save this, I now have 12 people. Now that might seem odd at first. Let's take a look at this. Why do I only have 12? Well, I come back up here and I have Henry Louis Jones. I'm starting with Henry Louis Jones and I'm ending with the two new people that I just appended. And the reason for that is because pplList up here was unchanged. So when I use it again on line 69, pplList does not have that first person that I pre-pended on line 66. If I wanted to get a list with all of those people with newPerson1 at the beginning and newPerson2 and 3 at the end, I would need to change this like this. Then if I go ahead and save, I now have the full list. But that's just showing that my pplList is not changed, and I get a new return value that gets stored in consList. So now I have ending with the two people that I put on as the new tail and up at the top beginning with the person that I put on as the new head.
headMaybe() and Empty Lists
Besides filtering, how else can I go about getting items out of this list? Well, it depends on the position of the item I'm trying to get. If I want to get just the first item, that's easy. I know that the first item is always going to be referenced by head. So a couple different ways I can go about doing this. Let me comment out a couple of these and we'll take a look at the first one. If I just want to go in and get the first item, this should be Henry Louis Jones. So if I go ahead and save, the head of my pplList, the first item in there, is that Henry Louis Jones. Now if I'm not sure whether or not there is anything in the list, it might be an empty list, then I can use this headMaybe. So you can see here on line 78 I'm creating a new immutable list, but I'm creating it from an empty array. So there's no head or tail, it's just empty. So I can use the headMaybe and you can see I get nothing back. So that's a way to work with a list that is potentially empty. We'll see a different way in just a little bit, but now if I say, well, I want to get something out of a list and I want to get the actual value, well, then we can use the headMaybe, then I'm going to get the actual value with the .some. So I go ahead and save that, and here is the value from inside my list, this is the item that is the head of that list. Now, I can also use a .orSome to specify a default value if there is nothing in the list. So here I'm again creating an immutable list from an empty array, so headMaybe returns nothing. So then I can put the .orSome and supply a default value, take some action, call another function, whatever it is I need to do if that immutable list was empty.
Introducing Non-empty Lists
So sometimes you might look at doing the whole headMaybe and orSome and some of the other capabilities that Monet provides for working with lists that may not have something in it might be too much. So they provide an interesting construct called a non-empty list. If we take a look at creating a non-empty list, which is often abbreviated in the API as just NEL, if I come in, let's actually start with this first one, we're going to first try to create a non-empty list and pass in undefined as the head. What we're going to get in that case is an error. Because I can't pass in, I can't create an empty non-empty list, and having an undefined head means that it's an empty list. Kind of goes against the grain for what we're calling a non-empty list. It doesn't mean, however, that there needs to be anything actually in the list. So I can create a non-empty list out of an empty array, and the head of the list is that empty array. So if I go ahead and save this, everything works and I don't get my error anymore, and now if I come down and try to go access the head of that non-empty list, I'm going to get back that empty array. So non-empty, it's a little bit of a misnomer; it doesn't mean it actually always has actual content, it just means that I'm not going to get undefined if I try to access it. And we'll be exploring this over the next couple of demos and seeing some of the ways that we can work with the non-empty lists. The full constructor for a non-empty list allows me to specify either just the head as I did on lines 89 and 90, or I can specify the head and the tail. So here I'm going to create a new non-empty list with the first element out of my pplList and then the rest of my pplList, so .head and .tail. If I take a look at this, it's got a whole bunch of stuff in there. Let's see what that stuff actually is by popping toArray on the end here. If I go ahead and save, that's my pplList. I pulled out the first item, it should be my Henry Louis Jones, and that's the head, and then everything else is just the tail. So if I want to go one step further, let me take out a couple more comments here, I'm going to explicitly create an empty tail or a nil tail, and an empty array as the head, and then create a new non-empty list with the two of those. If I go ahead and save, I get my non-empty list, but if I went in and did head, I get my empty array. If I go in and take a look at the tail, I get nil. Okay, so that is the new list that I created with an empty array as the head and then a nil tail.
Non-empty Lists - Guaranteed head() and tail()
Exploring the head and tail of my non-empty list a little bit further, we already saw how we can create a non-empty list out of an empty array and get the empty array as the head. We can contrast that with, if I try to create just an immutable list out of an empty array and go to get the head, it's going to come back as undefined. So that's what the non-empty list gets me is the ability to have an empty array still a valid head in my list. So to kind of sum things up for our non-empty list, if I have a regular immutable list that I create with fromArray, I can access the head, the first object in it. Same thing with the tail. But if I try to do that with an empty array or an empty list, then trying to access the head or the tail is going to throw an error. The nice thing about the non-empty list is that I can always access the head and the tail, I'm always going to get something back. Now, it may be nil, it may be an empty array, but there's always going to be something there; I won't get an undefined error. So that's a look at some of the capabilities of the library Monet, specifically looking at immutable lists and non-empty lists. We saw a little bit of use of maybe in there and some of the other capabilities that Monet provides. I recommend that if this kind of peaks your interest, you go take a look at the documentation on the Monet website and dig into this a little bit further.
Using Functional Libraries in Popular Frameworks
What We Do
The Money Slide
Released1 Jun 2018