This is a searchable description of the content of a live stream recording, specifically "Ep.43 - SAP TechEd Functional Programming recap" in the "Hands-on SAP dev with qmacro" series. There are links directly to specific highlights in the video recording. For links to annotations of other episodes, please see the "Catch the replays" section of the series blog post.
This episode was streamed live on Tue 19 Nov 2019 and is approximately 90 minutes in length. The stream recording is
available on YouTube.
Brief synopsis: At SAP TechEd 2019 in Bangalore we had this session "Write Solid Code with Functional Programming Techniques" where, using JS, we covered some functional programming aspects such as higher order functions, composition and reuse, partial application and immutability. It was a popular session and was repeated the next day. This episode gives us all the chance to look at the content of this session, and some related FP goodness.
00:04:30 An overview of what this episode is going to cover, including a recap of my Community Theatre session at SAP TechEd 2019 Bangalore "
Write Solid Code with Functional Programming Techniques" which was repeated the next day due to popularity, which was nice. A quick shoutout to to my colleague
mynyna.chau who ran the Community Theatre really well last week.
00:07:00 Reminded by A couple of my documents that I shared, covering more beginner functional programming topics:
00:07:30 Looking at the
tweet that
rsletta shared, describing a way to turn an array of records into a map (object) of those records, keyed by the value of a given property - this is also something that we are to cover in this episode.
00:07:50 A quick mention of
Code Sandbox which seems a great online code editor for lots of web dev goodness.
00:12:00 Not to forget that
Advent Of Code is almost upon us! We used a puzzle from Advent Of Code to warm our brains up at the very start of this series - if you're interested, check out
Episode 0!
00:13:20 I think I've mentioned this video before, but it's worth mentioning again, which I did at this point - a great talk by Brian Lonsdorf (aka
Dr Boolean) "
Hey Underscore, You're Doing It Wrong!".
00:14:20 Finally, a quick pointer to a video on my own YouTube channel, "
Implementing 'partition' three ways in JS and Ramda". Don't forget to please consider subscribing to
my YouTube channel and let's see if I can get to 1000 subscribers by the end of the year!
00:15:30 OK, starting with the
Northwind Products that we'll use as a small data set for our experiments.
00:17:50 Starting to work on producing a list of discontinued items for sale, with reduced prices, using a more "traditional" ("old fashioned"?) approach, with a
for
loop.
During this part, we see how many difficults we encounter, or rather cause for ourselves. Code and data that moves, changes. Worse, perhaps, is that we're having to instruct the computer
how to work through a list, rather than simply
what we want it to give us. But the ultimate issue is that we're clobbering data "upstream", our source of truth data set, without even realising! Not good, not now, and not further down the line when our program has grown and things are going wrong and we can't work out why.
Here's what the code looks like (this is the code that we
don't like!):
aSaleItems = []
for (var i = 0; i < Products.length; i++) {
var product = Products[i]
if (product.Discontinued === true) {
product.UnitPrice = Math.round(product.UnitPrice * 0.9)
aSaleItems.push(product)
}
}
00:25:40 A small digression on the meaning of 'idempotent', which is an important concept in REST (and how idempotency is different from having side-effects).
00:27:50 It's clear that we don't want this, we want more "solid-state" programming. And for that we can adopt some functional programming techniques, which we start to have a look at now.
00:28:50 I explain the concepts that we covered in the Community Theatre talk, and
chris.whealy kindly writes them down in the chat for me (thanks Chris!) ready to cover in this episode now:
- higher order functions
- reuse and chaining
- partial application
- immutability
00:31:10 Talking about the "
triumvirate" of functions
map
,
filter
and
reduce
and pointing out that
reduce
is the mother of all functions, in that, for example, you can implement both
map
and
filter
using
reduce
.
A bit later on, we note that these functions have built-in "list machinery" - see my post "
The beauty of recursion and list machinery" if you want to dig into this a little more.
00:32:40 Starting to build out the functional style equivalent, beginning with a simple
isDiscontinued
function definition.
isDiscontinued = x => x.Discontinued === true
00:45:50 A bit later on we also define a helper function for discounts, which is written in such a way that we can partially apply it:
applyDiscount = percent => price => Math.round(price * ((100 - percent) / 100))
Then we can create new functions based on a partial application of
applyDiscount
thus:
tenPercentOff = applyDiscount(10)
which will allow us to call it like this:
tenPercentOff(200)
//> 180
00:49:50 At this stage we have our new, solid-state equivalent code, which looks like this:
Products
.filter(isDiscontinued)
.map(x => {
let { ProductName: Name, UnitPrice } = x
return {
Name,
SalePrice: tenPercentOff(UnitPrice)
}
})
Doesn't that make you feel a lot more comfortable? No mutating of data, no moving parts (just implicit list machinery); solid-state code that just evaluates. Lovely!
By the way - we also used some other ES6 facilities in this code, can you remember and name them? Put your thoughts in the comments below.
00:54:20 Mentioning the concept of a
Functor, which is a lot less scary than the Wikipedia article might lead you to believe, and is a nice word to put on a tshirt
🙂
00:55:00 Starting to look at the next section, specifically the "challenge" set in the
tweet from Kyle Shevlin.
00:56:40 There is, in fact, a higher order function
find
, a sibling, so to speak, of
map
,
filter
and
reduce
, and it's quite effective and easy to read, but can of course have performance issues if the data set is large. Here's what we did with
find
, again on the Products data set, looking for the product record for 'Ikura':
Products.find(x => x.ProductName === 'Ikura')
//> {ProductID: 10, ProductName: "Ikura", SupplierID: 4, ... }
00:59:00 Effectively, we want to reshape (a term I learned from APL) the Products data like this:
[ ... ] -> { ... }
In other words, from an array to an object. Given that
map
takes an array and produces an array (as does
filter
), we can't use it. But we
can use
reduce
, which we can use to produce a result of any shape!
01:01:00 The
reduce
function is often used to reshape an array of data to a scalar, for example tranforming a list of numbers ...
... to what they all add up to:
nums.reduce((a, x) => a + x, 0)
//> 6
This is the equivalent of this "long hand" version:
nums.reduce(function(accumulator, item) {
accumulator = accumulator + item
return accumulator
})
01:03:00 Looking briefly at
the documentation for reduce
, on MDN, the Mozilla Developer Network and a great resource for JS docu.
01:12:20 Now we're ready to produce an object from the Products array, keyed on the product name. We do it manually, directly, first of all, and this is what it looks like:
Products
.reduce((a, x) => { a[x.ProductName] = x; return a }, {})
//> { Chai: {...}, Chang: {...}, ... }
The key thing to note here is that the starting value is an empty object, which puts us on the right path for our desired transformation:
[ ... ] -> { ... }
01:15:20 Now we think about adding this functionality to the Array object itself, to make it available on all arrays. To discover how to add a function to the Array prototype, we explore first, with a silly example:
Array.prototype.someRandomThing = function(n) { return this.length * n }
[1,2,3,4].someRandomThing(5)
//> 20
Note that we use the
function() { ... }
approach because we need to refer to
this
(to get to the actual array instance) which is not available in fat-arrow based function definitions.
01:18:00 Now we're ready. This is what we end up with:
Array.prototype.makeObjectOn = function(prop) {
return this.reduce((a, x, i) => { a[x[prop] || i] = x; return a }, {})
}
Note that we're using the third parameter
i
that's passed to the callback function, to use a fallback index parameter if the one supplied doesn't exist.
01:28:00 As a final flourish, we add a really nice affectation which allows us to do away with multiple statements inside the inner function - this was introduced to me by
chris.whealy a while ago, and allows us to write functions with single expressions only:
Array.prototype.makeObjectOn = function(prop) {
return this.reduce((a, x, i) => (_ => a)(a[x[prop] || i] = x, {})
}
Take a few minutes to stare at this for a bit, especially the construction
(_ => a)(...)
, which is an immediately invoked function expression (IIFE). You may grow to love it
🙂