生成唯一 ID 的纯函数式方法

Pure Functional approach to generate a unique id

这更像是一个理论问题,但我觉得一定有办法做到这一点。

我有一些 JS 组件,在创建它们时,它们需要为 html 元素分配一个唯一的 ID,该元素尚未在任何其他组件中使用过。这通常是非常微不足道的:

let currentId = 0;
function getNextId() {
  currentId += 1;
  return currentId;
}

function MyComponent() {
  this.id = getNextId();

  // Code which uses id
}

let c1 = new MyComponent();
// c1.id === 1
let c2 = new MyComponent();
// c2.id === 2

我想知道是否有任何方法可以只使用纯函数来做这种事情,因为我正在努力思考一些更高级的纯函数思想。据我所知,它需要 Monad 或类似的东西才能完成,但我不知道该怎么做。

谢谢!

您可以像这样使用闭包来保存计数器的状态:

 var generateRandom = (function seed(){
          var start = 0;
            return function(){
             return start++;
            }
         })();

要生成随机数,只需使用:

 generateRandom(); //logs 0
 generateRandom(); //logs 1
 generateRandom(); //logs 2 and so on..

虽然这看起来像是在调用纯函数,但我认为它仍然是 hack。正如您在 IIFE 中看到的那样,我基本上是一次 seed() 函数,然后基本上将返回的函数存储在变量 generateRandom 中。所以,可以说它不是纯粹的功能。

但是,我希望它能让您朝着正确的方向开始。

在Haskell中,你可以这样写

import Control.Monad.State

data Component  = Component Int String deriving (Show)

getNextId :: State Int Int
getNextId = do x <- get
               put (x + 1)
               return x

makeComponent :: String -> State Int Component
makeComponent name = do x <- getNextId
                        return (Component x name)

components = evalState (traverse makeComponent ["alice", "bob"]) 0

main = print $ components

以上脚本会输出

[Component 0 "alice",Component 1 "bob"]

因为每个 "call" 到 getNextId 都会 "return" 下一个数字。 traverse 函数类似于 map,但它确保每个 monad 的效果在将 makeComponent 应用于每个值的过程中发生。

This link 可能会提供一些帮助以适应 Javascript。


State 类型构造函数本身只是函数的包装器,这里的类型为 Int -> (Int, Int)。此类型的 Monad 实例可让您避免编写如下代码:

getNextID :: Int -> (Int, Int)
getNextID x = (x, x+1)

makeComponent :: String -> Int -> (Int, Int)
makeComponent name x = let (now, then) = getNextID x
                       in  (then, Component now name)

components = let state_0 = 0
                 (state_1, c1) = makeComponent "alice" state_0
                 (state_2, c2) = makeComponent "bob" state_1
             in [c1, c2]

纯函数意味着您不会改变状态。因此这将起作用:

function idGenerator(fnNext, aInit) {
  function Gen(value){this.value = value}
  Gen.prototype.next = function() {
      return new Gen(fnNext(this.value));
  };
  return new Gen(aInit);
}

const evenGen = idGenerator(function(n){return n+2;}, 2);
evenGen.value                     //==> 2
const evenGen2 = evenGen.next();
evenGen2.value                    //==> 4

const loop = function (seq, acc) {
  return seq.value > 16 ?
         acc : 
         loop(seq.next(), seq.value+acc);
}
const sumEven = loop(evenGen, 0);
console.log(sumEven);             //==>  72

对于您的示例,您需要稍微更改它以便可以传递状态:

const seq = idGenerator(function(n){return n+1;}, 1);

function MyComponent(seq) {
  this.id = seq.value;
  this.seq = seq;

  // Code which uses id
}

let c1 = new MyComponent(seq);
// c1.id === 1
let c2 = new MyComponent(c1.seq.next());
// c2.id === 2