【问题标题】:Pure Functional approach to generate a unique id生成唯一 ID 的纯函数式方法
【发布时间】:2016-11-04 15:31:21
【问题描述】:

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

我有 JS 组件,当它们被创建时,它们需要为一个 html 元素分配一个唯一的 id,这个 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

我想知道是否有任何方法可以只使用纯函数来做这种事情,因为我正试图围绕一些更高级的纯函数想法。据我所知,这需要 Monads 或类似的东西来完成,但我不知道该怎么做。

谢谢!

【问题讨论】:

  • 全局状态通过以下方式模拟:1) 将状态作为附加参数传递给每 2) 将(可能更新的)状态作为附加返回值返回,以及 3) 确保状态的所有消费者都是正确排序,以便将一个函数的更新状态传递给下一个函数。在 Haskell 中,这是由 State monad 封装的。
  • @chepner 这几乎是一个答案。
  • 嘿@chepner!谢谢(你的)信息!您能否提供一个 StackOverflow 答案,或许可以使用我上面给出的示例在实践中展示这个想法?如果你这样做并且它有效,我很乐意接受答案!
  • 相关:“concurrent-supply”包提供了可拆分标识符生成器,​​可用于处理多线程的更高级场景:hackage.haskell.org/package/concurrent-supply

标签: javascript haskell functional-programming purely-functional


【解决方案1】:

在 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"]

因为对getNextId 的每次“调用”都会“返回”行中的下一个数字。 traverse 函数类似于 map,但它确保每个 monad 的效果发生在将 makeComponent 应用于每个值的过程中。

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


State 类型构造函数本身只是一个函数的包装器,这里是Int -&gt; (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]

【讨论】:

  • 说得很好。为了更加安全,可以编写一个带有newtype 包装器的模块,围绕State Intderiving Monad,并且只导出getNextId, get 和其他东西。这样,进一步防止用户重置计数器,无法访问put
【解决方案2】:

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

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

【讨论】:

    【解决方案3】:

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

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

    要生成随机数,请使用 go:

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

    虽然这看起来像是在调用一个纯函数,但我想说它仍然是一个 hack。正如您在 IIFE 中看到的,我基本上是一次种子()函数,然后基本上将返回的函数存储在变量 generateRandom 中。因此,可以说它不是纯粹的功能性。

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

    【讨论】:

    • 但是由于 generateRandom() 返回具有相同参数的不同结果,这不是完全不是纯函数吗?
    • 是的,你是对的。 generateRandom() 每次都返回一个不同的值,但这就是你希望随机数生成器做的对吗?我收集到你想要一个随机数生成器的功能实现。这里的函数在内部保存状态,使其能够首先生成随机数
    • 这个实现所做的只是改变可变变量的范围;因此,它根本没有回答这个问题(尽管至少你对此很坦率)。
    • @VivekPradhan 我想我应该澄清一下,我不想要一个纯函数来做到这一点,这是不可能的,但是一个系统,你可以通过它只使用纯函数来获得与此类似的功能功能。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-02-19
    • 2013-12-05
    • 2012-02-22
    • 1970-01-01
    • 2011-01-25
    • 2017-08-26
    相关资源
    最近更新 更多