4

This is more of a theoretical question but I feel like there must be a way to do this.

I have JS components for which, when they are created, they need to assign a unique id to an html element, one that hasn't been used in any other component. This is pretty trivial normally:

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

I'm wondering if there's any way to do this kind of thing using just pure functions, as I'm trying to wrap my head around some more advanced pure functional ideas. As far as I can tell it would require Monads or something of that variety to accomplish, but I don't know how to do so.

Thank you!

Colton Voege
  • 123
  • 1
  • 5
  • 6
    Global state is simulated by 1) passing the state as an additional argument to every 2) returning the (possibly updated) state as an additional return value, and 3) ensuring that all consumers of the state are properly sequenced, so that the updated state from one function is passed to the next function. In Haskell, this is encapsulated by the `State` monad. – chepner Nov 04 '16 at 15:50
  • @chepner That is pretty much an answer already. – duplode Nov 04 '16 at 15:53
  • Hey @chepner! Thanks for the info! Could you provide a StackOverflow answer perhaps that would display this idea in practice using the example I gave above? If you do and it works I'd be glad to accept the answer! – Colton Voege Nov 04 '16 at 15:53
  • 1
    Related: the "concurrent-supply" package provides splittable identifier generators useful in more advanced scenarios dealing with multiple threads: http://hackage.haskell.org/package/concurrent-supply – danidiaz Nov 04 '16 at 16:37
  • Possibly related: https://stackoverflow.com/questions/6311512/creating-unique-labels-in-haskell . – atravers Sep 15 '20 at 00:36

3 Answers3

4

In Haskell, you might write something like

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

The above script would output

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

as each "call" to getNextId would "return" the next number in line. The traverse function is something like map, but it ensures that the effect of each monad takes place during the course of applying makeComponent to each value.

This link may provide some assistance in adapting this to Javascript.


The State type constructor itself is just a wrapper around a function, here of type Int -> (Int, Int). The Monad instance for this type lets you avoid writing code that looks like this:

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]
chepner
  • 497,756
  • 71
  • 530
  • 681
  • 1
    Nicely put. For even more safety, one can write a module with a `newtype` wrapper around `State Int`, `deriving Monad`, and export only `getNextId, get` and little else. In such way, the user is further prevented to reset the counter, not having access to `put`. – chi Nov 04 '16 at 17:04
1

Pure function would imply you don't mutate state. Thus this will work:

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

For you example you need to changeit slightly so that state can be passed:

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
Sylwester
  • 47,942
  • 4
  • 47
  • 79
-1

You could use closures to save the state of the counter like so:

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

To generate random numbers use just go:

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

Although this seems like you are calling a pure function, it is still a hack I would say. I am basically the seed() function once as you can see in the IIFE and then basically storing the returned function in the variable generateRandom. So, it is not purely functional so to speak.

But, I hope it gets you started in the right direction.

Vivek Pradhan
  • 4,777
  • 3
  • 26
  • 46
  • 1
    But isn't this kinda exactly not a pure function since generateRandom() returns a different result with the same parameters? – Colton Voege Nov 04 '16 at 16:26
  • Yes you are right. `generateRandom()` returns a different value everytime but that is what you want the random number generator to do right? I gathered that you want to have a functional implementation of the random number generator. The function here saves the state internally which enables it to generate random numbers in the first place – Vivek Pradhan Nov 04 '16 at 16:29
  • 2
    All this implementation does is changing the scope of the mutable variable; therefore, it doesn't answer the question at all (though at least you are upfront about that). – duplode Nov 04 '16 at 16:56
  • 1
    @VivekPradhan I think I should clarify, I don't want a single pure function to do this, that would not be possible, but a system by which you can get similar functionality to this using only pure functions. – Colton Voege Nov 04 '16 at 21:24