Чисто функциональный подход для создания уникального идентификатора

Это больше теоретический вопрос, но я чувствую, что должен быть способ сделать это.

У меня есть 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

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

Спасибо!


person Colton Voege    schedule 04.11.2016    source источник
comment
Глобальное состояние моделируется путем 1) передачи состояния в качестве дополнительного аргумента каждому 2) возврата (возможно, обновленного) состояния в качестве дополнительного возвращаемого значения и 3) обеспечения правильной последовательности всех потребителей состояния, чтобы обновленное состояние из одной функции передается в следующую функцию. В Haskell это инкапсулируется монадой State.   -  person chepner    schedule 04.11.2016
comment
@chepner Это уже почти ответ.   -  person duplode    schedule 04.11.2016
comment
Привет, @chepner! Спасибо за информацию! Не могли бы вы предоставить ответ StackOverflow, который, возможно, отобразил бы эту идею на практике, используя пример, который я привел выше? Если вы это сделаете, и это сработает, я был бы рад принять ответ!   -  person Colton Voege    schedule 04.11.2016
comment
Связано: пакет concurrent-supply предоставляет генераторы разделяемых идентификаторов, полезные в более сложных сценариях, связанных с несколькими потоками: hackage .haskell.org/package/concurrent-supply   -  person danidiaz    schedule 04.11.2016


Ответы (3)


В 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 к каждому значению.

Эта ссылка может помочь в адаптации этого к 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]
person chepner    schedule 04.11.2016
comment
Красиво сказано. Для еще большей безопасности можно написать модуль с оболочкой newtype вокруг State Int, deriving Monad и экспортировать только getNextId, get и немного больше. Таким образом, пользователь дополнительно не может сбросить счетчик, не имея доступа к put. - person chi; 04.11.2016

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

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
person Sylwester    schedule 04.11.2016

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

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

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

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

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

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

person Vivek Pradhan    schedule 04.11.2016
comment
Но разве это не совсем чистая функция, поскольку generateRandom() возвращает другой результат с теми же параметрами? - person Colton Voege; 04.11.2016
comment
Да, ты прав. generateRandom() каждый раз возвращает другое значение, но это то, что вы хотите, чтобы генератор случайных чисел делал правильно? Я понял, что вы хотите иметь функциональную реализацию генератора случайных чисел. Функция здесь сохраняет состояние внутри, что позволяет ей в первую очередь генерировать случайные числа. - person Vivek Pradhan; 04.11.2016
comment
Все, что делает эта реализация, — это изменяет область действия изменяемой переменной; поэтому он вообще не отвечает на вопрос (хотя, по крайней мере, вы прямо об этом говорите). - person duplode; 04.11.2016
comment
@VivekPradhan Я думаю, я должен уточнить, я не хочу, чтобы это делала одна чистая функция, это было бы невозможно, но система, с помощью которой вы можете получить аналогичную функциональность, используя только чистые функции. - person Colton Voege; 05.11.2016