每天推薦一個 GitHub 優質開源項目和一篇精選英文科技或編程文章原文,歡迎關注開源日報。交流QQ群:202790710;微博:https://weibo.com/openingsource;電報群 https://t.me/OpeningSourceOrg
今日推薦開源項目:《別人家的HTML和CSS Pure CSS Francine》GitHub鏈接
推薦理由:事先聲明推薦這個項目並不是讓你們去嘗試而是去參觀的。這是一個用 HTML 和 CSS 去畫18世紀風格油畫的項目,當我們還在用它們寫網頁的時候,別人已經開始拿去創造藝術了。作者給自己規定了所有元素必須手工製作,不使用 Atom 以外的編輯器等等的要求,好了廢話不多說直接上圖來看看別人家的 HTML 和 CSS 到底是什麼樣的吧。
鏈接:http://diana-adrianne.com/purecss-francine/
今日推薦英文原文:《Spice up your JavaScript》作者:Riccardo Odone
原文鏈接:https://medium.com/@riccardoodone/spice-up-your-javascript-5314bf28f3e5
推薦理由:這篇文章中提到了一些小技巧,可以讓你的 JS 代碼變得簡潔而易懂,推薦給正在使用 JavaScript 的朋友們一讀。
Spice up your JavaScript
Which one of the following equivalent implementation do you prefer?
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10] .filter(int => isEven(int)) .filter(int => isBiggerThan(3, int)) .map(int => int + 1) .map(int => toChar(int)) .filter(char => !isVowel(char)) .join(『』)
// 『fhjl』
vs
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10] .filter(isEven) .filter(isBiggerThan(3)) .map(plus(1)) .map(toChar) .filter(not(isVowel)) .join(『』)
// 『fhjl』
I』d argue the second one is waaaay more readable. The secret is removing the arguments from the filter
s and map
s.
Let』s see how it works. I promise, once you see how that works, you won』t be able to unsee it anymore.
A Simple Function
Let』s take a sum function
const sum = (a, b) => a + b
sum(1, 2) // 3
and rewrite it in a different way
const csum = a => b => a + b
sum(1)(2) // 3
They work the same, the only difference is how you call them: sum
accepts two parameters at once, csum
accepts parameters one by one. In particular, let』s say you call csum
only once with the term 1
csum(1) // b => 1 + b
then you get back a function that accepts the second term and returns it incremented by one
const plusOne = csum(1)
plusOne(2) // 3
Operating on Arrays
In JavaScript arrays can be manipulated with various methods. For instance, map
is used to apply the same function to each element of an array.
To increment each integer in an array
[1, 2, 3].map(x => x + 1) // [2, 3, 4]
In other words, x => x + 1
takes an integer and returns its successor. Using plusOne
from above the function can be rewritten to
[1, 2, 3].map(x => plusOne(x)) // [2, 3, 4]
But wait a sec, x => plusOne(x)
and just plusOne
are equivalent, in fact
const otherPlusOne = x => plusOne(x) otherPlusOne(1) // 2
plusOne(1) // 2
For the same reason
[1, 2, 3].map(x => plusOne(x)) // [2, 3, 4]
is equivalent to
[1, 2, 3].map(plusOne) // [2, 3, 4]
and since plusOne
was defined above as const plusOne = csum(1)
[1, 2, 3].map(csum(1)) // [2, 3, 4]
Now your turn, apply the same process used for sum
to isBiggerThan
so that you don』t need to specify arguments in the filter
(ie int =>
)
const isBiggerThan = (threshold, int) => int > threshold
[1, 2, 3, 4].filter(int => isBiggerThan(3, int))
Now the code from the intro shouldn』t have any more secrets to you.
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10] .filter(isEven) .filter(isBiggerThan(3)) .map(plus(1)) .map(toChar) .filter(not(isVowel)) .join(『』)
// 『fhjl』
Two Simple Rules
Rule 1
The following two are equivalent
[…].map(x => fnc(x))
[…].map(fnc)
Rule 2
It』s always possible to rewrite a callback to remove arguments
const fnc = (x, y, z) => … […].map(x => fnc(x, y, z))
const fnc = (y, z) => x => … […].map(fnc(y, z))
You』ve prolly applied this transformation if you』ve worked on the isBiggerThan
exercise. In fact, let』s say we want to keep integers bigger than 3
const isBiggerThan = (threshold, int) => int > threshold […].filter(int => isBiggerThan(3, int))
Now we can rewrite isBiggerThan
to remove the int =>
part in the filter
const isBiggerThan = threshold => int => int > threshold […].map(isBiggerThan(3))
Go After It
Let』s say you have
const keepGreatestChar = (char1, char2) => char1 > char2 ? char1 : char2
keepGreatestChar(『b』, 『f』) // 『f』 // because 『f』 comes after 『b』
Rewrite keepGreatestCharBetweenBAnd
to remove the char
argument
const keepGreatestChar = (char1, char2) => char1 > char2 ? char1 : char2
const keepGreatestCharBetweenBAnd = char => keepGreatestChar(『b』, char)
keepGreatestCharBetweenBAnd(『a』) // 『b』 // because 『b』 comes after 『a』
Rewrite greatestCharInArray
using keepGreatestChar
to remove the arguments (ie (acc, char)
) from inside reduce
const keepGreatestChar = (char1, char2) => char1 > char2 ? char1 : char2
const greatestCharInArray = array => array.reduce((acc, char) => acc > char ? acc : char, 『a』)
greatestCharInArray([『a』, 『b』, 『c』, 『d』]) // 『d』
Implement creduce
so that greatestCharInArray
can employ it and not need any arguments
const creduce = … const greatestCharInArray = array => array.reduce((acc, char) => acc > char ? acc : char, 『a』)
creduce
should be generic enough to be applied to any problems that require a reduce operation. In other words, it should receive callback, an init value and the array to operate upon.
Hint: you want to be able to write
const greatestCharInArray = creduce(keepGreatestChar, 『a』)
greatestCharInArray([『a』, 『b』, 『c』, 『d』]) // 『d』
What the Hell is that `c` in `csum` and `creduce`?
c
stands for curried and what you』ve seen above is how curried functions can make your code more readable. To be precise I haven』t used proper curried functions in this article but that』s good enough.
If you want to dig deeper the art and science of curried functions I recommend reading Chapter 4 of Mostly Adequate Guide to Functional Programming. And since you are there, trust me, read the book entirely.
Also, if you want to read more about functional programming in JavaScript, take a look at FP in vanilla JavaScript: a rookie』s intro.
每天推薦一個 GitHub 優質開源項目和一篇精選英文科技或編程文章原文,歡迎關注開源日報。交流QQ群:202790710;微博:https://weibo.com/openingsource;電報群 https://t.me/OpeningSourceOrg