Как поместить sh элемент в массив внутри Map, используя функциональное программирование в TypeScript / JavaScript? - PullRequest
1 голос
/ 01 апреля 2020

Я только начал свой путь от OOP фона к изучению FP и к процессу перехода от написания обычного TypeScript (обязательно?) К функциональному коду TypeScript. К сожалению, я уже пытаюсь выяснить, как преобразовать это в функциональный код:

const foos: Map<
  string,
  Bar[]
> = new Map();

export const addBar = (
  key: string,
  bar: Bar
) => {
  const foo = foos.get(key);

  if (foo) {
    foo.push(bar);
  } else {
    foos.set(key, [bar]);
  }
};

Я понимаю, как .map .filter .concat можно использовать в массиве, но как работать с картой, содержащей массивы ?

Что касается карты foos, то я думаю, что сама карта должна быть доступна только для чтения, а также массив Bar внутри нее, поэтому .set .pu sh невозможна. Но если я не могу вызвать .set на Карте, потому что она доступна только для чтения, имеет ли смысл использовать Карту или мне просто нужно использовать объект?

Без изменчивости, как сделать sh элемент для массив в значениях карты (или создать новую карту с массивом, если ключ еще не существует, как в коде выше)?

И достаточно ли это производительности, так как мне нужно будет добавить новый Элемент массива каждые две секунды, будет ли неизменный способ копировать всю карту (включая ее множество массивов) каждый раз, когда происходит изменение, не намного хуже, чем если бы я только что мутировал массив, как вы это обычно делаете?

Ответы [ 2 ]

2 голосов
/ 01 апреля 2020

Вы просто не можете использовать собственный Map, поскольку он обеспечивает только обязательный интерфейс.

Вы можете обратиться к библиотеке с открытым исходным кодом, такой как популярный Immutable JS.

или Вы можете написать свои собственные постоянные (неизменяемые) структуры данных. Основным требованием является то, что операции, предоставляемые вашей структурой данных , не изменяют входные данные. Вместо этого новая структура данных возвращается с каждой операцией -

const PersistentMap =
  { create: () =>
      ({})
  , set: (t = {}, key, value) =>
      ({ ...t, [key]: value })      // <-- immutable operation
  }

Сначала мы рассмотрим карту empty, результат операции set, а затем убедитесь, что empty Карта не изменена -

const empty =
  PersistentMap.create()

console.log
  ( empty
  , PersistentMap.set(empty, "hello", "world")
  , empty
  )

// {}
// { hello: "world" }
// {}

Теперь давайте посмотрим на новое промежуточное состояние, m1. Каждый раз, когда мы видим, set возвращает новую постоянную карту и не изменяет ввод -

const m1 =
  PersistentMap.set(empty, "hello", "earth")

console.log
  ( m1
  , PersistentMap.set(m1, "stay", "inside")
  , m1
  )
// { hello: "earth" }
// { hello: "earth", stay: "inside" }
// { hello: "earth" }

Теперь, чтобы ответить на ваш вопрос, мы можем добавить операцию push к нашему PersitentMap - нам нужно только убедиться, что мы не изменяем ввод. Вот одна из возможных реализаций -

const PersistentMap =
  { // ...

  , push: (t = {}, key, value) =>
      PersistentMap.set            // <-- immutable operation
        ( t
        , key
        , Array.isArray(t[key])
            ? [ ...t[key], value ] // <-- immutable operation
            : [ value ]
        )
  }

Мы видим push в действии ниже. Обратите внимание, что m2 или empty в результате изменяются -

const m2 =
  PersistentMap.push(empty, "fruits", "apple")

console.log
  ( m2
  , PersistentMap.push(m2, "fruits", "peach")
  , m2
  , empty
  )

// { fruits: [ "apple" ] }
// { fruits: [ "apple", "peach" ] }
// { fruits: [ "apple" ] }
// {}

Разверните фрагмент ниже, чтобы проверить результаты в своем собственном браузере

const PersistentMap =
  { create: () =>
      ({})
  , set: (t = {}, key, value) =>
      ({ ...t, [key]: value })
  , push: (t = {}, key, value) =>
      PersistentMap.set
        ( t
        , key
        , Array.isArray(t[key])
            ? [ ...t[key], value ]
            : [ value ]
        )
  }

const empty =
  PersistentMap.create()

console.log
  ( empty
  , PersistentMap.set(empty, "hello", "world")
  , empty
  )
// {}
// { hello: "world" }
// {}

const m1 =
  PersistentMap.set(empty, "hello", "earth")

console.log
  ( m1
  , PersistentMap.set(m1, "stay", "inside")
  , m1
  )
// { hello: "earth" }
// { hello: "earth", stay: "inside" }
// { hello: "earth" }

const m2 =
  PersistentMap.push(empty, "fruits", "apple")

console.log
  ( m2
  , PersistentMap.push(m2, "fruits", "peach")
  , m2
  , empty
  )
// { fruits: [ "apple" ] }
// { fruits: [ "apple", "peach" ] }
// { fruits: [ "apple" ] }
// {}
0 голосов
/ 01 апреля 2020

Я думаю, это зависит от того, чего вы хотите достичь. Если вы хотите, чтобы ваш код был тестируемым, FP не всегда означает просто написание функций, вы все равно можете использовать классы, но если у вас есть сложный кусок кода, который вы хотите протестировать отдельно, вы можете экспортировать этот кусок, чтобы протестировать его, и это выглядело бы примерно так:

// types.ts
type FooDis = Record<string, object[]>;

// addBarToFoos.ts
export const addBarToFoos = (foos: FooDis) => (key: string, bar: object): FooDis {
  foos = {
    ...foos,
    [key]: [
      ...foos[key],
      bar
    ]
  };

  return foos;
}

// FooClass.ts 
export class FooClass {
  private foos: FooDis = {};

  addBar(key: string, bar: object) {
    this.foos = addBarToFoos(this.foos)(key, bar);
  }
}

Таким образом, «сложный» метод можно тестировать отдельно без внешних зависимостей, и у вас есть реализация, использующая этот метод.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...