I jump between Java and Javascript a lot at work. I also write a lot of functional code, i.e. chains of lambdas.
One thing I like about Java is I can replace a lambda with a method reference. For example, I can replace this code
List<String> trimmedStrings = List.of("hi ", "bye ").stream()
.map(original -> original.trim())
.collect(toList());
with this:
List<String> trimmedStrings = List.of("hi ", "bye ").stream()
.map(String::trim)
.collect(toList());
I often make this replacement because I'm usually happier with the end state of how the code looks.
I have been wondering if I can do this with Javascript. I just tested this code in my browser console:
["hi ", "bye "].map(original => original.trim());
first I tried replacing it the simple way, which worked but doesn't accomplish my goal:
["hi ", "bye "].map(original => String.prototype.trim.apply(original))
So I figured the following would work, but it didn't:
["hi ", "bye "].map(String.prototype.trim.apply)
it gave me an error saying Uncaught TypeError: Can't call method on undefined
(in Firefox).
So my questions are:
String.prototype.trim
is just a function. When you call "foo".trim()
you call that function and set "foo"
as the context. This is just how methods work in JavaScript. I might suggest a method
helper to get around this -
const method = f => f.call.bind(f)
const list = [
" alice ",
" bob ",
" charlie "
]
console.log(list.map(method("".trim)))
Another option is to define trim
in advance -
const method = f => f.call.bind(f)
const trim = method("".trim)
const list = [
" alice ",
" bob ",
" charlie "
]
console.log(list.map(trim))
[
"alice",
"bob",
"charlie"
]
Some methods take additional arguments. method
works with that too -
const method = f => f.call.bind(f)
const trim = method("".trim)
const replace = method("".replace)
const list = [
" alice ",
" bob ",
" charlie "
]
console.log(list.map(trim).map(v => replace(v, "e", "E")))
[
"alicE",
"bob",
"charliE"
]
You can rewrite method
to enable tacit programming (aka point-free style) -
const method = f => (...args) => data =>
f.apply(data, args)
const trim = method("".trim)()
const upper = method("".toUpperCase)()
const replace = method("".replace)
const list = [
" alice ",
" bob ",
" charlie "
]
console.log(list.map(trim).map(replace(/[aeiou]/g, upper)))
[
"AlIcE",
"bOb",
"chArlIE"
]
Finally we can make method
smarter to analyze f.length
to determine if more arguments should be supplied by the caller -
const method = f =>
f.length == 0
? data => f.call(data)
: (...args) => data => f.apply(data, args)
const trim = method("".trim)
const upper = method("".toUpperCase)
const replace = method("".replace)
const list = [
" alice ",
" bob ",
" charlie "
]
console.log(list.map(trim).map(replace(/[aeiou]/g, upper)))
[
"AlIcE",
"bOb",
"chArlIE"
]