2

Это скорее теоретический вопрос, но я чувствую, что должен быть способ сделать это.Чистый Функциональный подход для генерации уникального идентификатора

У меня есть компоненты JS, для которых, когда они созданы, им необходимо назначить уникальный идентификатор элементу html, который не был использован ни в одном другом компоненте. Это довольно тривиальное правило:

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 

Я задаюсь вопросом, есть ли способ сделать такого рода вещи, используя только чистые функции, как я пытаюсь обернуть мою голову вокруг некоторых более продвинутых чистых функциональных идей. Насколько я могу судить, для этого потребуются Монады или что-то в этом роде, но я не знаю, как это сделать.

Спасибо!

+6

Глобальное состояние моделируется 1) пропусканием состояние в качестве дополнительного аргумента каждые 2) возврат (возможно, обновленное) состояние в качестве дополнительного возвращаемого значения и 3) обеспечение правильной последовательности всех потребителей состояния, чтобы обновленное состояние от одной функции передавалось следующей функции. В Haskell это инкапсулируется монадой «State». – chepner

+0

@chepner Это уже почти ответ. – duplode

+0

Эй, @чепнер! Спасибо за информацию! Не могли бы вы предоставить ответ StackOverflow, возможно, который продемонстрировал бы эту идею на практике, используя приведенный выше пример? Если вы это сделаете, и это сработает, я был бы рад принять ответ! –

ответ

4

В 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, но это гарантирует, что эффект каждой монады происходит во время применения 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] 
+1

Красиво положить. Для еще большей безопасности можно написать модуль с оболочкой 'newtype' вокруг' State Int', 'получение Monad' и экспортировать только' getNextId, get' и немного больше. Таким образом, пользователю дополнительно препятствует сброс счетчика, не имея доступа к 'put'. – chi

-1

Вы можете использовать затворы для сохранения состояния счетчика так:

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

Для генерации случайных чисел используют просто пойти:

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

Хотя это, кажется, как вы вызываете чистую функцию , это все равно хак, который я бы сказал. Я в основном функция seed(), как вы можете видеть в IIFE, а затем в основном сохраняя возвращаемую функцию в переменной generateRandom. Таким образом, это не чисто функционально, так сказать.

Но, надеюсь, вы начнете в правильном направлении.

+1

Но разве это не совсем точно функция, поскольку generateRandom() возвращает другой результат с теми же параметрами? –

+0

Да, вы правы. 'generateRandom()' возвращает другое значение каждый раз, но это то, что вы хотите, чтобы генератор случайных чисел работал правильно? Я понял, что вы хотите иметь функциональную реализацию генератора случайных чисел. Функция здесь сохраняет состояние внутри, что позволяет ему генерировать случайные числа в первую очередь –

+2

Вся эта реализация делает изменение области изменяемой переменной; поэтому он вообще не отвечает на вопрос (хотя, по крайней мере, вы об этом заранее). – duplode

1

Чистая функция будет означать, вы не мутировать состояние.Таким образом, это будет работать:

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 

Для вас, например, вы должны немного changeit, так что состояние может быть передано:

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