📜 ⬆️ ⬇️

Ramda Thinking: Pointless Notation

1. First steps
2. We combine functions
3. Partial application (currying)
4. Declarative programming
5. Ruleless Notation
6. Immutability and objects
7. Immutability and arrays
8. Lenses
9. Conclusion


This post is the fifth part of a series of articles on functional programming called "Thinking in the Ramda Style".


In the fourth part, we talked about writing code in a declarative style (explaining the computer what to do) instead of the imperative (telling him how to do it).


You may have noticed that some of the functions that we wrote ( forever21 , drivingAge and water , for example) all take a parameter, create a new function and apply this function to a parameter.


This is a very common pattern in functional programming, and Ramda here again provides us with utilities to clean up our code a little more.


Dotted notation


There are two main guidelines in Ramda, which we have already discussed in the third part .


1. Transfer the latest data
2. Curry all things


These two principles lead to a style that functional programmers call "pointless." I like to think of the pointless code as " Data? And where is the data? There is no data anywhere ."


There is one beautiful post Why Ramda? which perfectly illustrates the style of pointless notation. It has titles like “ Where is my data? ”, “ Okay, everything! Can I see some data? ” And “ I just need my data, thank you .”


We do not yet have the tools necessary for all our examples to become absolutely pointless, but we can already begin to do something.


Let's look at forever21 :


 const forever21 = age => ifElse(gte(__, 21), always(21), inc)(age) 

Notice that age only occurs twice: once in the list of arguments and once at the very end of the function when we use the function that is returned by the ifElse call.


If we are careful when working with Ramda, we will notice this pattern in many places. This almost always means that there is an opportunity to convert the function into a pointless style.


Let's see how it will look like:


 const forever21 = ifElse(gte(__, 21), always(21), inc) 

And, poof! We just made age disappear. Current notation Please note that there are no differences in behavior between these two versions of the functions. This code still returns a function that will get age, but now we don’t explicitly specify the age parameter.


We can also do the same things with alwaysDrivingAge and water .


The last time alwaysDrivingAge looked like this:


 const alwaysDrivingAge = age => ifElse(lt(__, 16), always(16), identity)(age) 

We can apply a similar transformation to it in order to make it pointless:


 const alwaysDrivingAge = when(lt(__, 16), always(16)) 

And we left this water function:


 const water = temperature => cond([ [equals(0), always('water freezes at 0°C')], [equals(100), always('water boils at 100°C')], [T, temp => `nothing special happens at ${temp}°C`] ])(temperature) 

And here is its beschechechnye analogue:


 const water = cond([ [equals(0), always('water freezes at 0°C')], [equals(100), always('water boils at 100°C')], [T, temp => `nothing special happens at ${temp}°C`] ]) 

Multi-argument functions


What about functions that take more than one argument? Let's go back to the third part of the titlesForYear function.


 const titlesForYear = curry((year, books) => pipe( filter(publishedInYear(year)), map(book => book.title) )(books) ) 

Notice that books is found only twice: once as the last parameter in the argument list (the data goes last!), And once at the very end of the function when we use our pipeline. This is similar to the pattern that we saw with age earlier, so let's apply a similar transformation to this situation:


 const titlesForYear = year => pipe( filter(publishedInYear(year)), map(book => book.title) ) 

It is working! Now we have a non- titlesForYear version of titlesForYear .


Honestly, perhaps I would not want to use a pointless version of this function, because JavaScript does not have an agreement to call a series of single-argument functions, which was already discussed in previous posts.


If we want to use titlesForYear in the pipeline, everything will be wonderful. We can just call titlesForYear(2012) . But if we want to use this function separately, we will have to return to the pattern )( which we saw in the previous post: titlesForYear(2012)(books) . In my opinion, it's not worth it.


But at any time, when I have a single-argument function that follows (or can be refactored to follow) the above-described pattern, I almost always make it pointless.


Refractory in beschechny style


There will be situations where our functions will not follow this pattern. We can start working with data several times in one function.


There are several similar examples from the second part . In those examples, we refactored our code to combine functions using such things as both , both , pipe and compose . Once we were done with this, casting these functions to bestotchny became quite simple.


Let's take a look at the isEligibleToVote method. This is where we started:


 const wasBornInCountry = person => person.birthCountry === OUR_COUNTRY const wasNaturalized = person => Boolean(person.naturalizationDate) const isOver18 = person => person.age >= 18 const isCitizen = person => wasBornInCountry(person) || wasNaturalized(person) const isEligibleToVote = person => isOver18(person) && isCitizen(person) 

Let's start with isCitizen . This function takes a person and applies two different functions to it, combining the result with || . As we already learned in the second part , instead we can use either to combine the two functions into a new function and then apply it to the person .


 const isCitizen = person => either(wasBornInCountry, wasNaturalized)(person) 

We can do similar things with isEligibleToVote using both :


 const isEligibleToVote = person => both(isOver18, isCitizen)(person) 

Now that we’re done with this refactoring, we can see that both of our functions follow the pattern we talked about earlier: the person is mentioned twice, once as a function argument and once at the very end of applying our combined functions to it. Now we can convert them to a pointless style:


 const isCitizen = either(wasBornInCountry, wasNaturalized) const isEligibleToVote = both(isOver18, isCitizen) 

Why?


Pointless style takes some time to get used to it. It may be difficult to adapt to the missing data arguments everywhere. It is also important to familiarize yourself with the functions of Ramda, in order to know how many arguments they usually need.


But once you have them, they will become very powerful when you need to create sets of small point-free functions, combined in various interesting ways.


What is the advantage of pointless style? We can argue that this is only an academic lesson, recognized in order to give functional programming another badge. Nevertheless, I think that he still has a few real virtues, even despite the fact that you will have to take the time to get used to it:



Conclusion


Pointless style, also known as silent programming , can make our code cleaner and easier to think about. By refactoring our code to combine all our transformations into a single function, we end up with small building blocks that can be used in many places.


Further


In our examples, we could not refactor everything to the pointless style. We still have code that is written in an imperative style. Most of this code works with objects and arrays.


We need to find declarative ways to work with objects and arrays. And what about immunity? How are we going to manipulate objects and arrays in an immutable style?


The next post in this series, “ Immutability and Objects ” will discuss how we can work with objects in a functional and immiable style. After this, the post “Immutability and Arrays” will be published, in which the same will be discussed in relation to arrays.


')

Source: https://habr.com/ru/post/358452/


All Articles