Search code examples
javascripthaskellfunctional-programmingpurely-functional

Pure Functional approach to generate a unique id


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!


Solution

  • 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]