What for is for? What if no if?
How you can improve your data layer using higher order functions (map, compactMap, flatMap, filter, sorted) and direct return.
Let’s imagine we are working as an iOS developers for one of the largest social medias and after it is purchased by a huge tech-investor, our team is to implement a data layer for new analytics system where managers will be able to check metrics about content makers, so business decisions can be taken.
We have models above to parse jsons into them, so data from backend can be worked out. We operate (models are extremely shortened in our example for the sake of simplicity, in real life they would have about 50–100 properties):
- struct Bloggers - we parse an array of bloggers to work with (let’s say, they were sorted basing on our request by the server using some predicate: region, age, whatever)
- struct Blogger - describes a single blogger (we state, that blogger can not register without a post, so posts array is obligatory, but, at the same time, blogger can, for example, write about Objective-C, so it is really possible that there are no subscribers, haha. Due to that this array is optional)
- struct Post - contains amount of views on a single post and a boolean flag stating if it is sponsored, pretty clear
- struct Subscriber - also simple enough, subscriber’s nickname and a flag showing if the account is premium
Our task is implementing functionality to:
- Check if the blogger is popular (the blogger is considered popular if he or she has more than 10000 views on publications)
- Check if monetization can be turned on for the blogger (platform enables monetization if the blogger has at least one premium subscriber and one sponsored post)
- Show all blogger’s premium subscribers nicknames
- Sort group of bloggers by a total amount of views every blogger has and show nicknames of bloggers from this group after that
- Check if group of bloggers is financially efficient (group of bloggers is considered financially efficient when the views rate on sponsored posts is higher than on regular ones)
In order to get tasks completed, we decide to extend our models by functionality of returning information directly from model instances. So we create two files: Bloggers+Extensions and Blogger+Extensions in order to start writing our code. After that, let’s start to code!
Task 1
This is going to be quite simple, we should go over all the blogger’s posts adding amount of views from each post to a local container and then return the value of it. After that, we should check if amount of views is more than 10k and return true or false respectivelly.
🙅♂️ Bad solution
We’ve decided to create separate totalAmountOfViews property since we know we will need it to complete task 4.
💁♂️ Good solution
Both variants return the same, but the second one is compact, and looks more readable (after you get good experience using this approach, lol). Let’s provide an explanation for those who meet such coding way for the first time:
As we see, when map() is used on array of posts, it produces a collection of numbers, since by ‘$0.amountOfViews’ we stated that every ‘amountOfViews’ property of every post is to be placed into an array. After we’ve got our [Int] collection, reduce() function summarizes all numbers from it and returns the sum of all the sequence’s numbers. isPopular property just actually says “return me the result of comparing totalAmountOfViews to 10000”, so if totalAmountOfViews is less than 10k, it returns false, if more, it returns true.
Technically, the logic of “Good solution” is slightly different from “Bad solution”, but it saves you a bunch of space and looks more advanced. Also, as we have only one line of code, we can arrange a direct return (without using a ‘return’ operator).
Task 2
In here, we should iterate over all blogger’s subscribers and posts to find out if there are premium and sponsored ones. Then, if both predicates are satisfied, we state that platform can enable monetization for the blogger. In order to do that, we collect all premium subscribers and sponsored posts to separate arrays and after that check whether they are empty. If they are not, we know that blogger can have monetization turned on.
🙅♂️ Bad solution
In this case we’ve chosen the way of constructing separate variables for subscribers and posts since we know this functionality will be needed for implementing tasks 3 and 5. We also need a ‘nonSponsoredPosts’ variable for task 5, but we will ignore it for the sake of code snippet size economy.
💁♂️ Good solution
Let’s give a quick look how variables above work.
premiumSubscribers:
As we see, when we compactMap() an optional array with ‘$0’ argument passed into, what it does is just actually maps all the elements of array, deleting nil values, so we get non-optional array as a result. After that we call filter() function and give it a predicate, which states “the resulting array should have only subscribers which have isPremium property equal to true”.
Never call map() on optional arrays, since it can cause the app to crash!
‘sponsoredPosts’ and ‘nonSponsoredPosts’ variables work pretty straight forward, they just return an array of filtered posts which are either sponsored or not (depending on ‘$0.isSponsored == true/false’ predicates). After we have everything settled, we can create ‘monetizationCanBeEnabled’ property, which depends on ‘premiumSubscribers’ and ‘sponsoredPosts’ variables.
Task 3
What we need to do here is extract all the premium subscribers nicknames. As we already have ‘premiumSubscribers’ property, which returns an array of blogger’s premium subscribers, we can surely use it in here.
🙅♂️ Bad solution
💁♂️ Good solution
Since we already know how map() works, no explanation is needed I guess. Without a ‘premiumSubscribers’ property code above would look like that:
Task 4
Now we have to calculate total amount of views for each blogger, sort bloggers by this amount of views and extract their nicknames. We are going to extend Bloggers struct instead of Blogger this time, since we are working with group of bloggers, not with a single one. Also, in both examples we create ‘sortedByPopularity’ property, which returns array of Blogger objects which are sorted and will be used then for extracting nicknames out of it.
🙅♂️ Bad solution (as almost every sorting you create by yourself)
Do not blame me, mechanism actually could be better, haha.
💁♂️ Good solution
By ‘$0.totalAmountOfViews > $1.totalAmountOfViews’ statement we say that we want the elements of resulting array to be sorted in the way so ‘totalAmountOfViews’ value of left element should be higher that value of it’s right element [100, 70, 50] for example.
And finally we can extract nicknames using property below (you didn’t plan to use a loop for that right?)
Task 5
In this task we have to analyze a whole bloggers group’s financial efficiency by summarizing all views on sponsored posts this group made and doing the same with views on non-sponsored posts. After we make a comparison of those views total amount, we can state if this group is profitable.
🙅♂️ Bad solution
As creating two separate variables for sponsored and regular posts will make us to repeat a bunch of code, we decide to put all the logic into one variable.
💁♂️ Good solution
As code repeating is not critical space-wise now, we can afford it. Let’s give a brief explanation how it works.
As we are using ‘sponsoredPosts’ property from task 2, we can shorten our declaration for a bit. Just in case, without it declaration would look like:
So, what it does is it takes array of bloggers, maps all the posts from all the bloggers (on this step we have array of arrays of type Post), combines these arrays into just one array by a flatMap(), filters posts, so only sponsored are left, maps all amountOfViews properties into new array and summarizes it.
Yeah, last one looks quite complicated, but believe me, when you gain a strong command with higher order functions, you will easily combine them into one statement depending on your needs.
After we get total amount of views on both types of posts, we just compare them and see if group of bloggers is financially efficient.
As a bonus tip, you can also play with access levels if you don’t want some parts of your business logic to be seen out of the class / module (for instance, you can make only ‘areFinanciallyEfficient’ property public, so instance, interacting with a class / module knows that group of bloggers is profitable if sponsored posts views rate is higher than rate on regular ones, but at the same time make ‘totalViewsOnSponsoredPosts’ private, so nobody knows how it is counted and what is the way your model is built respectively).
Conclusion
Using higher order functions and direct return improves code readability, takes less space and makes your code look advanced.
As an advice, always when you plan to use for loop in order to complete some operation with a sequence, try to think if it can be substituted by one of the higher order functions. If you need to arrange some transformations, more than likely you do not need to use for loop and will be good with one of the functions we discussed in this article.