javascript - 在函数式编程中实现双向计数器?

标签 javascript functional-programming

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

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

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();

最佳答案

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

  • 这是一个函数( Ackermann function ):

    Ackermann function

    数学函数是纯的(即它没有副作用)。数学函数只做一件事:它将输入值映射到输出值。它不会改变任何变量的值。
  • 这是一个计算阿克曼函数结果的子程序:

    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;
    }
    

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

  • 现在,考虑您的 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 (即,我们可以使用函数 n 将任何数字 c 转换为等效的计数器 Counter,我们可以使用函数 c 将计数器 n 转换回数字 count,反之亦然) .

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

    所以,现在我们要更新 counter 的值使用名为 increment 的函数.坚持,稍等。在函数式编程中,函数不能改变变量的值。我们如何更新 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然后是 incrementdecrement计数器操作 c将被定义为 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)。我将切换到 Haskell,因为我需要一种比 JavaScript 更好的函数式编程语言:

    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 .

    关于javascript - 在函数式编程中实现双向计数器?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32235143/

    相关文章:

    JavaScript:滚动时消失的脉冲按钮

    javascript - CommonJs模块系统中 "module.exports"和 "exports"之间的区别

    javascript - 使用 css 使表格 td 灵活

    scala - 根据 A 成员将 List[A] 转换为 List[Option[A]]

    r - 在 R 中优化 Apply()

    Haskell Netwire - 类型错误

    javascript - 让鼠标滚轮选框无限大?

    javascript - 如何使用 jQuery 禁用单击并稍后重新启用它

    string - 使用 Stream API 比较两个字符串并计算差异

    javascript - 函数参数中的对象解构是一个好的模式吗?