const comp = f => g => x => f(g(x))const ord = char => char.charCodeAt(0)const isBetween =(min,max)=> x => (x> = min& x
代码审查
>
顺便说一下,你使用默认参数和泄露的私有API所做的这件事很漂亮
// charC被泄露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
我明白你可能在做这件事,所以你不必写这个
//我猜你想避免这个
const isDigit = char => {
let charC = char.charCodeAt(0)
return charC> 47&& charC< 58
}
...但是这个函数实际上好很多,因为它没有'将私有API( charC
var)泄露给外部调用者
你会注意到我解决这个问题的方式在我的是使用我自己的 isBetween
combinator和currying这导致了一个相当干净的实现,imo
const comp = f => g => x => f(g(x))
const ord = char => char.charCodeAt(0)
const isBetween =(min,max)=> x => (b> = min& x< = max)
const isDigit = comp(isBetween(48,57))(ord)
更多的代码可以完成这个糟糕的默认参数。
//怀疑你认为这有点更好,因为它是一个单线程
//滥用默认参数,例如这是坏的,坏的,坏的
const removeLeadingChars:(str:string)=> ; string =
(str,arr = str.split(''),
dummy = pipe(isDigit,notFun),cnt = recCountWhile(arr,dummy,0))=> (c,c).reduce((acc,e)=> acc.concat(e),'');
尽量避免损害代码的质量,以便使所有内容都成为一行代码。上面的函数比这个更糟糕
//可读性和可靠性的巨大提高
//不泄露变量!
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)
或者如果您想使用我的curried <$
const identity = x => c $ c> comp
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
正在合并具有无数用途的身份函数
I am working in TS but will show the tsc -> ES6 code below.
I have a function 'isDigit' that returns true if the the character code is in the range of digits 0-9. This function (isDigit) must be passed as an argument into a higher order function.
const isDigit = (char, charC = char.charCodeAt(0)) => (charC > 47 && charC < 58);
As part of another higher order function, I need to know if a character is NOT a digit. Of course something like below would work...
const notDigit = (char, charC = char.charCodeAt(0)) => !isDigit(char);
BUT it would be more satisfying if I could compose isDigit with another function (I'll call notFun) to apply a not operator to the result of isDigit to make notDigit. In the code below 'boolCond' serves to control if the not operator is applied or not. The code below 'almost' works, but it returns a boolean not a function which does not work when dealing with higher order functions.
const notFun = (myFun, boolCond = Boolean(condition)) => (boolCond) ? !myFun : myFun;
As is usually the case, while preparing this question I wound up finding an answer, so I will share my answer and see what improvements come from the community.
The issue observed above (getting a boolean instead of a function) is an issue of 'functional composition, I found several optional approaches in the post of Eric Elliot, from which I selected the 'pipe' functional composition method.
see Eric Elliot's excellent post
const pipe = (...fns) => x => fns.reduce((v, f) => f(v), x);
The implementation of this pipe composition function looked like the below TS... For those following along at home, I have included the recursive count while 'recCountWhile' function that is the ultimate consumer of the composed (i.e. piped) function (please excuse the inverted order that these functions appear but this was done for clarity).
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);
The result is a composed function 'removeLeadingChars' that composes the 'isDigit' with the 'notFun' (using the pipe function ) into 'dummy' function that is passed to the recCountWhile function. This returns the count of 'not digits' (i.e. characters other than digits) that lead the string, these characters that are then 'sliced' from the head of the array, and the array is reduced back to a string.
I would be very keen to hear about any tweaks that may improve on this approach.
解决方案
Good on you to find your answer and still post the question. I think this is a nice way to learn.
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
Code review
Btw, this thing you're doing with the default parameters and leaked private API is pretty wonky
// 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 understand you're probably doing it so you don't have to write this
// I'm guessing you want to avoid this
const isDigit = char => {
let charC = char.charCodeAt(0)
return charC > 47 && charC < 58
}
... but that function is actually a lot better because it doesn't leak private API (the charC
var) to the external caller
You'll notice the way I solved this in mine was to use my own isBetween
combinator and currying which results in a pretty clean implementation, 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)
More of your code that does this awful default parameters thing
// 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),'');
Try to avoid compromising the quality of your code for the sake of making everything a one-liner. The above function is much worse than this one here
// 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), '')
}
Keep it simple
Instead of splitting the string into an array, then iterating over the array to count the leading non digits, then slicing the head of the array based on the count, then finally reassembling the array into an output string, you can... keep it simple
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')) // ''
So yeah, I'm not sure if the code in your question was merely there for an exercise, but there's really a much simpler way to remove leading non digits from a string, if that's really the end goal.
The removeLeadningNonDigits
function provided here is pure function, does not leak private variables, handles all inputs for its given domain (String), and maintains an easy-to-read style. I would suggest this (or something like this) compared to the proposed solution in your question.
Function Composition and "Pipe"
Composing two functions is usually done in right-to-left order. Some people find that hard to read/reason about, so they came up with a left-to-right function composer and most people seem to agree that pipe
is a good name.
There's nothing wrong with your pipe
implementation, but I think it's nice to see how if you strive to keep things as simple as possible, the resulting code cleans up a bit.
const identity = x => x
const comp = (f,g) => x => f(g(x))
const compose = (...fs) => fs.reduce(comp, identity)
Or if you'd like to work with my curried comp
presented earlier in the post
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)
Each of these functions have their own independent utility. So if you define compose
in this way, you get the other 3 functions for free.
Contrast this to the pipe
implementation provided in your question:
const pipe = (...fns) => x => fns.reduce((v, f) => f(v), x);
Again, it's fine, but it mixes all of these things together in one function.
(v,f) => f(v)
is useful function on its own, why not define it separately and then use it by name?- the
=> x
is merging the identity function which has countless uses
这篇关于布尔型“不”函数的功能组成(不是布尔值)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!