【问题标题】:Implement a Bi-Directional Counter in Functional Programming?在函数式编程中实现双向计数器?
【发布时间】:2015-08-26 19:28:26
【问题描述】:

我正在尝试围绕一些函数式编程基础知识。

所以,通过使用高阶函数,我可以创建一个可以递增的计数器:

function counter( start ) {
  var count = start;
  return function() {
    return ++count;
  }
}

var myCounter = counter( 2 );
myCounter();
myCounter();

但是,实现双向计数器的正确方法(就函数式编程而言)是什么?我想出了以下内容,但对我来说这似乎太便宜了:

function bicounter( start ) {
  var count = start;
  var mutate = function(amount) {
    return function() { count += amount; }
  };
  return {
    increment: mutate(1),
    decrement: mutate(-1)
  }
}

var myCounter = bicounter( 2 );
myCounter.increment();
myCounter.decrement();

【问题讨论】:

  • 看起来不错。如果打算完全避免突变,也许您可​​以使用递归并传递 n + 1n - 1 和回调。
  • 如果您只想了解高阶函数(返回函数的函数),那么您的代码就可以了。但在更广泛的 FP 观点中,计数器的概念并不存在,因为计数器只不过是“可以递增和递减的可变状态”。

标签: javascript functional-programming


【解决方案1】:

函数式编程的字面意思是“用函数编程”。在这里,我们谈论的是mathematical function,而不是subroutine。有什么区别?

  1. 这是一个函数(Ackermann function):

    数学函数是纯的(即它没有副作用)。数学函数做一件事:它将输入值映射到输出值。它不会改变任何变量的值。

  2. 这是一个计算阿克曼函数结果的子程序:

    function A(m, n) {
        var stack = [], exit;
    
        do {
            if (m > 0) {
                m = m - 1;
    
                while (n > 0) {
                    stack.push(m);
                    n = n - 1;
                }
    
                n = 1;
            } else {
                m = stack.pop();
                n = n + 1;
            }
        } while (m !== exit);
    
        return n;
    }
    

    子程序可能是纯的,也可能不是纯的。例如,上面的子程序是不纯的,因为它修改了变量mnstack。因此,虽然它计算的是阿克曼函数的结果,它是一个数学函数,但它不是一个数学函数。

现在,考虑您的 counter 函数:

function counter(count) {
    return function () {
        return ++count;
    };
}

var countup = counter(0);

alert(countup()); // 1
alert(countup()); // 2
alert(countup()); // 3

这是函数式编程吗?简短的回答是,这是有争议的,因为您确实在使用higher-order functions。但是,您的 counter 函数不是数学函数。因此,在函数式编程(即使用数学函数编程)的严格定义中,您的程序并不是真正的函数式。

注意:我认为大部分混淆的产生是因为在 JavaScript 中,一流的子例程被称为函数。实际上,它们可以用作函数。但是,它们不是数学函数。

实际上,您的程序是面向对象的。每次调用 counter 时,您都在创建一个新的 abstract data type 代表计数器对象。由于该对象只定义了一个操作,因此您可以从counter 函数中返回该操作本身。因此,您的直觉是绝对正确的。这不是函数式编程。这是面向对象的编程。

那么您将如何使用函数式编程来实现计数器?

在函数式编程中,一切都可以定义为函数。那很奇怪。数字呢?好吧,numbers can be defined as functions too一切都可以定义为一个函数。

然而,为了简单起见,让我们假设我们有一些原始数据类型,例如Number,并且我们可以使用structural typing 定义新的数据类型。例如,任何具有{ count: Number } 结构的对象(即任何具有名为count 的单一属性的对象,其类型为Number)都是Counter 类型的值。例如,考虑:

var counter = { count: 5 }; // counter :: Counter

打字判断counter :: Counter读作“counterCounter类型的值”。

但是,通常最好编写一个构造函数来构造新的数据结构:

// Counter :: Number -> Counter

function Counter(count) {
    return { count: count };
}

var counter = Counter(5); // counter :: Counter

注意Counter的值(它是Number -> Counter类型的函数,读作“NumberCounter”)不同于Counter类型(它是形成{ count: Number })。类型和值可以具有相同的名称。我们知道它们是不同的。

现在,让我们编写一个返回计数器值的函数:

// count :: Counter -> Number

function count(counter) {
    return counter.count;
}

这不是很有趣。然而,这是因为Counter 数据类型本身并不是很有趣。事实上,Number 数据类型和Counter 数据类型是isomorphic(即我们可以使用函数Counter 将任何数字n 转换为等效的计数器c,我们可以将计数器转换为@使用函数count将987654365@变回数字n,反之亦然。

因此,我们本可以避免定义 Counter 数据类型并使用 Number 本身作为计数器。但是,出于教学目的,让我们为 Counter 使用单独的数据类型。

所以,现在我们要使用名为increment 的函数来更新counter 的值。坚持,稍等。函数不能改变函数式编程中变量的值。我们如何更新counter 的值?好吧,我们无法更新counter 的值。但是,我们可以返回一个带有更新值的新计数器。这正是我们在函数式编程中所做的:

// increment :: Counter -> Counter

function increment(counter) {
    return Counter(count(counter) + 1);
}

同样,我们可以定义decrement函数:

// decrement :: Counter -> Counter

function decrement(counter) {
    return Counter(count(counter) - 1);
}

请注意,如果我们使用Number 作为Counter,则计数器cincrementdecrement 操作将分别定义为c + 1c - 1。这进一步加强了我们对计数器只是一个数字的理解。

这都是花花公子,但函数式编程的意义何在?

目前,函数式编程似乎比普通编程更难。毕竟,有时你真的需要变异来编写有趣的程序。例如,您可以使用函数式编程轻松做到这一点吗?

function bicounter(count) {
    return {
        increment: update(+1),
        decrement: update(-1)
    };

    function update(amount) {
        return function () {
            return count += amount;
        };
    }
}

var counter = bicounter(0);
alert(counter.increment()); // 1
alert(counter.decrement()); // 0

实际上,是的,您可以使用 State monad 做到这一点。由于需要比 JavaScript 更好的函数式编程语言,我将切换到 Haskell:

import Control.Monad.State

type Counter = Int

counter :: Counter
counter = 0

increment = modify (+1)
decrement = modify (subtract 1)

alert = get >>= (liftIO . print)

program = do
    increment
    alert
    decrement
    alert

main = evalStateT program counter

这是一个可运行的 Haskell 程序。是不是很简洁?这就是函数式编程的力量。如果您对函数式编程的想法很感兴趣,那么您绝对应该考虑learning Haskell

【讨论】:

  • 这对于想要学习函数式编程的人来说可能是一个很好的阅读材料,但我认为它不能回答这个问题......除非你将 Haskell 代码重写为 Javascript。另外我认为counter 方法是一个纯函数。对于相同的输入,它总是返回相同的结果。
  • 谢谢,这里有很多很棒的内容要考虑!
  • 您好,您能否也复习一下这个 CodeReview 问题:codereview.stackexchange.com/q/140812/14625
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-06-25
  • 2010-12-23
  • 2023-03-16
  • 2014-07-01
  • 2013-06-03
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多