我在TS工作,但会显示tsc - > ES6代码如下。
我有一个函数'isDigit',如果字符代码在数字0-9的范围内,则返回true。必须将此函数(isDigit)作为参数传递给更高阶函数。
const isDigit = (char, charC = char.charCodeAt(0)) => (charC > 47 && charC < 58);
作为另一个高阶函数的一部分,我需要知道一个字符是不是数字。当然下面的内容会起作用......
const notDigit = (char, charC = char.charCodeAt(0)) => !isDigit(char);
但如果我可以用另一个函数(我将调用notFun)将isDigit应用于isDigit的结果来生成notDigit,那将会更令人满意。在下面的代码中,'boolCond'用于控制是否应用not运算符。下面的代码“几乎”可以工作,但它返回一个布尔值,而不是一个在处理高阶函数时不起作用的函数。
const notFun = (myFun, boolCond = Boolean(condition)) => (boolCond) ? !myFun : myFun;
通常情况下,在准备这个问题时,我最终找到答案,所以我将分享我的答案,看看社区有哪些改进。
上面观察到的问题(得到一个布尔而不是一个函数)是一个'功能组合问题',我在Eric Elliot的帖子中找到了几个可选方法,我选择了'管道'功能组合方法。
see Eric Elliot's excellent post
const pipe = (...fns) => x => fns.reduce((v, f) => f(v), x);
这个管道组合函数的实现看起来像下面的TS ...对于那些在家里跟随的人,我已经包含递归计数而'recCountWhile'函数是组合(即管道)函数的最终消费者(请原谅这些函数出现的反转顺序,但这样做是为了清楚起见。)
export const removeLeadingChars: (str: string) => string =
(str, arr = str.split(''),
dummy = pipe(isDigit, notFun), cnt = recCountWhile(arr, dummy, 0)) =>
arr.slice(cnt).reduce((acc, e) => acc.concat(e),'');
export const recCountWhile: (arr: string[], fun: (char: string) => boolean, sum: number) => number =
(arr, fun, sum, test = (!(arr[0])) || !fun(arr[0]) ) =>
(test) ? sum : recCountWhile(arr.slice(1), fun, sum + 1);
结果是一个组合函数'removeLeadingChars',它将'isDigit'与'notFun'(使用管道函数)组合成'dummy'函数,该函数传递给recCountWhile函数。这将返回引导字符串的'not digits'(即数字以外的字符)的计数,这些字符然后从数组的头部“切片”,然后数组缩减回字符串。
我非常希望听到任何可能改进这种方法的调整。
答案 0 :(得分:2)
很高兴你找到答案并仍然发布问题。我认为这是一种很好的学习方式。
为了进行功能组合练习,以下是我如何构建你的功能。
请参阅保持简单,了解如何使用实用代码处理此问题
const comp = f => g => x => f(g(x))
const ord = char => char.charCodeAt(0)
const isBetween = (min,max) => x => (x >= min && x <= max)
const isDigit = comp (isBetween(48,57)) (ord)
const not = x => !x
const notDigit = comp (not) (isDigit)
console.log(isDigit('0')) // true
console.log(isDigit('1')) // true
console.log(isDigit('2')) // true
console.log(isDigit('a')) // false
console.log(notDigit('0')) // false
console.log(notDigit('a')) // true
代码审核
顺便说一下,你用默认参数和泄露的私有API做的事情是非常不可取的
// charC is leaked API
const isDigit = (char, charC = char.charCodeAt(0)) => (charC > 47 && charC < 58);
isDigit('9') // true
isDigit('9', 1) // false wtf
isDigit('a', 50) // true wtf
我知道你可能正在这样做,所以你不必写这个
// I'm guessing you want to avoid this
const isDigit = char => {
let charC = char.charCodeAt(0)
return charC > 47 && charC < 58
}
...但是该功能实际上要好得多,因为它不会将私有API(charC
var)泄露给外部调用者
你会注意到我解决这个问题的方法是使用我自己的isBetween
组合器和currying,这会产生一个非常干净的实现,imo
const comp = f => g => x => f(g(x))
const ord = char => char.charCodeAt(0)
const isBetween = (min,max) => x => (x >= min && x <= max)
const isDigit = comp (isBetween(48,57)) (ord)
执行这个可怕的默认参数的更多代码
// is suspect you think this is somehow better because it's a one-liner
// abusing default parameters like this is bad, bad, bad
const removeLeadingChars: (str: string) => string =
(str, arr = str.split(''),
dummy = pipe(isDigit, notFun), cnt = recCountWhile(arr, dummy, 0)) =>
arr.slice(cnt).reduce((acc, e) => acc.concat(e),'');
尽量避免损害代码的质量,以使所有内容都成为一行代码。以上功能比这里的功能差得多
// huge improvement in readability and reliability
// no leaked variables!
const removeLeadingChars: (str: string) => string = (str) => {
let arr = str.split('')
let dummy = pipe(isDigit, notFun)
let count = recCountWhile(arr, dummy, 0)
return arr.slice(count).reduce((acc, e) => acc.concat(e), '')
}
保持简单
不是将字符串拆分成数组,而是迭代数组以计算前导非数字,然后根据计数切割数组的头部,然后最终将数组重新组合成输出字符串,您可以..保持简单
const isDigit = x => ! Number.isNaN (Number (x))
const removeLeadingNonDigits = str => {
if (str.length === 0)
return ''
else if (isDigit(str[0]))
return str
else
return removeLeadingNonDigits(str.substr(1))
}
console.log(removeLeadingNonDigits('hello123abc')) // '123abc'
console.log(removeLeadingNonDigits('123abc')) // '123abc'
console.log(removeLeadingNonDigits('abc')) // ''
所以是的,我不确定你问题中的代码是否只是用于练习,但是如果真的是最终目标,那么从字符串中删除前导非数字真的有一种更简单的方法。
此处提供的removeLeadningNonDigits
函数是纯函数,不会泄漏私有变量,处理其给定域(String)的所有输入,并保持易于阅读的样式。与你提出的解决方案相比,我会建议这个(或喜欢这个)。
功能组合和“管道”
组合两个函数通常按从右到左的顺序进行。有些人发现难以阅读/推理,所以他们想出了一个从左到右的函数作曲家,大多数人似乎都同意pipe
是一个好名字。
您的pipe
实现没有任何问题,但我认为很高兴看到您如何尽可能地保持简单,最终的代码会清理一下。
const identity = x => x
const comp = (f,g) => x => f(g(x))
const compose = (...fs) => fs.reduce(comp, identity)
或者,如果您想使用帖子中前面提到的comp
const identity = x => x
const comp = f => g => x => f(g(x))
const uncurry = f => (x,y) => f(x)(y)
const compose = (...fs) => fs.reduce(uncurry(comp), identity)
这些功能中的每一个都有自己独立的实用程序。因此,如果以这种方式定义compose
,则可以免费获得其他3个函数。
将此与您问题中提供的pipe
实施对比:
const pipe = (...fns) => x => fns.reduce((v, f) => f(v), x);
同样,它很好,但它将所有这些东西混合在一起。
(v,f) => f(v)
本身就是一个有用的功能,为什么不单独定义它然后按名称使用呢?=> x
正在合并具有无数用途的身份功能