We just need to get rid of the block we are not using, and we can also make it curried so is more reusable. And instead of calling it splitName, we just call it what it is: spaceSplit:
@lukeshiru You're right. Thanks for the tips. I tried to keep things in this article (and the video). But yeah, once we get the pipe operator, things are going to be much more fun.
You and me seem to have pretty different definitions of "unclean" .... you might want to take a look at currying ... and "unclean" libraries such as ramda 😅
Guess what isQueenOfSpades contains in ramda docs? Guessing not works in here, in such way need to read source to know. While in normal JS where naming matters it is a boolean.
Is it clean? In common programming sense, name is given to describe what function or variable does, so in this way no, it's not clean. Functional programming has roots in math where naming doesn't matter, they are cool with formulas like this one, but it's not right to expect your teammates will be happy of guessing how your log works.
You don't need to read the source to understand a function, nowadays we have inline docs and typing systems.
Is it really hard for you to figure out what isQueenOfSpades does? Do you prefer the name to be isAnObjectWhichContainsARankQAndASuitOfSpades instead?
A name should hint what a function does, but you don't need to explain the entire implementation in the name.
If you're working with currying, you already know every function is an unary that returns a new function until you have all the arguments.
Programming has roots in binary and asm, and that doesn't have anything to do with the way we do things nowadays. I would never advocate for naming practices like the ones used in languages like Haskell, with single letters. Using Haskell to debate FP practices is like using ASM to justify that programming is hard to understand.
Nobody needs to "guess" how something works if it has good docs, types and a clear enough name.
Do you use libraries without knowing what the utils they provide will return, and relying only on the name? I imagine back in the day you had a really hard time with jQuery's $.get if that's the case. My teammates are perfectly fine with a log function, because when they use it they get autocompletion in their editors telling them the arguments that function takes, the thing it returns, and if they use it wrong they get a red squiggly underline to let them know, not to mention that if they hover over the function or the arguments, they get examples from the JSDocs 😄
Agree, makes sense now, I get used to rely on naming and I'm not hovering things at all, while in this practice programmer has to rely on hovering hint. I've tested and it works nicely, even in plain JS I got hint
log = (prepend: any) => (log: any) => void
Libraries is a different thing and only way is it read docs and memoize frequently used stuff.
$.get - jQuery was intended to do what document.querySelector does before it appeared, so I expect it to get element by selector. I honestly didn't check docs. But if it was ramda I would expect: get(key, object) and get(key)(object), but wrong, it's a 'prop' in ramda.
1 nowadays we have inline docs and typing systems.
So in general thanks for response, in editors it's not hard to get hints.
But not everywhere, while reviewing code in github it can give you hints as well, but far less intelligent and it won't work so nicely.
2 and 3 isAnObjectWhichContainsARankQAndASuitOfSpades
I would have a type Card = { rank: string, suite: string }
And a function getIsCardAQueenOfSpaces.
And now I can assign it to local variable isQueenOfSpaces and use it in imperative way
4 Makes sense, but it means whole project need to follow FP and currying to stay predictable, everyone in team must be good with FP and be on same page
Do you use libraries without knowing what the utils they provide will return, and relying only on the name?
Yes, sure, other teammates may introduce libs which I never used and I'm reviewing the code, and usually it's understandable just by looking on name and usage
Have you actually tried currying? Let's use split as an example to compare different approaches:
// This block are just the values we'll use in our examples, you can ignore themconstspaced1="hello world";constspaced2="goodbye world";constdotted1="hello.world";constdotted2="goodbye.world";constspacedArray=[...Array(10)].fill(spaced1);constdottedArray=[...Array(10)].fill(dotted2);/* Without helper functions ***************************************************/constsplitSpaced1=spaced1.split("");constsplitSpaced2=spaced2.split("");constsplitDotted1=dotted1.split(".");constsplitDotted2=dotted2.split(".");constsplitSpacedArray=spacedArray.map(spaced=>spaced.split(""));constsplitDottedArray=dottedArray.map(dotted=>dotted.split(""));/* With helper functions with no options **************************************/// More code here:constspaceSplit=string=>string.split("");constdotSplit=string=>string.split(".");// But way less code and more readability here:constsplitSpaced1=spaceSplit(spaced1);constsplitSpaced2=spaceSplit(spaced2);constsplitDotted1=dotSplit(dotted1);constsplitDotted2=dotSplit(dotted2);constsplitSpacedArray=spacedArray.map(spaceSplit);constsplitDottedArray=dottedArray.map(dotSplit);/* With helper functions with options *****************************************/// A little bit more code here:constsplit=(string,separator)=>string.split(separator);// A little bit more readability and reuse here:constspaceSplit=string=>split(string,"");constdotSplit=string=>split(split,".");// Usage is the same as above, so same benefits/* With curried functions *****************************************************/// Same amount of characters as above here:constsplit=separator=>string=>string.split(separator);// But way less code here, and even more readable:constspaceSplit=split("");constdotSplit=split(".");// Usage is almost the same as above, with extra benefits, we'll see that next
You can see you how you only write "more" if you use it once, but the power of currying comes from reuse, and that's where it becomes more readable and where you end up writing more. Keeping the examples above, let's say a new requirement appears in which some strings might come with the _ character, here's how we would solve that with each approach:
constsnakeCased="hello_world";constsnakeCasesArray=[...Array(10)].fill(snakeCased);/* Without utils **************************************************************/constsplitSnakeCasedArray=snakeCasesArray.map(snakeCased=>snakeCased.split("_"),);/* With utils without options *************************************************/// Again like before, we wrote more code (it could be less)constsnakeCaseSplit=string=>string.split("_");// But more readability and less code when usingconstsplitSnakeCasedArray=snakeCasesArray.map(snakeCaseSplit);/* With utils with options ****************************************************/// A little bit of reuse hereconstsnakeCaseSplit=string=>split("_",string);// Usage is the same as above/* Finally, with currying *****************************************************/// Way less code, and still readableconstsnakeCaseSplit=split("_");// And usage can be the same as above, but also if is only once place you could:constsplitSnakeCasedArray=snakeCasesArray.map(split("_"));
I understand that every time we do something thinking in reuse it will require a little bit more characters first, but then it requires less everywhere else. In general readability is improved because we go from stuff like this:
array.map(item=>item.split(""));
To stuff like this:
array.map(split(""));
Not to mention that map itself can be curried like this:
constmap=mapper=>array=>array.map(mapper);
And then you get even more reuse and readability:
constspaceSplit=split("");constspaceSplitMap=map(spaceSplit);spaceSplitMap(array);// And the "inline" version for single case uses:map(split(""))(array);
Currying is amazing for code size and reuse, and is very readable if we stay away from "Haskell like" practices like having single character arguments. Hope this helps see the value of currying more clearly. I was requested a few times to do a full post on the subject and I will do it soon, so I'll try to remember to let you know about it if you still don't see the value in this.
Nice article, a few things to add:
We just need to get rid of the block we are not using, and we can also make it curried so is more reusable. And instead of calling it
splitName, we just call it what it is:spaceSplit:Object.assignwhen you want to actually update a newly created object that's more complex than a plain one, take this example:With some luck we'll get the pipe operator soon, and this will be even cleaner:
async/awaitsolution can be also simplified without the need forasync/await:Cheers!
@lukeshiru You're right. Thanks for the tips. I tried to keep things in this article (and the video). But yeah, once we get the pipe operator, things are going to be much more fun.
The article was about general advises, and you are suggesting a functional programming approach which is worth a separate article :)
The approach is tricky, leads to unclean code (what createElement does? creates a function!) so I wouldn't recommend it as a general advise
You and me seem to have pretty different definitions of "unclean" .... you might want to take a look at currying ... and "unclean" libraries such as ramda 😅
Guess what
isQueenOfSpadescontains in ramda docs? Guessing not works in here, in such way need to read source to know. While in normal JS where naming matters it is a boolean.Is it clean? In common programming sense, name is given to describe what function or variable does, so in this way no, it's not clean. Functional programming has roots in math where naming doesn't matter, they are cool with formulas like this one, but it's not right to expect your teammates will be happy of guessing how your
logworks.Let's go one thing at a time:
isQueenOfSpadesdoes? Do you prefer the name to beisAnObjectWhichContainsARankQAndASuitOfSpadesinstead?Do you use libraries without knowing what the utils they provide will return, and relying only on the name? I imagine back in the day you had a really hard time with jQuery's
$.getif that's the case. My teammates are perfectly fine with alogfunction, because when they use it they get autocompletion in their editors telling them the arguments that function takes, the thing it returns, and if they use it wrong they get a red squiggly underline to let them know, not to mention that if they hover over the function or the arguments, they get examples from the JSDocs 😄Agree, makes sense now, I get used to rely on naming and I'm not hovering things at all, while in this practice programmer has to rely on hovering hint. I've tested and it works nicely, even in plain JS I got hint
Libraries is a different thing and only way is it read docs and memoize frequently used stuff.
$.get - jQuery was intended to do what document.querySelector does before it appeared, so I expect it to get element by selector. I honestly didn't check docs. But if it was ramda I would expect: get(key, object) and get(key)(object), but wrong, it's a 'prop' in ramda.
1 nowadays we have inline docs and typing systems.
So in general thanks for response, in editors it's not hard to get hints.
But not everywhere, while reviewing code in github it can give you hints as well, but far less intelligent and it won't work so nicely.
2 and 3
isAnObjectWhichContainsARankQAndASuitOfSpadesI would have a type Card = { rank: string, suite: string }
And a function
getIsCardAQueenOfSpaces.And now I can assign it to local variable
isQueenOfSpacesand use it in imperative way4 Makes sense, but it means whole project need to follow FP and currying to stay predictable, everyone in team must be good with FP and be on same page
Yes, sure, other teammates may introduce libs which I never used and I'm reviewing the code, and usually it's understandable just by looking on name and usage
I don't see the value to use piped functions (even with pipe operator) ? It's less readable and requires more code and characters.
Have you actually tried currying? Let's use split as an example to compare different approaches:
You can see you how you only write "more" if you use it once, but the power of currying comes from reuse, and that's where it becomes more readable and where you end up writing more. Keeping the examples above, let's say a new requirement appears in which some strings might come with the
_character, here's how we would solve that with each approach:I understand that every time we do something thinking in reuse it will require a little bit more characters first, but then it requires less everywhere else. In general readability is improved because we go from stuff like this:
To stuff like this:
Not to mention that map itself can be curried like this:
And then you get even more reuse and readability:
Currying is amazing for code size and reuse, and is very readable if we stay away from "Haskell like" practices like having single character arguments. Hope this helps see the value of currying more clearly. I was requested a few times to do a full post on the subject and I will do it soon, so I'll try to remember to let you know about it if you still don't see the value in this.
Cheers!
I'm seeing a useful of pipe operator now. Thank Luke Shiru.