布尔型“不”函数的功能组成(不是布尔值) [英] functional composition of a boolean 'not' function (not a boolean value)

查看:113
本文介绍了布尔型“不”函数的功能组成(不是布尔值)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述



我有一个函数'isDigit',如果字符代码是在数字0-9范围内。这个函数(isDigit)必须作为参数传递给高阶函数。

  const isDigit =(char,charC = char .charCodeAt(0))=> (charC> 47&& charC< 58); 

作为另一个高阶函数的一部分,我需要知道一个字符是不是数字。当然下面的东西可以工作......

  const notDigit =(char,charC = char.charCodeAt(0))= > !ISDIGIT(焦); 

但是如果我能用另一个函数编写isDigit会更令人满意(我会调用notFun)将not运算符应用于isDigit的结果以使notDigit。在下面的代码中,'boolCond'用于控制是否应用非操作符。 'almost'下面的代码有效,但它返回一个布尔函数,而不是一个在处理高阶函数时不起作用的函数。

  const notFun =(myFun,boolCond = Boolean(condition))=> (boolCond)? !myFun:myFun; 

通常情况下,在准备这个问题时我最终找到了答案,所以我会分享我的回答,并看看来自社区的改进。 上面观察到的问题(得到一个布尔函数而不是函数)是一个'功能组合'的问题,我在Eric Elliot的帖子中发现了几个可选的方法,从中我选择了'pipe'功能组合方法。

见埃里克艾略特的优秀帖子

  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))=> (c,c).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]))=>
(测试)? sum:recCountWhile(arr.slice(1),fun,sum + 1);

结果是一个组合函数'removeLeadingChars',用'notFun'组成'isDigit'(使用管道函数)传递给传递给recCountWhile函数的'虚拟'函数。这将返回引导字符串的'非数字'(即数字以外的字符)的计数,然后这些字符将从数组的头部切片,并将数组缩减回字符串。



我很想听听任何可能改善这种方法的调整。

解决方案

很好,你找到你的答案,仍然发布这个问题。我认为这是一种很好的学习方式。


为了实现功能组合练习,下面介绍我可能会构造你的函数。



请参阅下面的保持简单以了解如何使用实际代码处理此问题。


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 => !xconst notDigit = comp(not)(isDigit)console.log(isDigit('0'))// trueconsole.log(isDigit('1'))// trueconsole.log(isDigit('2'))// trueconsole.log(isDigit('a'))// falseconsole.log(notDigit('0'))// falseconsole.log(notDigit('a'))// true


代码审查

>

顺便说一下,你使用默认参数和泄露的私有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.

For the sake of a function composition exercise, here's how I might structure your functions.

see Keep it simple below for how I would handle this with practical code

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

这篇关于布尔型“不”函数的功能组成(不是布尔值)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆