生成唯一 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
这更像是一个理论问题,但我觉得一定有办法做到这一点。
我有一些 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