首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >函数式/声明式FizzBuzz

函数式/声明式FizzBuzz
EN

Code Review用户
提问于 2019-07-26 06:50:56
回答 3查看 433关注 0票数 2

今天,我花了一些时间试图以功能性/声明性的方式编写FizzBuzz。我认为这将是一个很好的机会来获得一些反馈,因为我已经做了大约一年的函数式编程。

(我知道这里有一些类似的问题,但它们对代码的处理方法与我的不同,最好能得到更具体于我的代码的反馈)

我想知道的主要事情是:

  • 这段代码与函数式和声明式编程的最佳实践有多大的一致性?
  • 有什么不好的做法,我应该知道吗?
代码语言:javascript
复制
const isFizz = number => number % 5 === 0
const isBuzz = number => number % 3 === 0

const newArrayInRange = (min, max) => [...Array(max + 1).keys()].slice(min)

const fizzBuzz = (min, max) => newArrayInRange(min, max)
  .map(number => {
    if (isFizz(number) && isBuzz(number)) {
      return 'fizzbuzz'
    }
    if (isFizz(number)) {
      return 'fizz'
    }
    if (isBuzz(number)) {
      return 'buzz'
    }
    return number
  })
  .join('\n')

console.log(fizzBuzz(1, 100))
EN

回答 3

Code Review用户

发布于 2019-07-26 22:58:52

可能会改变一些

  • 移除保持计数器所需的数组,对于计数器来说,这是一个巨大的开销。
  • 避免使用(num % val)返回字符串和使用空字符串添加数字的过多逻辑语句(参见标记为/*A*/的代码)
  • 将所有内容放入数组中,以便在完成时像您所做的那样加入。这是在JavaScript中构建长字符串的最快方法。

因此,我们的结局是.

代码语言:javascript
复制
const fizz = num => num % 5 ? "" : "fizz";
const buzz = num => num % 3 ? "" : "buzz";
const fizzBuzz = num => fizz(num) + buzz(num);
const fizzBuzzer = (min, max) => {
    const res = [];
    do {
        const fb =  fizzBuzz(min);
        res.push(fb ? fb : min);       /*A*/
    } while (min++ < max);

    return res.join("\n");
}

就个人而言,函数fizzbuzzfizzBuzz只是在毫无理由的情况下添加代码,因此需要关闭,以避免污染它所处的范围。

这样,三个函数就成为了const fb =的表达权。

此外,我不喜欢开放范围内的箭头函数,所以使用函数声明来确保可访问性。

代码语言:javascript
复制
function fizzBuzzer(min, max) {
    const res = [];
    do {
        const fb =  (min % 5 ? "" : "fizz") + (min % 3 ? "" : "buzz");
        res.push(fb ? fb : min);
    } while (min++ < max);

    return res.join("\n");
}

你问

这段代码与函数式和声明式编程的最佳实践有多大的一致性?

最好的实践,那是主观的,上下文的,只有当与“坏”代码相比时才会有比较的意义?

您的代码还不错,可以工作,三年前由于JS引擎处理数组的方式,情况可能会更糟,但现在大多数优化器都认识到了这种模式并使其正常运行。

有无数种方法可以编写任何代码。对于分号的使用以及自动分号插入是否是一个好主意,JS程序员仍然不能达成一致意见,那么,如何使用任何代码都是最好的做法。

有什么不好的做法,我应该知道吗?

哦,是的,有..。分号,它们在哪里?

代码语言:javascript
复制
// Example of bad
const fizzBuzzard = (min, max) => {
    const next = n => n <= max ? order[n % 15] : () => "";
    const txt = w => n => (w ? w : n + "\n") + next(++n)(n);
    const [n, b, f, fb] = [txt(), txt("buzzard\n"), txt("fizz\n"), txt("fizzBuzzard\n")];
    const order = [fb, n, n, b, n, f, b, n, n, b, f, n, b, n, n];
    return next(min)(min);
}
console.log(fizzBuzzard(1, 100));
票数 3
EN

Code Review用户

发布于 2019-07-27 01:10:06

这段代码与函数式和声明式编程的最佳实践有多大的一致性?

我不知道。除非您正在做一些像手卷单簧管这样的有趣的事情,否则您可能只是在“用函数编程”,而不是真正地进行“函数式编程”。你可以问克罗克福德,如果他不设法从你的下一次当地技术会议中得到邀请的话。

有什么不好的做法,我应该知道吗?

在我看来没问题。

就我个人而言,我喜欢这里的另一个答案。我投票支持它,并希望它赢得车臣选举,因为它是非常实用的。

但这不是我所说的“功能性”

代码语言:javascript
复制
const fizzbuzz = (min, max) =>
    Array(max + 1).fill('', min)
        .map((v, i) => i % 5 ? v : 'fizz')
        .map((v, i) => i % 3 ? v : v + 'buzz')
        .map((v, i) => v || i)
        .join('\n').trim()

如果问题中的代码是"functional“,我想这是”更实用的“,因为我们摆脱了更多的流控制,将更多的函数链接在一起,并在其中抛出更多的lambdas。不过,我绝对不会把它称为“声明式”。在我看来,它仍然是必需的;一系列命令将以特定的顺序执行。因此,我认为没有理由比盲人67‘S的方法更看重这样的东西。

有些任务更适合于声明式风格(数据转换,实际发生的顺序并不重要的事情,例如XSLT ),还有一些任务更适合于命令式风格(任何需要精确的流控制的东西)。当然,Fizzbuzz可以使用正确的语言或库以声明的方式完成。但是对于普通的JavaScript来说,以一种强制的方式去做似乎要简单得多。

票数 2
EN

Code Review用户

发布于 2020-05-24 09:23:08

首先,我想说代码在某种程度上是功能性的,所以我将指出我喜欢什么,什么可以(甚至)更实用。

我喜欢isFizzisBuzz,因为它们是声明性和纯函数(即它们没有明显的副作用,而且是确定性的)。我还喜欢map不使用中间变量,因为这将是一种更必要的方法。

一项建议是在不使用中间变量的情况下,尝试减少map中的潜在评估数(对于每个numberisFizzisBuzz都可能执行两次)。我的建议可以在下面的toTerms函数或Ramdba伪代码中找到。

在学习完v3功能光JavaScript课程之后,我也尝试编写了一个声明性/功能性FizzBuzz实现。

在这门课中,凯尔·辛普森建议逐渐转换成声明式风格,我觉得这很有帮助。此外,它还导致了一种过度设计的实现,但我认为它确实显示了在找到适当的声明性替代方案之前我必须考虑的部分代码。

该实现是在这把小提琴中实现的,我的中间进度也是这样。它还包含进一步重构的建议。这是撰写本报告时的状况:

代码语言:javascript
复制
const range = (function rangeInner(acc) {
	return (first, last) => {
    if(first > last) {
      return acc;
    }
    return rangeInner([...acc, first])(first + 1, last);  	
  }
})([]);

const isMod3 = n => n % 3 === 0;
const isMod5 = n => n % 5 === 0;

const predicatesAndTerms = [
    [isMod3, "Fizz"],
    [isMod5, "Buzz"]
]

const toTerms = n => predicatesAndTerms
	.reduce((acc, [predicateFn, term]) => acc += predicateFn(n) ? term : "", "");

const toFizzBuzz = n => toTerms(n) || n.toString();

console.log(range(1, 15).map(toFizzBuzz));

为了避免对每个数字计算isMod3 (问题中的isFizz)和isMod5两次,我在一个由“谓词”和“术语”组成的元组数组上重构为reduce,该元组由一个直接返回值的三元运算符计算。我还尽量避免中间变量,强迫自己创建微小的内聚函数。

在早期版本中,我使用了类似于问题中的range实现,但我重构它以尝试递归和acc参数的闭包,并选择稍后使用R.curry重构它(其中R引用兰达)。

使用Ramda进一步重构toFizzBuzz的伪代码可以是:

代码语言:javascript
复制
const orDefault = fn => n => fn(n) || n; // looks like R.defaultTo, but executes fn over n first.
const toTermsOrDefault = orDefault(toTerms);
const toString = n => n.toString();
const toFizzBuzz = compose(toString, toTermsOrDefault);

祝您学习FP JavaScript好运!如果你想讨论FP JS,我一直很感兴趣。

票数 -1
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/224951

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档