Разрешает ли Элм циркулярные ссылки?

Предположим, два типа данных:

type alias Player = 
  { name : String
  , team : Team
  }

type alias Team =
  { name : String
  , players : List Player
  }

И это JSON:

{
  "players": [
    { "id": 100, "name": "Sam Bradford", "teamId": 200 },
    { "id": 101, "name": "Kyle Rudolph", "teamId": 200 },
    { "id": 102, "name": "Matthew Stafford", "teamId": 201 },
    { "id": 103, "name": "Marvin Jones Jr.", "teamId": 201 },
    { "id": 104, "name": "Golden Tate", "teamId": 201 },
  ],
  "teams": [
    { "id": 200, "name": "Minnesota Vikings" },
    { "id": 201, "name": "Detroit Lions" },
  ]
}

Ясно, что этот JSON может быть декодирован в ненулевые связанные объекты, и это может быть определено JSON-декодером при декодировании данных. Есть ли способ декодировать этот JSON и создавать связанные структуры данных? Я не уверен, как это сделать с чисто неизменяемыми структурами данных, или если это возможно.

2 ответа

Решение

Здесь есть хорошее объяснение рекурсивных типов данных в Elm.

Если вы попытаетесь скомпилировать ваши типы данных, вы получите следующую ошибку:

-- ALIAS PROBLEM ---------------------------------------------------------------

This type alias is part of a mutually recursive set of type aliases.

4|>type alias Player = 
5|>  { name : String
6|>  , team : Team
7|>  }

The following type aliases are mutually recursive:

    ┌─────┐
    │     V
    │    Player
    │     │
    │     V
    │    Team
    └─────┘

You need to convert at least one `type alias` into a `type`. This is a kind of
subtle distinction, so definitely read up on this before you make a fix:
<https://github.com/elm-lang/elm-compiler/blob/0.17.0/hints/recursive-alias.md>

Вы также можете иметь дело с этим по-другому. Я предпочитаю прибегать к ID ссылки, например,

type alias ID = Int

type alias PlayerList = Dict ID PLayer
type alias TeamList = Dict ID Team

type alias Player = 
  { name : String
  , teamID : ID
  }

type alias Team =
  { name : String
  , players : List ID
  }

ОБНОВЛЕНИЕ: Вы также упоминаете null ссылки в вашем вопросе. В вышеперечисленных типах данных каждый игрок ДОЛЖЕН иметь команду. Если команда является необязательным полем, я бы определил следующее:

type alias Player = 
  { name : String
  , teamID : Maybe ID
  }

За List а также String типы, вам не нужно Maybe тип. Пустое состояние для тех легче с [] (пустой список) и "" (пустой строки).

PS: Другое предположение заключается в том, что у вас есть особая необходимость хранить список командных игроков в каждой команде вашей модели. Потому что, строго говоря, вам не нужно: в списке игроков уже есть все данные, необходимые для того, чтобы узнать, какие игроки (если таковые имеются) принадлежат команде X. Обратите внимание, что это большая работа (и может привести к неприятным ошибкам) если вы поддерживаете двустороннюю ссылку: если игрок меняет команды, вам нужно обновить 3 записи (1 игрок + 2 записи команды).

С неизменяемыми структурами данных невозможно создать полностью связанные иерархии значений, которые ссылаются друг на друга каким-то круговым способом.

По своей сути проблема может быть проиллюстрирована в несколько шагов:

  1. Создать ребенка с Nothing для его родительского поля
  2. Создайте родителя, который указывает на ребенка
  3. Обновите ребенка, чтобы он указывал на родителя

Число 3 невозможно, если у вас есть неизменяемые структуры данных, поскольку "модификация" дочернего элемента означает, что вы создаете новое значение, а родительский элемент все еще будет указывать на это старое значение. Если вы обновите родительский элемент, вам придется обновить дочерний элемент до бесконечности.

Я бы порекомендовал использовать Dict как игроков, так и команд, проиндексированных по их идентификаторам для поиска.

Другие вопросы по тегам