Functional compositions are purposely opaque, with no obvious way to "visualize" the data as it transforms. This is great when our function works, as it's hard to add bugs, but challenging when our compositions aren't correct. This lesson teaches you how to debug functional compositions by writing and using a trace()
function to log out our values as they transform. This allows us to continue using pointfree programming while also providing a view into our compositions.
Instructor: [00:00] Compositions of pure point free functions can often be opaque and difficult to debug. Let me illustrate that with an example. I'm going to create a function slugify that will take these book titles and turn them into URL slugs. I'm going to purposefully put a bug in the code. Now let's try logging out the result. We get a huge error.
[00:31] What we see is that string.toLowercase is not a function, which probably means that when we get to lower case, the value getting passed in isn't a string. What we need is a function that gives us a side-effect of being able to log out the current value.
[00:47] We can do this by creating a trace function. Trace receives a message as its first augment, and then the value getting passed to it, and now we're going to use the comma operator to log out our message and value, and return the value.
[01:02] Now we can place traces before and after functions in our composition in order to see the value transform step by step. We'll place a trace before we split. Remember with compositions we work from right to left, or bottom to up, hence why that one's before.
[01:21] We'll call this one afterSplit, and finally we'll put one after lower case. We'll save this and run it in the terminal again. We still got our error, but now we can see a little more information about what's taken place.
[01:36] Before split we have our array of book titles, and after split we actually have a two-dimensional array. What happened was that split took our strings. Split them at spaces, and made them arrays themselves, and our map.lowercase isn't working, because lowercase expects a string, and what it's getting is an array.
[01:56] What we can do is reverse the arguments that we have of map.split and map.lowercase. Now I'll rename my trace functions, I'll save it, and I'll run it again. All right, we didn't get an error, but I didn't get slugs at the bottom either. Let's look at our trace one more time.
[02:15] Before lowercase we have the array of titles like we would expect. After lowercase, we still have a one-dimensional array of lowercase strings. After the split, it looks like we now have a two-dimensional array each one was split at their space, and now we have each string individually.
[02:34] What happened when we called join on this array, is we actually put the hyphen between the last and the first values of each array. What we need to do, is we need to call a map.onJoin as well. We'll come back to our code, we'll add map here, we'll save this, and we'll run our code again.
[02:52] You can see we now have an array of slugs exactly like we expected. We can now clean up our traces knowing that our function works the way we expected. Save it, run it, nothing should have changed, we still have our array. Now you might notice we're calling map on all of these, so how could we make this better?
[03:10] We can make a composition of the functions and pass it once into map. We'll save that, and we'll run it in our terminal, and we get the same answer.
Hey Haroen, you are correct that the final output could be refactored one step further by removing the outer compose
since we're only passing one argument to compose
. In truth, it just didn't occur to me at the time to take it that one step further. Fortunately, there's nothing mathematically invalid about having a redundant compose this way. Good catch.
Now, just to be clear to others, if they didn't understand what took place in that final step, because our composition was three map
s over individual functions, we could compose those individual functions into a new function and pass that into a single map
.
So what starts as:
const slugify = compose(
map(join('-')),
map(split(' ')),
map(lowerCase)
)
Can be:
const slugify = map(compose(join('-'), split(' '), lowerCase))
Thanks, that makes sense!
Thanks so much Kyle, this was a nice and comprehensive course At this point, if you guys want to know a little more about functional programming I recomend you the free book 'Mostly adequate guide to FP (in javascript)' https://github.com/MostlyAdequate/mostly-adequate-guide
Hi Kyle, very helpful course, can you explain what is the feature you used as (console.log(msg, x), x) . Why does this returns the x after comma?
Hey Hakan, good question I was also wondering, check out this: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comma_Operator
Hi Kyle, very helpful course, can you explain what is the feature you used as (console.log(msg, x), x) . Why does this returns the x after comma?
Hi Hakan, we need "x" param after console.log to pass forward "x" value through the composition since the console.log just log the value and nothing to return
Hey Hakan, good question I was also wondering, check out this: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comma_Operator
Hi Hakan, we need "x" param after console.log to pass forward "x" value through the composition since the console.log just log the value and nothing to return
Thanks Kostiantyn and Florian, didn't know about this comma operator.
Thanks Kyle! Really a good explanation of how to use functional programming. I have read a lot about it, and this goes from theory into practice. It would be great to see more, like examples of implementation in a web app.
Thoroughly enjoyed the course. Every example was simple, very clean and easy to understand. Comma operator
and point-free programming
were two interesting things that I learned apart from the core JS functional concepts! Loved it.
In the last example, why is the root
map
wrapped in acompose
?