在功能编程中实现双向计数器?

TbW*_*321 4 javascript functional-programming

我试图围绕一些函数式编程基础知识.

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

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

var myCounter = counter( 2 );
myCounter();
myCounter();
Run Code Online (Sandbox Code Playgroud)

但是,实现双向计数器的正确方法(就功能编程而言)是什么?我想出了以下内容,但它对我来说似乎太像一个便宜的物体了:

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();
Run Code Online (Sandbox Code Playgroud)

Aad*_*hah 6

功能编程完全是字面意思是"用函数编程".在这里,我们讨论的是数学函数,而不是子程序.有什么不同?

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

    阿克曼功能

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

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

    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;
    }
    
    Run Code Online (Sandbox Code Playgroud)

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

现在,考虑你的counter功能:

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

var countup = counter(0);

alert(countup()); // 1
alert(countup()); // 2
alert(countup()); // 3
Run Code Online (Sandbox Code Playgroud)

这是函数式编程吗?简短的回答是,这是有争议的,因为你确实使用高阶函数.但是,您的counter功能不是数学函数.因此,在函数式编程的严格定义中(即使用数学函数进行编程),您的程序并不是真正的功能.

注意:我认为大多数混淆都是因为JavaScript中的第一类子程序被称为函数.实际上,它们可以用作函数.但是,它们不是数学函数.

实际上,您的程序是面向对象的.每次调用时counter都会创建一个表示计数器对象的新抽象数据类型.由于此对象只有一个操作定义,因此您可以从counter函数返回该操作本身.因此,你的直觉是绝对正确的.这不是函数式编程.它是面向对象的编程.

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

在函数式编程中,一切都可以定义为函数.真奇怪.数字怎么样?好吧,数字也可以定义为函数.一切都可以定义为一个功能.

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

var counter = { count: 5 }; // counter :: Counter
Run Code Online (Sandbox Code Playgroud)

打字判断counter :: Counter读作" counter是类型的值Counter".

但是,编写构造函数来构造新的数据结构通常会更好:

// Counter :: Number -> Counter

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

var counter = Counter(5); // counter :: Counter
Run Code Online (Sandbox Code Playgroud)

请注意,值Counter(类型的函数Number -> Counter,读作" Numberto Counter")与类型Counter(表单的数据结构{ count: Number })不同.类型和值可以具有相同的名称.我们知道他们是不同的.

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

// count :: Counter -> Number

function count(counter) {
    return counter.count;
}
Run Code Online (Sandbox Code Playgroud)

这不是很有趣.但是,这是因为Counter数据类型本身并不是很有趣.事实上,Number数据类型和Counter数据类型是同构(即,我们可以将任何数目转换n成等价的计数器c使用函数Counter,我们可以对置转换c回数n使用功能count,并且反之亦然).

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

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

// increment :: Counter -> Counter

function increment(counter) {
    return Counter(count(counter) + 1);
}
Run Code Online (Sandbox Code Playgroud)

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

// decrement :: Counter -> Counter

function decrement(counter) {
    return Counter(count(counter) - 1);
}
Run Code Online (Sandbox Code Playgroud)

请注意,如果我们使用a Number作为a,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
Run Code Online (Sandbox Code Playgroud)

实际上,是的,你可以使用Statemonad 做到这一点.我将切换到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
Run Code Online (Sandbox Code Playgroud)

这是一个可运行的Haskell程序.相当简洁不是吗?这是函数式编程的强大功能.如果您的功能编程理念被卖掉,那么您一定要考虑学习Haskell.

  • 对于想要学习函数式编程的人来说,这可能是一个很好的阅读材料,但我认为它不会回答这个问题...除非你将Haskell代码重写为Javascript.另外我认为`counter`方法是一个纯函数.对于相同的输入,它总是返回相同的结果. (2认同)